blob: da257266ee084247eb5a52c60891fc00fa3c19e1 [file] [log] [blame]
Dichen Zhang85b37562022-10-11 11:08:28 -07001/*
2 * Copyright 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Dichen Zhangdbceb0e2023-04-14 19:03:18 +000017#include <ultrahdr/jpegr.h>
18#include <ultrahdr/jpegencoderhelper.h>
19#include <ultrahdr/jpegdecoderhelper.h>
20#include <ultrahdr/gainmapmath.h>
21#include <ultrahdr/jpegrutils.h>
22#include <ultrahdr/multipictureformat.h>
23#include <ultrahdr/icc.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040024
Dichen Zhanga8766262022-11-07 23:48:24 +000025#include <image_io/jpeg/jpeg_marker.h>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000026#include <image_io/jpeg/jpeg_info.h>
27#include <image_io/jpeg/jpeg_scanner.h>
28#include <image_io/jpeg/jpeg_info_builder.h>
29#include <image_io/base/data_segment_data_source.h>
Dichen Zhang53751272023-01-17 19:09:01 -080030#include <utils/Log.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040031
Dichen Zhang6438a192023-01-29 07:51:15 +000032#include <map>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040033#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000034#include <sstream>
35#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000036#include <cmath>
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080037#include <condition_variable>
38#include <deque>
39#include <mutex>
40#include <thread>
41#include <unistd.h>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000042
43using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000044using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070045
Dichen Zhangdbceb0e2023-04-14 19:03:18 +000046namespace android::ultrahdr {
Dichen Zhang85b37562022-10-11 11:08:28 -070047
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080048#define USE_SRGB_INVOETF_LUT 1
49#define USE_HLG_OETF_LUT 1
50#define USE_PQ_OETF_LUT 1
51#define USE_HLG_INVOETF_LUT 1
52#define USE_PQ_INVOETF_LUT 1
Dichen Zhang10959a42023-04-10 16:28:16 -070053#define USE_APPLY_GAIN_LUT 1
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080054
Nick Deakinf6bca5a2022-11-04 10:43:43 -040055#define JPEGR_CHECK(x) \
56 { \
57 status_t status = (x); \
58 if ((status) != NO_ERROR) { \
59 return status; \
60 } \
61 }
62
Nick Deakin6bd90432022-11-20 16:26:37 -050063// The current JPEGR version that we encode to
Nick Deakin05ceebf2023-04-19 15:27:13 -040064static const char* const kJpegrVersion = "1.0";
Nick Deakin6bd90432022-11-20 16:26:37 -050065
Nick Deakinf6bca5a2022-11-04 10:43:43 -040066// Map is quarter res / sixteenth size
67static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang53751272023-01-17 19:09:01 -080068// JPEG block size.
Dichen Zhang56a7d592023-04-14 16:57:34 +000069// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma,
70// and 8 x 8 for chroma.
71// Width must be 16 dividable for luma, and 8 dividable for chroma.
72// If this criteria is not ficilitated, we will pad zeros based on the required block size.
73static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
74static const size_t kJpegBlockSquare = kJpegBlock * kJpegBlock;
Dichen Zhang10959a42023-04-10 16:28:16 -070075// JPEG compress quality (0 ~ 100) for gain map
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000076static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040077
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080078#define CONFIG_MULTITHREAD 1
79int GetCPUCoreCount() {
80 int cpuCoreCount = 1;
81#if CONFIG_MULTITHREAD
82#if defined(_SC_NPROCESSORS_ONLN)
83 cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
84#else
85 // _SC_NPROC_ONLN must be defined...
86 cpuCoreCount = sysconf(_SC_NPROC_ONLN);
87#endif
88#endif
89 return cpuCoreCount;
90}
91
Ram Mohanac1cfec2023-05-18 14:41:15 +053092status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
93 jr_uncompressed_ptr uncompressed_yuv_420_image,
94 ultrahdr_transfer_function hdr_tf,
95 jr_compressed_ptr dest) {
96 if (uncompressed_p010_image == nullptr || uncompressed_p010_image->data == nullptr) {
97 ALOGE("received nullptr for uncompressed p010 image");
Dichen Zhang66ca6e32023-04-05 12:22:54 -070098 return ERROR_JPEGR_INVALID_NULL_PTR;
99 }
100
Ram Mohanac1cfec2023-05-18 14:41:15 +0530101 if (uncompressed_p010_image->width % 2 != 0
102 || uncompressed_p010_image->height % 2 != 0) {
103 ALOGE("Image dimensions cannot be odd, image dimensions %dx%d",
104 uncompressed_p010_image->width, uncompressed_p010_image->height);
105 return ERROR_JPEGR_INVALID_INPUT_TYPE;
106 }
107
108 if (uncompressed_p010_image->width == 0
109 || uncompressed_p010_image->height == 0) {
110 ALOGE("Image dimensions cannot be zero, image dimensions %dx%d",
111 uncompressed_p010_image->width, uncompressed_p010_image->height);
112 return ERROR_JPEGR_INVALID_INPUT_TYPE;
113 }
114
115 if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
116 || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
117 ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut);
118 return ERROR_JPEGR_INVALID_INPUT_TYPE;
119 }
120
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700121 if (uncompressed_p010_image->luma_stride != 0
122 && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530123 ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d",
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700124 uncompressed_p010_image->luma_stride, uncompressed_p010_image->width);
125 return ERROR_JPEGR_INVALID_INPUT_TYPE;
126 }
127
Ram Mohanac1cfec2023-05-18 14:41:15 +0530128 if (uncompressed_p010_image->chroma_data != nullptr
129 && uncompressed_p010_image->chroma_stride < uncompressed_p010_image->width) {
130 ALOGE("Chroma stride can not be smaller than width, stride=%d, width=%d",
131 uncompressed_p010_image->chroma_stride,
132 uncompressed_p010_image->width);
133 return ERROR_JPEGR_INVALID_INPUT_TYPE;
134 }
135
136 if (dest == nullptr || dest->data == nullptr) {
137 ALOGE("received nullptr for destination");
138 return ERROR_JPEGR_INVALID_NULL_PTR;
139 }
140
141 if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX) {
142 ALOGE("Invalid hdr transfer function %d", hdr_tf);
143 return ERROR_JPEGR_INVALID_INPUT_TYPE;
144 }
145
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700146 if (uncompressed_yuv_420_image == nullptr) {
147 return NO_ERROR;
148 }
149
Ram Mohanac1cfec2023-05-18 14:41:15 +0530150 if (uncompressed_yuv_420_image->data == nullptr) {
151 ALOGE("received nullptr for uncompressed 420 image");
152 return ERROR_JPEGR_INVALID_NULL_PTR;
153 }
154
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700155 if (uncompressed_yuv_420_image->luma_stride != 0) {
156 ALOGE("Stride is not supported for YUV420 image");
157 return ERROR_JPEGR_UNSUPPORTED_FEATURE;
158 }
159
160 if (uncompressed_yuv_420_image->chroma_data != nullptr) {
161 ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must"
162 "be immediately after the luma data.");
163 return ERROR_JPEGR_UNSUPPORTED_FEATURE;
164 }
165
166 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
167 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
168 ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d",
169 uncompressed_p010_image->width,
170 uncompressed_p010_image->height,
171 uncompressed_yuv_420_image->width,
172 uncompressed_yuv_420_image->height);
173 return ERROR_JPEGR_RESOLUTION_MISMATCH;
174 }
175
Ram Mohanac1cfec2023-05-18 14:41:15 +0530176 if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
177 || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
178 ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut);
179 return ERROR_JPEGR_INVALID_INPUT_TYPE;
180 }
181
182 return NO_ERROR;
183}
184
185status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
186 jr_uncompressed_ptr uncompressed_yuv_420_image,
187 ultrahdr_transfer_function hdr_tf,
188 jr_compressed_ptr dest,
189 int quality) {
190 if (status_t ret = areInputArgumentsValid(
191 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
192 return ret;
193 }
194
195 if (quality < 0 || quality > 100) {
196 ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
197 return ERROR_JPEGR_INVALID_INPUT_TYPE;
198 }
199
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700200 return NO_ERROR;
201}
202
Dichen Zhang636f5242022-12-07 20:25:44 +0000203/* Encode API-0 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000204status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000205 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000206 jr_compressed_ptr dest,
207 int quality,
208 jr_exif_ptr exif) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530209 if (status_t ret = areInputArgumentsValid(
210 uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
211 hdr_tf, dest, quality) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700212 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800213 }
214
Ram Mohanac1cfec2023-05-18 14:41:15 +0530215 if (exif != nullptr && exif->data == nullptr) {
216 ALOGE("received nullptr for exif metadata");
217 return ERROR_JPEGR_INVALID_NULL_PTR;
218 }
219
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000220 ultrahdr_metadata_struct metadata;
Dichen Zhang636f5242022-12-07 20:25:44 +0000221 metadata.version = kJpegrVersion;
Dichen Zhang636f5242022-12-07 20:25:44 +0000222
223 jpegr_uncompressed_struct uncompressed_yuv_420_image;
Dichen Zhang56a7d592023-04-14 16:57:34 +0000224 size_t gain_map_length = uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2;
225 // Pad a pseudo chroma block (kJpegBlock / 2) x (kJpegBlock / 2)
226 // if width is not kJpegBlock aligned.
227 if (uncompressed_p010_image->width % kJpegBlock != 0) {
228 gain_map_length += kJpegBlockSquare / 4;
229 }
230 unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(gain_map_length);
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800231 uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
Dichen Zhang636f5242022-12-07 20:25:44 +0000232 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
233
234 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700235 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500236 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Dichen Zhang636f5242022-12-07 20:25:44 +0000237 std::unique_ptr<uint8_t[]> map_data;
238 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
239
240 jpegr_compressed_struct compressed_map;
241 compressed_map.maxLength = map.width * map.height;
242 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
243 compressed_map.data = compressed_map_data.get();
Dichen Zhang10959a42023-04-10 16:28:16 -0700244 JPEGR_CHECK(compressGainMap(&map, &compressed_map));
Dichen Zhang636f5242022-12-07 20:25:44 +0000245
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000246 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000247 uncompressed_yuv_420_image.colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000248
Dichen Zhang02dd0592023-02-10 20:16:57 +0000249 JpegEncoderHelper jpeg_encoder;
Dichen Zhang636f5242022-12-07 20:25:44 +0000250 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
251 uncompressed_yuv_420_image.width,
Dichen Zhang6438a192023-01-29 07:51:15 +0000252 uncompressed_yuv_420_image.height, quality,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000253 icc->getData(), icc->getLength())) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000254 return ERROR_JPEGR_ENCODE_ERROR;
255 }
256 jpegr_compressed_struct jpeg;
257 jpeg.data = jpeg_encoder.getCompressedImagePtr();
258 jpeg.length = jpeg_encoder.getCompressedImageSize();
259
Dichen Zhang10959a42023-04-10 16:28:16 -0700260 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000261
262 return NO_ERROR;
263}
264
265/* Encode API-1 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000266status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000267 jr_uncompressed_ptr uncompressed_yuv_420_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000268 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000269 jr_compressed_ptr dest,
270 int quality,
271 jr_exif_ptr exif) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530272 if (uncompressed_yuv_420_image == nullptr) {
273 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000274 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000275 }
276
Ram Mohanac1cfec2023-05-18 14:41:15 +0530277 if (exif != nullptr && exif->data == nullptr) {
278 ALOGE("received nullptr for exif metadata");
279 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhangffa34012022-11-03 23:21:13 +0000280 }
281
Ram Mohanac1cfec2023-05-18 14:41:15 +0530282 if (status_t ret = areInputArgumentsValid(
283 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf,
284 dest, quality) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700285 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800286 }
287
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000288 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500289 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500290
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400291 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700292 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500293 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400294 std::unique_ptr<uint8_t[]> map_data;
295 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
296
297 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000298 compressed_map.maxLength = map.width * map.height;
299 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400300 compressed_map.data = compressed_map_data.get();
Dichen Zhang10959a42023-04-10 16:28:16 -0700301 JPEGR_CHECK(compressGainMap(&map, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400302
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000303 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000304 uncompressed_yuv_420_image->colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000305
Dichen Zhang02dd0592023-02-10 20:16:57 +0000306 JpegEncoderHelper jpeg_encoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400307 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
308 uncompressed_yuv_420_image->width,
Dichen Zhang6438a192023-01-29 07:51:15 +0000309 uncompressed_yuv_420_image->height, quality,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000310 icc->getData(), icc->getLength())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400311 return ERROR_JPEGR_ENCODE_ERROR;
312 }
313 jpegr_compressed_struct jpeg;
314 jpeg.data = jpeg_encoder.getCompressedImagePtr();
315 jpeg.length = jpeg_encoder.getCompressedImageSize();
316
Dichen Zhang10959a42023-04-10 16:28:16 -0700317 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400318
Dichen Zhang6947d532022-10-22 02:16:21 +0000319 return NO_ERROR;
320}
321
Dichen Zhang636f5242022-12-07 20:25:44 +0000322/* Encode API-2 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000323status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000324 jr_uncompressed_ptr uncompressed_yuv_420_image,
325 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000326 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000327 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530328 if (uncompressed_yuv_420_image == nullptr) {
329 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000330 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000331 }
332
Ram Mohanac1cfec2023-05-18 14:41:15 +0530333 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
334 ALOGE("received nullptr for compressed jpeg image");
335 return ERROR_JPEGR_INVALID_NULL_PTR;
336 }
337
338 if (status_t ret = areInputArgumentsValid(
339 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700340 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800341 }
342
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000343 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500344 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500345
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400346 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700347 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500348 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400349 std::unique_ptr<uint8_t[]> map_data;
350 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
351
352 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000353 compressed_map.maxLength = map.width * map.height;
354 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400355 compressed_map.data = compressed_map_data.get();
Dichen Zhang10959a42023-04-10 16:28:16 -0700356 JPEGR_CHECK(compressGainMap(&map, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400357
Dichen Zhang10959a42023-04-10 16:28:16 -0700358 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400359
Dichen Zhang6947d532022-10-22 02:16:21 +0000360 return NO_ERROR;
361}
362
Dichen Zhang636f5242022-12-07 20:25:44 +0000363/* Encode API-3 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000364status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000365 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000366 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000367 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530368 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
369 ALOGE("received nullptr for compressed jpeg image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000370 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000371 }
372
Ram Mohanac1cfec2023-05-18 14:41:15 +0530373 if (status_t ret = areInputArgumentsValid(
374 uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
375 hdr_tf, dest) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700376 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800377 }
378
Dichen Zhang02dd0592023-02-10 20:16:57 +0000379 JpegDecoderHelper jpeg_decoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400380 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
381 return ERROR_JPEGR_DECODE_ERROR;
382 }
383 jpegr_uncompressed_struct uncompressed_yuv_420_image;
384 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
385 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
386 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500387 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400388
389 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
390 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
391 return ERROR_JPEGR_RESOLUTION_MISMATCH;
392 }
393
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000394 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500395 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500396
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400397 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700398 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500399 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400400 std::unique_ptr<uint8_t[]> map_data;
401 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
402
403 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000404 compressed_map.maxLength = map.width * map.height;
405 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400406 compressed_map.data = compressed_map_data.get();
Dichen Zhang10959a42023-04-10 16:28:16 -0700407 JPEGR_CHECK(compressGainMap(&map, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400408
Dichen Zhang10959a42023-04-10 16:28:16 -0700409 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400410
Dichen Zhang6947d532022-10-22 02:16:21 +0000411 return NO_ERROR;
412}
413
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000414/* Encode API-4 */
415status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
416 jr_compressed_ptr compressed_gainmap,
417 ultrahdr_metadata_ptr metadata,
418 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530419 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
420 ALOGE("received nullptr for compressed jpeg image");
421 return ERROR_JPEGR_INVALID_NULL_PTR;
422 }
423
424 if (compressed_gainmap == nullptr || compressed_gainmap->data == nullptr) {
425 ALOGE("received nullptr for compressed gain map");
426 return ERROR_JPEGR_INVALID_NULL_PTR;
427 }
428
429 if (dest == nullptr || dest->data == nullptr) {
430 ALOGE("received nullptr for destination");
431 return ERROR_JPEGR_INVALID_NULL_PTR;
432 }
433
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000434 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
435 metadata, dest));
436 return NO_ERROR;
437}
438
Dichen Zhang61ede362023-02-22 18:50:13 +0000439status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530440 if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
441 ALOGE("received nullptr for compressed jpegr image");
442 return ERROR_JPEGR_INVALID_NULL_PTR;
443 }
444
445 if (jpegr_info == nullptr) {
446 ALOGE("received nullptr for compressed jpegr info struct");
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000447 return ERROR_JPEGR_INVALID_NULL_PTR;
448 }
449
Dichen Zhang10959a42023-04-10 16:28:16 -0700450 jpegr_compressed_struct primary_image, gain_map;
451 JPEGR_CHECK(extractPrimaryImageAndGainMap(compressed_jpegr_image,
452 &primary_image, &gain_map));
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000453
Dichen Zhang02dd0592023-02-10 20:16:57 +0000454 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000455 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
456 &jpegr_info->width, &jpegr_info->height,
457 jpegr_info->iccData, jpegr_info->exifData)) {
458 return ERROR_JPEGR_DECODE_ERROR;
459 }
460
461 return NO_ERROR;
462}
463
Dichen Zhang636f5242022-12-07 20:25:44 +0000464/* Decode API */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000465status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000466 jr_uncompressed_ptr dest,
Dichen Zhangc6605702023-03-15 18:40:55 -0700467 float max_display_boost,
Dichen Zhang61ede362023-02-22 18:50:13 +0000468 jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000469 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700470 jr_uncompressed_ptr gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000471 ultrahdr_metadata_ptr metadata) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000472 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000473 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000474 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000475
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000476 if (max_display_boost < 1.0f) {
477 return ERROR_JPEGR_INVALID_INPUT_TYPE;
478 }
479
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000480 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Dichen Zhang02dd0592023-02-10 20:16:57 +0000481 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000482 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
483 true)) {
484 return ERROR_JPEGR_DECODE_ERROR;
485 }
486 jpegr_uncompressed_struct uncompressed_rgba_image;
487 uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
488 uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
489 uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
490 memcpy(dest->data, uncompressed_rgba_image.data,
491 uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
492 dest->width = uncompressed_rgba_image.width;
493 dest->height = uncompressed_rgba_image.height;
Dichen Zhang14f3c472023-03-08 07:24:48 +0000494
Dichen Zhang10959a42023-04-10 16:28:16 -0700495 if (gain_map == nullptr && exif == nullptr) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000496 return NO_ERROR;
497 }
498
499 if (exif != nullptr) {
500 if (exif->data == nullptr) {
501 return ERROR_JPEGR_INVALID_NULL_PTR;
502 }
503 if (exif->length < jpeg_decoder.getEXIFSize()) {
504 return ERROR_JPEGR_BUFFER_TOO_SMALL;
505 }
506 memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
507 exif->length = jpeg_decoder.getEXIFSize();
508 }
Dichen Zhang10959a42023-04-10 16:28:16 -0700509 if (gain_map == nullptr) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000510 return NO_ERROR;
511 }
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000512 }
513
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400514 jpegr_compressed_struct compressed_map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700515 JPEGR_CHECK(extractGainMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400516
Dichen Zhang10959a42023-04-10 16:28:16 -0700517 JpegDecoderHelper gain_map_decoder;
518 if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000519 return ERROR_JPEGR_DECODE_ERROR;
520 }
521
Dichen Zhang10959a42023-04-10 16:28:16 -0700522 if (gain_map != nullptr) {
523 gain_map->width = gain_map_decoder.getDecompressedImageWidth();
524 gain_map->height = gain_map_decoder.getDecompressedImageHeight();
525 int size = gain_map->width * gain_map->height;
526 gain_map->data = malloc(size);
527 memcpy(gain_map->data, gain_map_decoder.getDecompressedImagePtr(), size);
Dichen Zhang14f3c472023-03-08 07:24:48 +0000528 }
529
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000530 ultrahdr_metadata_struct uhdr_metadata;
Dichen Zhang10959a42023-04-10 16:28:16 -0700531 if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000532 gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
Dichen Zhange286f1c2023-03-14 00:22:00 +0000533 return ERROR_JPEGR_DECODE_ERROR;
534 }
535
536 if (metadata != nullptr) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000537 metadata->version = uhdr_metadata.version;
538 metadata->minContentBoost = uhdr_metadata.minContentBoost;
539 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
Dichen Zhange286f1c2023-03-14 00:22:00 +0000540 }
541
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000542 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000543 return NO_ERROR;
544 }
545
Dichen Zhang02dd0592023-02-10 20:16:57 +0000546 JpegDecoderHelper jpeg_decoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400547 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
548 return ERROR_JPEGR_DECODE_ERROR;
549 }
550
Dichen Zhangb80e2262023-03-08 06:59:51 +0000551 if (exif != nullptr) {
552 if (exif->data == nullptr) {
553 return ERROR_JPEGR_INVALID_NULL_PTR;
554 }
555 if (exif->length < jpeg_decoder.getEXIFSize()) {
556 return ERROR_JPEGR_BUFFER_TOO_SMALL;
557 }
558 memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
559 exif->length = jpeg_decoder.getEXIFSize();
560 }
561
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000562 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700563 map.data = gain_map_decoder.getDecompressedImagePtr();
564 map.width = gain_map_decoder.getDecompressedImageWidth();
565 map.height = gain_map_decoder.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000566
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400567 jpegr_uncompressed_struct uncompressed_yuv_420_image;
568 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
569 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
570 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
571
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000572 JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700573 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000574 return NO_ERROR;
575}
576
Dichen Zhang10959a42023-04-10 16:28:16 -0700577status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
578 jr_compressed_ptr dest) {
579 if (uncompressed_gain_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000580 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700581 }
582
Dichen Zhang02dd0592023-02-10 20:16:57 +0000583 JpegEncoderHelper jpeg_encoder;
Dichen Zhang10959a42023-04-10 16:28:16 -0700584 if (!jpeg_encoder.compressImage(uncompressed_gain_map->data,
585 uncompressed_gain_map->width,
586 uncompressed_gain_map->height,
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000587 kMapCompressQuality,
588 nullptr,
589 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400590 true /* isSingleChannel */)) {
591 return ERROR_JPEGR_ENCODE_ERROR;
592 }
593
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000594 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400595 return ERROR_JPEGR_BUFFER_TOO_SMALL;
596 }
597
598 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
599 dest->length = jpeg_encoder.getCompressedImageSize();
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000600 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400601
Dichen Zhang6947d532022-10-22 02:16:21 +0000602 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700603}
604
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800605const int kJobSzInRows = 16;
606static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
607 "align job size to kMapDimensionScaleFactor");
608
609class JobQueue {
610 public:
611 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
612 void enqueueJob(size_t rowStart, size_t rowEnd);
613 void markQueueForEnd();
614 void reset();
615
616 private:
617 bool mQueuedAllJobs = false;
618 std::deque<std::tuple<size_t, size_t>> mJobs;
619 std::mutex mMutex;
620 std::condition_variable mCv;
621};
622
623bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
624 std::unique_lock<std::mutex> lock{mMutex};
625 while (true) {
626 if (mJobs.empty()) {
627 if (mQueuedAllJobs) {
628 return false;
629 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000630 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800631 }
632 } else {
633 auto it = mJobs.begin();
634 rowStart = std::get<0>(*it);
635 rowEnd = std::get<1>(*it);
636 mJobs.erase(it);
637 return true;
638 }
639 }
640 return false;
641}
642
643void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
644 std::unique_lock<std::mutex> lock{mMutex};
645 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
646 lock.unlock();
647 mCv.notify_one();
648}
649
650void JobQueue::markQueueForEnd() {
651 std::unique_lock<std::mutex> lock{mMutex};
652 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000653 lock.unlock();
654 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800655}
656
657void JobQueue::reset() {
658 std::unique_lock<std::mutex> lock{mMutex};
659 mJobs.clear();
660 mQueuedAllJobs = false;
661}
662
Dichen Zhang10959a42023-04-10 16:28:16 -0700663status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
664 jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000665 ultrahdr_transfer_function hdr_tf,
666 ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -0700667 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700668 if (uncompressed_yuv_420_image == nullptr
669 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500670 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700671 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000672 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700673 }
674
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400675 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
676 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
677 return ERROR_JPEGR_RESOLUTION_MISMATCH;
678 }
679
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000680 if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED
681 || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500682 return ERROR_JPEGR_INVALID_COLORGAMUT;
683 }
684
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400685 size_t image_width = uncompressed_yuv_420_image->width;
686 size_t image_height = uncompressed_yuv_420_image->height;
687 size_t map_width = image_width / kMapDimensionScaleFactor;
688 size_t map_height = image_height / kMapDimensionScaleFactor;
Dichen Zhang53751272023-01-17 19:09:01 -0800689 size_t map_stride = static_cast<size_t>(
690 floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
691 size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400692
Dichen Zhang53751272023-01-17 19:09:01 -0800693 dest->width = map_stride;
694 dest->height = map_height_aligned;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000695 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Dichen Zhang53751272023-01-17 19:09:01 -0800696 dest->data = new uint8_t[map_stride * map_height_aligned];
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400697 std::unique_ptr<uint8_t[]> map_data;
698 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
699
Nick Deakin6bd90432022-11-20 16:26:37 -0500700 ColorTransformFn hdrInvOetf = nullptr;
Nick Deakin65f492a2022-11-29 22:47:40 -0500701 float hdr_white_nits = 0.0f;
Nick Deakin01759062023-02-02 18:21:43 -0500702 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000703 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000704 hdrInvOetf = identityConversion;
705 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000706 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800707#if USE_HLG_INVOETF_LUT
708 hdrInvOetf = hlgInvOetfLUT;
709#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500710 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800711#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500712 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500713 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000714 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800715#if USE_PQ_INVOETF_LUT
716 hdrInvOetf = pqInvOetfLUT;
717#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500718 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800719#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500720 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500721 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000722 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000723 // Should be impossible to hit after input validation.
724 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500725 }
726
Nick Deakina2215292023-02-14 21:40:06 -0500727 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
728 metadata->minContentBoost = 1.0f;
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700729 float log2MinBoost = log2(metadata->minContentBoost);
730 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500731
Nick Deakin6bd90432022-11-20 16:26:37 -0500732 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
733 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
734
735 ColorCalculationFn luminanceFn = nullptr;
736 switch (uncompressed_yuv_420_image->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000737 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500738 luminanceFn = srgbLuminance;
739 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000740 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500741 luminanceFn = p3Luminance;
742 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000743 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500744 luminanceFn = bt2100Luminance;
745 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000746 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500747 // Should be impossible to hit after input validation.
748 return ERROR_JPEGR_INVALID_COLORGAMUT;
749 }
750
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800751 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800752 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
753 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
754 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500755
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800756 std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
757 metadata, dest, hdrInvOetf, hdrGamutConversionFn,
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700758 luminanceFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
759 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800760 size_t rowStart, rowEnd;
Dichen Zhang24b4a392023-02-02 22:54:01 +0000761 size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
762 size_t dest_map_stride = dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800763 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
764 for (size_t y = rowStart; y < rowEnd; ++y) {
Dichen Zhang24b4a392023-02-02 22:54:01 +0000765 for (size_t x = 0; x < dest_map_width; ++x) {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800766 Color sdr_yuv_gamma =
767 sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
768 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800769#if USE_SRGB_INVOETF_LUT
770 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
771#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800772 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800773#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800774 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
775
776 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
777 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
778 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
779 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
780 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
781
Dichen Zhang24b4a392023-02-02 22:54:01 +0000782 size_t pixel_idx = x + y * dest_map_stride;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800783 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Dichen Zhang10959a42023-04-10 16:28:16 -0700784 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800785 }
786 }
787 }
788 };
789
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800790 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500791 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800792 for (int th = 0; th < threads - 1; th++) {
793 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400794 }
795
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800796 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
797 for (size_t rowStart = 0; rowStart < map_height;) {
798 size_t rowEnd = std::min(rowStart + rowStep, map_height);
799 jobQueue.enqueueJob(rowStart, rowEnd);
800 rowStart = rowEnd;
801 }
802 jobQueue.markQueueForEnd();
803 generateMap();
804 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
805
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400806 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000807 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700808}
809
Dichen Zhang10959a42023-04-10 16:28:16 -0700810status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
811 jr_uncompressed_ptr uncompressed_gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000812 ultrahdr_metadata_ptr metadata,
813 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700814 float max_display_boost,
815 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700816 if (uncompressed_yuv_420_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -0700817 || uncompressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500818 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700819 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000820 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700821 }
822
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800823 dest->width = uncompressed_yuv_420_image->width;
824 dest->height = uncompressed_yuv_420_image->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530825 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000826 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -0700827 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +0530828
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800829 JobQueue jobQueue;
Dichen Zhang10959a42023-04-10 16:28:16 -0700830 std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
Dichen Zhang3e5798c2023-03-01 22:30:43 +0000831 metadata, dest, &jobQueue, &idwTable, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700832 &gainLUT, display_boost]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800833 size_t width = uncompressed_yuv_420_image->width;
834 size_t height = uncompressed_yuv_420_image->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400835
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800836 size_t rowStart, rowEnd;
837 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
838 for (size_t y = rowStart; y < rowEnd; ++y) {
839 for (size_t x = 0; x < width; ++x) {
840 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
841 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800842#if USE_SRGB_INVOETF_LUT
843 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
844#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800845 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800846#endif
Dichen Zhang10959a42023-04-10 16:28:16 -0700847 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800848 // TODO: determine map scaling factor based on actual map dims
849 size_t map_scale_factor = kMapDimensionScaleFactor;
850 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
851 // Currently map_scale_factor is of type size_t, but it could be changed to a float
852 // later.
853 if (map_scale_factor != floorf(map_scale_factor)) {
Dichen Zhang10959a42023-04-10 16:28:16 -0700854 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800855 } else {
Dichen Zhang10959a42023-04-10 16:28:16 -0700856 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800857 }
Dichen Zhangc6605702023-03-15 18:40:55 -0700858
Dichen Zhang10959a42023-04-10 16:28:16 -0700859#if USE_APPLY_GAIN_LUT
860 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800861#else
Dichen Zhang10959a42023-04-10 16:28:16 -0700862 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800863#endif
Dichen Zhangc6605702023-03-15 18:40:55 -0700864 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800865 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +0000866
867 switch (output_format) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000868 case ULTRAHDR_OUTPUT_HDR_LINEAR:
Dichen Zhang3e5798c2023-03-01 22:30:43 +0000869 {
870 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
871 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
872 break;
873 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000874 case ULTRAHDR_OUTPUT_HDR_HLG:
Dichen Zhang3e5798c2023-03-01 22:30:43 +0000875 {
876#if USE_HLG_OETF_LUT
877 ColorTransformFn hdrOetf = hlgOetfLUT;
878#else
879 ColorTransformFn hdrOetf = hlgOetf;
880#endif
881 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
882 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
883 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
884 break;
885 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000886 case ULTRAHDR_OUTPUT_HDR_PQ:
Dichen Zhang3e5798c2023-03-01 22:30:43 +0000887 {
888#if USE_HLG_OETF_LUT
889 ColorTransformFn hdrOetf = pqOetfLUT;
890#else
891 ColorTransformFn hdrOetf = pqOetf;
892#endif
893 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
894 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
895 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
896 break;
897 }
898 default:
899 {}
900 // Should be impossible to hit after input validation.
901 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800902 }
903 }
904 }
905 };
906
907 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
908 std::vector<std::thread> workers;
909 for (int th = 0; th < threads - 1; th++) {
910 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400911 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800912 const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
913 for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
914 int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
915 jobQueue.enqueueJob(rowStart, rowEnd);
916 rowStart = rowEnd;
917 }
918 jobQueue.markQueueForEnd();
919 applyRecMap();
920 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000921 return NO_ERROR;
922}
923
Dichen Zhang10959a42023-04-10 16:28:16 -0700924status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
925 jr_compressed_ptr primary_image,
926 jr_compressed_ptr gain_map) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000927 if (compressed_jpegr_image == nullptr) {
928 return ERROR_JPEGR_INVALID_NULL_PTR;
929 }
930
931 MessageHandler msg_handler;
932 std::shared_ptr<DataSegment> seg =
933 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
934 static_cast<const uint8_t*>(compressed_jpegr_image->data),
935 DataSegment::BufferDispositionPolicy::kDontDelete);
936 DataSegmentDataSource data_source(seg);
937 JpegInfoBuilder jpeg_info_builder;
938 jpeg_info_builder.SetImageLimit(2);
939 JpegScanner jpeg_scanner(&msg_handler);
940 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
941 data_source.Reset();
942
943 if (jpeg_scanner.HasError()) {
944 return ERROR_JPEGR_INVALID_INPUT_TYPE;
945 }
946
947 const auto& jpeg_info = jpeg_info_builder.GetInfo();
948 const auto& image_ranges = jpeg_info.GetImageRanges();
949 if (image_ranges.empty()) {
950 return ERROR_JPEGR_INVALID_INPUT_TYPE;
951 }
952
953 if (image_ranges.size() != 2) {
954 // Must be 2 JPEG Images
955 return ERROR_JPEGR_INVALID_INPUT_TYPE;
956 }
957
958 if (primary_image != nullptr) {
959 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
960 image_ranges[0].GetBegin();
961 primary_image->length = image_ranges[0].GetLength();
962 }
963
Dichen Zhang10959a42023-04-10 16:28:16 -0700964 if (gain_map != nullptr) {
965 gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000966 image_ranges[1].GetBegin();
Dichen Zhang10959a42023-04-10 16:28:16 -0700967 gain_map->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000968 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400969
Dichen Zhang6947d532022-10-22 02:16:21 +0000970 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700971}
972
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000973
Dichen Zhang10959a42023-04-10 16:28:16 -0700974status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image,
975 jr_compressed_ptr dest) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000976 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000977 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700978 }
979
Dichen Zhang10959a42023-04-10 16:28:16 -0700980 return extractPrimaryImageAndGainMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700981}
982
Dichen Zhangd18bc302022-12-16 20:55:24 +0000983// JPEG/R structure:
984// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +0000985//
986// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000987// APP1 (ff e1)
988// 2 bytes of length (2 + length of exif package)
989// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +0000990//
991// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000992// 2 bytes of length (2 + 29 + length of xmp package)
993// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +0000994// XMP
995//
996// (Required, MPF package) APP2 (ff e2)
997// 2 bytes of length
998// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +0000999//
1000// (Required) primary image (without the first two bytes (SOI), may have other packages)
1001//
Dichen Zhang61ede362023-02-22 18:50:13 +00001002// SOI (ff d8)
1003//
1004// (Required, XMP package) APP1 (ff e1)
1005// 2 bytes of length (2 + 29 + length of xmp package)
1006// name space ("http://ns.adobe.com/xap/1.0/\0")
1007// XMP
1008//
Dichen Zhang10959a42023-04-10 16:28:16 -07001009// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001010//
1011// Metadata versions we are using:
1012// ECMA TR-98 for JFIF marker
1013// Exif 2.2 spec for EXIF marker
1014// Adobe XMP spec part 3 for XMP marker
1015// ICC v4.3 spec for ICC
Dichen Zhang10959a42023-04-10 16:28:16 -07001016status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
1017 jr_compressed_ptr compressed_gain_map,
1018 jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001019 ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001020 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +00001021 if (compressed_jpeg_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -07001022 || compressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -05001023 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +00001024 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001025 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001026 }
1027
Dichen Zhang15345ea2023-02-23 23:54:32 +00001028 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
1029 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001030
Dichen Zhang15345ea2023-02-23 23:54:32 +00001031 // calculate secondary image length first, because the length will be written into the primary
1032 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001033 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001034 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
1035 + nameSpaceLength /* 29 bytes length of name space including \0 */
1036 + xmp_secondary.size(); /* length of xmp packet */
1037 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
1038 + xmp_secondary_length
Dichen Zhang10959a42023-04-10 16:28:16 -07001039 + compressed_gain_map->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001040 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001041 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001042 // same as primary
1043 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001044
Dichen Zhang15345ea2023-02-23 23:54:32 +00001045 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001046 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001047 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001048 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1049 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001050
1051 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001052 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001053 const int length = 2 + exif->length;
1054 const uint8_t lengthH = ((length >> 8) & 0xff);
1055 const uint8_t lengthL = (length & 0xff);
1056 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1057 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1058 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1059 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1060 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1061 }
1062
1063 // Prepare and write XMP
1064 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001065 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001066 const uint8_t lengthH = ((length >> 8) & 0xff);
1067 const uint8_t lengthL = (length & 0xff);
1068 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1069 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1070 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1071 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1072 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001073 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1074 }
1075
1076 // Prepare and write MPF
1077 {
1078 const int length = 2 + calculateMpfSize();
1079 const uint8_t lengthH = ((length >> 8) & 0xff);
1080 const uint8_t lengthL = (length & 0xff);
1081 int primary_image_size = pos + length + compressed_jpeg_image->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001082 // between APP2 + package size + signature
1083 // ff e2 00 58 4d 50 46 00
1084 // 2 + 2 + 4 = 8 (bytes)
1085 // and ff d8 sign of the secondary image
1086 int secondary_image_offset = primary_image_size - pos - 8;
1087 sp<DataStruct> mpf = generateMpf(primary_image_size,
1088 0, /* primary_image_offset */
1089 secondary_image_size,
1090 secondary_image_offset);
Dichen Zhang61ede362023-02-22 18:50:13 +00001091 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1092 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1093 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1094 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1095 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001096 }
1097
1098 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +00001099 JPEGR_CHECK(Write(dest,
1100 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001101 // Finish primary image
1102
Dichen Zhang10959a42023-04-10 16:28:16 -07001103 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001104 // Write SOI
1105 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1106 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1107
1108 // Prepare and write XMP
1109 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001110 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001111 const uint8_t lengthH = ((length >> 8) & 0xff);
1112 const uint8_t lengthL = (length & 0xff);
1113 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1114 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1115 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1116 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1117 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1118 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1119 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001120
1121 // Write secondary image
Dichen Zhang61ede362023-02-22 18:50:13 +00001122 JPEGR_CHECK(Write(dest,
Dichen Zhang10959a42023-04-10 16:28:16 -07001123 (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001124
1125 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001126 dest->length = pos;
1127
Dichen Zhangd18bc302022-12-16 20:55:24 +00001128 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001129 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001130}
1131
Dichen Zhang61ede362023-02-22 18:50:13 +00001132status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001133 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001134 return ERROR_JPEGR_INVALID_NULL_PTR;
1135 }
1136
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001137 uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
Dichen Zhang98b06852023-05-17 16:56:51 +00001138 size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001139
Dichen Zhang98b06852023-05-17 16:56:51 +00001140 uint16_t* src_chroma_data;
1141 size_t src_chroma_stride;
1142 if (src->chroma_data == nullptr) {
1143 src_chroma_stride = src_luma_stride;
1144 src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
1145 } else {
1146 src_chroma_stride = src->chroma_stride;
1147 src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001148 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001149 dest->width = src->width;
1150 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +00001151
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001152 size_t dest_luma_pixel_count = dest->width * dest->height;
1153
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001154 for (size_t y = 0; y < src->height; ++y) {
1155 for (size_t x = 0; x < src->width; ++x) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001156 size_t src_y_idx = y * src_luma_stride + x;
1157 size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
1158 size_t src_v_idx = src_u_idx + 1;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001159
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001160 uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
1161 uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
1162 uint16_t v_uint = src_chroma_data[src_v_idx] >> 6;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001163
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001164 size_t dest_y_idx = x + y * dest->width;
1165 size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
1166
1167 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
1168 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
1169 uint8_t* v = &reinterpret_cast<uint8_t*>(
1170 dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001171
1172 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1173 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1174 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1175 }
1176 }
1177
1178 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001179
1180 return NO_ERROR;
1181}
1182
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001183} // namespace android::ultrahdr