1 /*
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
17 #ifdef _WIN32
18 #include <windows.h>
19 #include <sysinfoapi.h>
20 #else
21 #include <unistd.h>
22 #endif
23
24 #include <condition_variable>
25 #include <deque>
26 #include <functional>
27 #include <mutex>
28 #include <thread>
29
30 #include "ultrahdr/gainmapmetadata.h"
31 #include "ultrahdr/ultrahdrcommon.h"
32 #include "ultrahdr/jpegr.h"
33 #include "ultrahdr/icc.h"
34 #include "ultrahdr/multipictureformat.h"
35
36 #include "image_io/base/data_segment_data_source.h"
37 #include "image_io/jpeg/jpeg_info.h"
38 #include "image_io/jpeg/jpeg_info_builder.h"
39 #include "image_io/jpeg/jpeg_marker.h"
40 #include "image_io/jpeg/jpeg_scanner.h"
41
42 using namespace std;
43 using namespace photos_editing_formats::image_io;
44
45 namespace ultrahdr {
46
47 #define USE_SRGB_INVOETF_LUT 1
48 #define USE_HLG_OETF_LUT 1
49 #define USE_PQ_OETF_LUT 1
50 #define USE_HLG_INVOETF_LUT 1
51 #define USE_PQ_INVOETF_LUT 1
52 #define USE_APPLY_GAIN_LUT 1
53
54 // JPEG compress quality (0 ~ 100) for gain map
55 static const int kMapCompressQuality = 85;
56
57 // Gain map metadata
58 static const bool kWriteXmpMetadata = true;
59 static const bool kWriteIso21496_1Metadata = false;
60
61 // Gain map calculation
62 static const bool kUseMultiChannelGainMap = false;
63
GetCPUCoreCount()64 int GetCPUCoreCount() {
65 int cpuCoreCount = 1;
66
67 #if defined(_WIN32)
68 SYSTEM_INFO system_info;
69 ZeroMemory(&system_info, sizeof(system_info));
70 GetSystemInfo(&system_info);
71 cpuCoreCount = (size_t)system_info.dwNumberOfProcessors;
72 #elif defined(_SC_NPROCESSORS_ONLN)
73 cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
74 #elif defined(_SC_NPROCESSORS_CONF)
75 cpuCoreCount = sysconf(_SC_NPROCESSORS_CONF);
76 #else
77 #error platform-specific implementation for GetCPUCoreCount() missing.
78 #endif
79 if (cpuCoreCount <= 0) cpuCoreCount = 1;
80 return cpuCoreCount;
81 }
82
83 /*
84 * MessageWriter implementation for ALOG functions.
85 */
86 class AlogMessageWriter : public MessageWriter {
87 public:
WriteMessage(const Message & message)88 void WriteMessage(const Message& message) override {
89 std::string log = GetFormattedMessage(message);
90 ALOGD("%s", log.c_str());
91 }
92 };
93
94 const string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
95 const string kIsoNameSpace = "urn:iso:std:iso:ts:21496:-1";
96
97 /*
98 * Helper function copies the JPEG image from without EXIF.
99 *
100 * @param pDest destination of the data to be written.
101 * @param pSource source of data being written.
102 * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
103 * (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
104 * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
105 */
copyJpegWithoutExif(jr_compressed_ptr pDest,jr_compressed_ptr pSource,size_t exif_pos,size_t exif_size)106 static void copyJpegWithoutExif(jr_compressed_ptr pDest, jr_compressed_ptr pSource, size_t exif_pos,
107 size_t exif_size) {
108 const size_t exif_offset = 4; // exif_pos has 4 bytes offset to the FF sign
109 pDest->length = pSource->length - exif_size - exif_offset;
110 pDest->data = new uint8_t[pDest->length];
111 pDest->maxLength = pDest->length;
112 pDest->colorGamut = pSource->colorGamut;
113 memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
114 memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
115 (uint8_t*)pSource->data + exif_pos + exif_size, pSource->length - exif_pos - exif_size);
116 }
117
areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,jr_uncompressed_ptr yuv420_image_ptr,ultrahdr_transfer_function hdr_tf,jr_compressed_ptr dest_ptr)118 status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
119 jr_uncompressed_ptr yuv420_image_ptr,
120 ultrahdr_transfer_function hdr_tf,
121 jr_compressed_ptr dest_ptr) {
122 if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
123 ALOGE("Received nullptr for input p010 image");
124 return ERROR_JPEGR_BAD_PTR;
125 }
126 if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
127 ALOGE("Image dimensions cannot be odd, image dimensions %zux%zu", p010_image_ptr->width,
128 p010_image_ptr->height);
129 return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
130 }
131 if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
132 ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %zux%zu", kMinWidth,
133 kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
134 return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
135 }
136 if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
137 ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %zux%zu", kMaxWidth,
138 kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
139 return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
140 }
141 if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
142 p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
143 ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
144 return ERROR_JPEGR_INVALID_COLORGAMUT;
145 }
146 if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
147 ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu",
148 p010_image_ptr->luma_stride, p010_image_ptr->width);
149 return ERROR_JPEGR_INVALID_STRIDE;
150 }
151 if (p010_image_ptr->chroma_data != nullptr &&
152 p010_image_ptr->chroma_stride < p010_image_ptr->width) {
153 ALOGE("Chroma stride must not be smaller than width, stride=%zu, width=%zu",
154 p010_image_ptr->chroma_stride, p010_image_ptr->width);
155 return ERROR_JPEGR_INVALID_STRIDE;
156 }
157 if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
158 ALOGE("Received nullptr for destination");
159 return ERROR_JPEGR_BAD_PTR;
160 }
161 if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
162 ALOGE("Invalid hdr transfer function %d", hdr_tf);
163 return ERROR_JPEGR_INVALID_TRANS_FUNC;
164 }
165 if (yuv420_image_ptr == nullptr) {
166 return JPEGR_NO_ERROR;
167 }
168 if (yuv420_image_ptr->data == nullptr) {
169 ALOGE("Received nullptr for uncompressed 420 image");
170 return ERROR_JPEGR_BAD_PTR;
171 }
172 if (yuv420_image_ptr->luma_stride != 0 &&
173 yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
174 ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu",
175 yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
176 return ERROR_JPEGR_INVALID_STRIDE;
177 }
178 if (yuv420_image_ptr->chroma_data != nullptr &&
179 yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
180 ALOGE("Chroma stride must not be smaller than (width / 2), stride=%zu, width=%zu",
181 yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
182 return ERROR_JPEGR_INVALID_STRIDE;
183 }
184 if (p010_image_ptr->width != yuv420_image_ptr->width ||
185 p010_image_ptr->height != yuv420_image_ptr->height) {
186 ALOGE("Image resolutions mismatch: P010: %zux%zu, YUV420: %zux%zu", p010_image_ptr->width,
187 p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
188 return ERROR_JPEGR_RESOLUTION_MISMATCH;
189 }
190 if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
191 yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
192 ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
193 return ERROR_JPEGR_INVALID_COLORGAMUT;
194 }
195 return JPEGR_NO_ERROR;
196 }
197
areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,jr_uncompressed_ptr yuv420_image_ptr,ultrahdr_transfer_function hdr_tf,jr_compressed_ptr dest_ptr,int quality)198 status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
199 jr_uncompressed_ptr yuv420_image_ptr,
200 ultrahdr_transfer_function hdr_tf,
201 jr_compressed_ptr dest_ptr, int quality) {
202 if (quality < 0 || quality > 100) {
203 ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
204 return ERROR_JPEGR_INVALID_QUALITY_FACTOR;
205 }
206 return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
207 }
208
209 /* Encode API-0 */
encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,ultrahdr_transfer_function hdr_tf,jr_compressed_ptr dest,int quality,jr_exif_ptr exif)210 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
211 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
212 // validate input arguments
213 JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality));
214 if (exif != nullptr && exif->data == nullptr) {
215 ALOGE("received nullptr for exif metadata");
216 return ERROR_JPEGR_BAD_PTR;
217 }
218
219 // clean up input structure for later usage
220 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
221 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
222 if (!p010_image.chroma_data) {
223 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
224 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
225 p010_image.chroma_stride = p010_image.luma_stride;
226 }
227
228 const size_t yu420_luma_stride = ALIGNM(p010_image.width, 16);
229 unique_ptr<uint8_t[]> yuv420_image_data =
230 make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
231 jpegr_uncompressed_struct yuv420_image;
232 yuv420_image.data = yuv420_image_data.get();
233 yuv420_image.width = p010_image.width;
234 yuv420_image.height = p010_image.height;
235 yuv420_image.colorGamut = p010_image.colorGamut;
236 yuv420_image.chroma_data = nullptr;
237 yuv420_image.luma_stride = yu420_luma_stride;
238 yuv420_image.chroma_stride = yu420_luma_stride >> 1;
239 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
240 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
241
242 // tone map
243 JPEGR_CHECK(toneMap(&p010_image, &yuv420_image, hdr_tf));
244
245 // gain map
246 ultrahdr_metadata_struct metadata;
247 metadata.version = kJpegrVersion;
248 jpegr_uncompressed_struct gainmap_image;
249 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
250 std::unique_ptr<uint8_t[]> map_data;
251 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
252
253 // compress gain map
254 JpegEncoderHelper jpeg_enc_obj_gm;
255 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
256 jpegr_compressed_struct compressed_map;
257 compressed_map.data = jpeg_enc_obj_gm.getCompressedImagePtr();
258 compressed_map.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
259 compressed_map.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
260 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
261
262 std::shared_ptr<DataStruct> icc =
263 IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
264
265 // convert to Bt601 YUV encoding for JPEG encode
266 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
267 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)) && \
268 defined(__aarch64__))
269 JPEGR_CHECK(convertYuv_neon(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
270 #else
271 JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
272 #endif
273 }
274
275 // compress 420 image
276 JpegEncoderHelper jpeg_enc_obj_yuv420;
277 const uint8_t* planes[3]{reinterpret_cast<uint8_t*>(yuv420_image.data),
278 reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
279 reinterpret_cast<uint8_t*>(yuv420_image.chroma_data) +
280 yuv420_image.chroma_stride * yuv420_image.height / 2};
281 const size_t strides[3]{yuv420_image.luma_stride, yuv420_image.chroma_stride,
282 yuv420_image.chroma_stride};
283 if (!jpeg_enc_obj_yuv420.compressImage(planes, strides, yuv420_image.width, yuv420_image.height,
284 UHDR_IMG_FMT_12bppYCbCr420, quality, icc->getData(),
285 icc->getLength())) {
286 return ERROR_JPEGR_ENCODE_ERROR;
287 }
288 jpegr_compressed_struct jpeg;
289 jpeg.data = jpeg_enc_obj_yuv420.getCompressedImagePtr();
290 jpeg.length = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize());
291 jpeg.maxLength = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize());
292 jpeg.colorGamut = yuv420_image.colorGamut;
293
294 // append gain map, no ICC since JPEG encode already did it
295 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
296 &metadata, dest));
297
298 return JPEGR_NO_ERROR;
299 }
300
301 /* Encode API-1 */
encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,jr_uncompressed_ptr yuv420_image_ptr,ultrahdr_transfer_function hdr_tf,jr_compressed_ptr dest,int quality,jr_exif_ptr exif)302 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
303 jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
304 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
305 // validate input arguments
306 if (yuv420_image_ptr == nullptr) {
307 ALOGE("received nullptr for uncompressed 420 image");
308 return ERROR_JPEGR_BAD_PTR;
309 }
310 if (exif != nullptr && exif->data == nullptr) {
311 ALOGE("received nullptr for exif metadata");
312 return ERROR_JPEGR_BAD_PTR;
313 }
314 JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality))
315
316 // clean up input structure for later usage
317 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
318 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
319 if (!p010_image.chroma_data) {
320 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
321 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
322 p010_image.chroma_stride = p010_image.luma_stride;
323 }
324 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
325 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
326 if (!yuv420_image.chroma_data) {
327 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
328 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
329 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
330 }
331
332 // gain map
333 ultrahdr_metadata_struct metadata;
334 metadata.version = kJpegrVersion;
335 jpegr_uncompressed_struct gainmap_image;
336 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
337 std::unique_ptr<uint8_t[]> map_data;
338 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
339
340 // compress gain map
341 JpegEncoderHelper jpeg_enc_obj_gm;
342 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
343 jpegr_compressed_struct compressed_map;
344 compressed_map.data = jpeg_enc_obj_gm.getCompressedImagePtr();
345 compressed_map.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
346 compressed_map.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
347 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
348
349 std::shared_ptr<DataStruct> icc =
350 IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
351
352 jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
353 unique_ptr<uint8_t[]> yuv_420_bt601_data;
354 // Convert to bt601 YUV encoding for JPEG encode
355 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
356 const size_t yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, 16);
357 yuv_420_bt601_data =
358 make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
359 yuv420_bt601_image.data = yuv_420_bt601_data.get();
360 yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
361 yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
362 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
363 yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
364 yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
365
366 {
367 // copy luma
368 uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
369 uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
370 if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
371 memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
372 } else {
373 for (size_t i = 0; i < yuv420_image.height; i++) {
374 memcpy(y_dst, y_src, yuv420_image.width);
375 if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
376 memset(y_dst + yuv420_image.width, 0,
377 yuv420_bt601_image.luma_stride - yuv420_image.width);
378 }
379 y_dst += yuv420_bt601_image.luma_stride;
380 y_src += yuv420_image.luma_stride;
381 }
382 }
383 }
384
385 if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
386 // copy luma
387 uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
388 uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
389 memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
390 } else {
391 // copy cb & cr
392 uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
393 uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
394 uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
395 uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
396 for (size_t i = 0; i < yuv420_image.height / 2; i++) {
397 memcpy(cb_dst, cb_src, yuv420_image.width / 2);
398 memcpy(cr_dst, cr_src, yuv420_image.width / 2);
399 if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
400 memset(cb_dst + yuv420_image.width / 2, 0,
401 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
402 memset(cr_dst + yuv420_image.width / 2, 0,
403 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
404 }
405 cb_dst += yuv420_bt601_image.chroma_stride;
406 cb_src += yuv420_image.chroma_stride;
407 cr_dst += yuv420_bt601_image.chroma_stride;
408 cr_src += yuv420_image.chroma_stride;
409 }
410 }
411
412 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)) && \
413 defined(__aarch64__))
414 JPEGR_CHECK(
415 convertYuv_neon(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
416 #else
417 JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
418 #endif
419 }
420
421 // compress 420 image
422 JpegEncoderHelper jpeg_enc_obj_yuv420;
423 const uint8_t* planes[3]{reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
424 reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
425 reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data) +
426 yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2};
427 const size_t strides[3]{yuv420_bt601_image.luma_stride, yuv420_bt601_image.chroma_stride,
428 yuv420_bt601_image.chroma_stride};
429 if (!jpeg_enc_obj_yuv420.compressImage(planes, strides, yuv420_bt601_image.width,
430 yuv420_bt601_image.height, UHDR_IMG_FMT_12bppYCbCr420,
431 quality, icc->getData(), icc->getLength())) {
432 return ERROR_JPEGR_ENCODE_ERROR;
433 }
434
435 jpegr_compressed_struct jpeg;
436 jpeg.data = jpeg_enc_obj_yuv420.getCompressedImagePtr();
437 jpeg.length = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize());
438 jpeg.maxLength = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize());
439 jpeg.colorGamut = yuv420_image.colorGamut;
440
441 // append gain map, no ICC since JPEG encode already did it
442 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
443 &metadata, dest));
444 return JPEGR_NO_ERROR;
445 }
446
447 /* Encode API-2 */
encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,jr_uncompressed_ptr yuv420_image_ptr,jr_compressed_ptr yuv420jpg_image_ptr,ultrahdr_transfer_function hdr_tf,jr_compressed_ptr dest)448 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
449 jr_uncompressed_ptr yuv420_image_ptr,
450 jr_compressed_ptr yuv420jpg_image_ptr,
451 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
452 // validate input arguments
453 if (yuv420_image_ptr == nullptr) {
454 ALOGE("received nullptr for uncompressed 420 image");
455 return ERROR_JPEGR_BAD_PTR;
456 }
457 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
458 ALOGE("received nullptr for compressed jpeg image");
459 return ERROR_JPEGR_BAD_PTR;
460 }
461 JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest))
462
463 // clean up input structure for later usage
464 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
465 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
466 if (!p010_image.chroma_data) {
467 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
468 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
469 p010_image.chroma_stride = p010_image.luma_stride;
470 }
471 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
472 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
473 if (!yuv420_image.chroma_data) {
474 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
475 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
476 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
477 }
478
479 // gain map
480 ultrahdr_metadata_struct metadata;
481 metadata.version = kJpegrVersion;
482 jpegr_uncompressed_struct gainmap_image;
483 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
484 std::unique_ptr<uint8_t[]> map_data;
485 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
486
487 // compress gain map
488 JpegEncoderHelper jpeg_enc_obj_gm;
489 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
490 jpegr_compressed_struct gainmapjpg_image;
491 gainmapjpg_image.data = jpeg_enc_obj_gm.getCompressedImagePtr();
492 gainmapjpg_image.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
493 gainmapjpg_image.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
494 gainmapjpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
495
496 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
497 }
498
499 /* Encode API-3 */
encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,jr_compressed_ptr yuv420jpg_image_ptr,ultrahdr_transfer_function hdr_tf,jr_compressed_ptr dest)500 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
501 jr_compressed_ptr yuv420jpg_image_ptr,
502 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
503 // validate input arguments
504 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
505 ALOGE("received nullptr for compressed jpeg image");
506 return ERROR_JPEGR_BAD_PTR;
507 }
508 JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest))
509
510 // clean up input structure for later usage
511 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
512 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
513 if (!p010_image.chroma_data) {
514 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
515 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
516 p010_image.chroma_stride = p010_image.luma_stride;
517 }
518
519 // decode input jpeg, gamut is going to be bt601.
520 JpegDecoderHelper jpeg_dec_obj_yuv420;
521 if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
522 yuv420jpg_image_ptr->length)) {
523 return ERROR_JPEGR_DECODE_ERROR;
524 }
525 jpegr_uncompressed_struct yuv420_image{};
526 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
527 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
528 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
529 if (jpeg_dec_obj_yuv420.getICCSize() > 0) {
530 ultrahdr_color_gamut cg = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
531 jpeg_dec_obj_yuv420.getICCSize());
532 if (cg == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
533 (yuv420jpg_image_ptr->colorGamut != ULTRAHDR_COLORGAMUT_UNSPECIFIED &&
534 yuv420jpg_image_ptr->colorGamut != cg)) {
535 ALOGE("configured color gamut %d does not match with color gamut specified in icc box %d",
536 yuv420jpg_image_ptr->colorGamut, cg);
537 return ERROR_JPEGR_INVALID_COLORGAMUT;
538 }
539 yuv420_image.colorGamut = cg;
540 } else {
541 if (yuv420jpg_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
542 yuv420jpg_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
543 ALOGE("Unrecognized 420 color gamut %d", yuv420jpg_image_ptr->colorGamut);
544 return ERROR_JPEGR_INVALID_COLORGAMUT;
545 }
546 yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
547 }
548 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
549 if (!yuv420_image.chroma_data) {
550 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
551 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
552 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
553 }
554
555 if (p010_image_ptr->width != yuv420_image.width ||
556 p010_image_ptr->height != yuv420_image.height) {
557 return ERROR_JPEGR_RESOLUTION_MISMATCH;
558 }
559
560 // gain map
561 ultrahdr_metadata_struct metadata;
562 metadata.version = kJpegrVersion;
563 jpegr_uncompressed_struct gainmap_image;
564 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
565 true /* sdr_is_601 */));
566 std::unique_ptr<uint8_t[]> map_data;
567 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
568
569 // compress gain map
570 JpegEncoderHelper jpeg_enc_obj_gm;
571 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
572 jpegr_compressed_struct gainmapjpg_image;
573 gainmapjpg_image.data = jpeg_enc_obj_gm.getCompressedImagePtr();
574 gainmapjpg_image.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
575 gainmapjpg_image.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize());
576 gainmapjpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
577
578 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
579 }
580
581 /* Encode API-4 */
encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,jr_compressed_ptr gainmapjpg_image_ptr,ultrahdr_metadata_ptr metadata,jr_compressed_ptr dest)582 status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
583 jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
584 jr_compressed_ptr dest) {
585 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
586 ALOGE("received nullptr for compressed jpeg image");
587 return ERROR_JPEGR_BAD_PTR;
588 }
589 if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
590 ALOGE("received nullptr for compressed gain map");
591 return ERROR_JPEGR_BAD_PTR;
592 }
593 if (dest == nullptr || dest->data == nullptr) {
594 ALOGE("received nullptr for destination");
595 return ERROR_JPEGR_BAD_PTR;
596 }
597
598 // We just want to check if ICC is present, so don't do a full decode. Note,
599 // this doesn't verify that the ICC is valid.
600 JpegDecoderHelper decoder;
601 if (!decoder.parseImage(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length)) {
602 return ERROR_JPEGR_DECODE_ERROR;
603 }
604
605 // Add ICC if not already present.
606 if (decoder.getICCSize() > 0) {
607 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
608 /* icc */ nullptr, /* icc size */ 0, metadata, dest));
609 } else {
610 if (yuv420jpg_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
611 yuv420jpg_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
612 ALOGE("Unrecognized 420 color gamut %d", yuv420jpg_image_ptr->colorGamut);
613 return ERROR_JPEGR_INVALID_COLORGAMUT;
614 }
615 std::shared_ptr<DataStruct> newIcc =
616 IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
617 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
618 newIcc->getData(), newIcc->getLength(), metadata, dest));
619 }
620
621 return JPEGR_NO_ERROR;
622 }
623
getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr,jr_info_ptr jpegr_image_info_ptr)624 status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr) {
625 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
626 ALOGE("received nullptr for compressed jpegr image");
627 return ERROR_JPEGR_BAD_PTR;
628 }
629 if (jpegr_image_info_ptr == nullptr) {
630 ALOGE("received nullptr for compressed jpegr info struct");
631 return ERROR_JPEGR_BAD_PTR;
632 }
633
634 jpegr_compressed_struct primary_image, gainmap_image;
635 JPEGR_CHECK(extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image))
636
637 JPEGR_CHECK(parseJpegInfo(&primary_image, jpegr_image_info_ptr->primaryImgInfo,
638 &jpegr_image_info_ptr->width, &jpegr_image_info_ptr->height))
639 if (jpegr_image_info_ptr->gainmapImgInfo != nullptr) {
640 JPEGR_CHECK(parseJpegInfo(&gainmap_image, jpegr_image_info_ptr->gainmapImgInfo))
641 }
642
643 return JPEGR_NO_ERROR;
644 }
645
646 /* Decode API */
decodeJPEGR(jr_compressed_ptr jpegr_image_ptr,jr_uncompressed_ptr dest,float max_display_boost,jr_exif_ptr exif,ultrahdr_output_format output_format,jr_uncompressed_ptr gainmap_image_ptr,ultrahdr_metadata_ptr metadata)647 status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
648 float max_display_boost, jr_exif_ptr exif,
649 ultrahdr_output_format output_format,
650 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
651 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
652 ALOGE("received nullptr for compressed jpegr image");
653 return ERROR_JPEGR_BAD_PTR;
654 }
655 if (dest == nullptr || dest->data == nullptr) {
656 ALOGE("received nullptr for dest image");
657 return ERROR_JPEGR_BAD_PTR;
658 }
659 if (max_display_boost < 1.0f) {
660 ALOGE("received bad value for max_display_boost %f", max_display_boost);
661 return ERROR_JPEGR_INVALID_DISPLAY_BOOST;
662 }
663 if (exif != nullptr && exif->data == nullptr) {
664 ALOGE("received nullptr address for exif data");
665 return ERROR_JPEGR_BAD_PTR;
666 }
667 if (gainmap_image_ptr != nullptr && gainmap_image_ptr->data == nullptr) {
668 ALOGE("received nullptr address for gainmap data");
669 return ERROR_JPEGR_BAD_PTR;
670 }
671 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
672 ALOGE("received bad value for output format %d", output_format);
673 return ERROR_JPEGR_INVALID_OUTPUT_FORMAT;
674 }
675
676 jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
677 JPEGR_CHECK(
678 extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image))
679
680 JpegDecoderHelper jpeg_dec_obj_yuv420;
681 if (!jpeg_dec_obj_yuv420.decompressImage(
682 primary_jpeg_image.data, primary_jpeg_image.length,
683 (output_format == ULTRAHDR_OUTPUT_SDR) ? DECODE_TO_RGB_CS : DECODE_TO_YCBCR_CS)) {
684 return ERROR_JPEGR_DECODE_ERROR;
685 }
686
687 if (output_format == ULTRAHDR_OUTPUT_SDR) {
688 #ifdef JCS_ALPHA_EXTENSIONS
689 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
690 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
691 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
692 return ERROR_JPEGR_DECODE_ERROR;
693 }
694 #else
695 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
696 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3) >
697 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
698 return ERROR_JPEGR_DECODE_ERROR;
699 }
700 #endif
701 } else {
702 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
703 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
704 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
705 return ERROR_JPEGR_DECODE_ERROR;
706 }
707 }
708
709 if (exif != nullptr) {
710 if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
711 return ERROR_JPEGR_BUFFER_TOO_SMALL;
712 }
713 memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
714 exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
715 }
716
717 JpegDecoderHelper jpeg_dec_obj_gm;
718 jpegr_uncompressed_struct gainmap_image;
719 if (gainmap_image_ptr != nullptr || output_format != ULTRAHDR_OUTPUT_SDR) {
720 if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length,
721 DECODE_STREAM)) {
722 return ERROR_JPEGR_DECODE_ERROR;
723 }
724 gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
725 gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
726 gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
727 gainmap_image.pixelFormat = jpeg_dec_obj_gm.getDecompressedImageFormat();
728
729 if (gainmap_image_ptr != nullptr) {
730 gainmap_image_ptr->width = gainmap_image.width;
731 gainmap_image_ptr->height = gainmap_image.height;
732 gainmap_image_ptr->pixelFormat = gainmap_image.pixelFormat;
733 memcpy(gainmap_image_ptr->data, gainmap_image.data,
734 gainmap_image_ptr->width * gainmap_image_ptr->height);
735 }
736 }
737
738 ultrahdr_metadata_struct uhdr_metadata;
739 if (metadata != nullptr || output_format != ULTRAHDR_OUTPUT_SDR) {
740 uint8_t* iso_ptr = static_cast<uint8_t*>(jpeg_dec_obj_gm.getIsoMetadataPtr());
741 if (iso_ptr != nullptr) {
742 size_t iso_size = jpeg_dec_obj_gm.getIsoMetadataSize();
743 if (iso_size < kIsoNameSpace.size() + 1) {
744 return ERROR_JPEGR_METADATA_ERROR;
745 }
746 gain_map_metadata decodedMetadata;
747 std::vector<uint8_t> iso_vec;
748 for (size_t i = kIsoNameSpace.size() + 1; i < iso_size; i++) {
749 iso_vec.push_back(iso_ptr[i]);
750 }
751
752 JPEGR_CHECK(gain_map_metadata::decodeGainmapMetadata(iso_vec, &decodedMetadata));
753 JPEGR_CHECK(
754 gain_map_metadata::gainmapMetadataFractionToFloat(&decodedMetadata, &uhdr_metadata));
755 } else {
756 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
757 jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
758 return ERROR_JPEGR_METADATA_ERROR;
759 }
760 }
761 if (metadata != nullptr) {
762 metadata->version = uhdr_metadata.version;
763 metadata->minContentBoost = uhdr_metadata.minContentBoost;
764 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
765 metadata->gamma = uhdr_metadata.gamma;
766 metadata->offsetSdr = uhdr_metadata.offsetSdr;
767 metadata->offsetHdr = uhdr_metadata.offsetHdr;
768 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
769 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
770 }
771 }
772
773 if (output_format == ULTRAHDR_OUTPUT_SDR) {
774 dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
775 dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
776 #ifdef JCS_ALPHA_EXTENSIONS
777 memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
778 dest->width * dest->height * 4);
779 #else
780 uint32_t* pixelDst = static_cast<uint32_t*>(dest->data);
781 uint8_t* pixelSrc = static_cast<uint8_t*>(jpeg_dec_obj_yuv420.getDecompressedImagePtr());
782 for (int i = 0; i < dest->width * dest->height; i++) {
783 *pixelDst = pixelSrc[0] | (pixelSrc[1] << 8) | (pixelSrc[2] << 16) | (0xff << 24);
784 pixelSrc += 3;
785 pixelDst += 1;
786 }
787 #endif
788 dest->colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
789 jpeg_dec_obj_yuv420.getICCSize());
790 return JPEGR_NO_ERROR;
791 }
792
793 jpegr_uncompressed_struct yuv420_image;
794 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
795 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
796 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
797 yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
798 jpeg_dec_obj_yuv420.getICCSize());
799 yuv420_image.luma_stride = yuv420_image.width;
800 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
801 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
802 yuv420_image.chroma_stride = yuv420_image.width >> 1;
803
804 JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
805 max_display_boost, dest));
806 return JPEGR_NO_ERROR;
807 }
808
compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,JpegEncoderHelper * jpeg_enc_obj_ptr)809 status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
810 JpegEncoderHelper* jpeg_enc_obj_ptr) {
811 if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
812 return ERROR_JPEGR_BAD_PTR;
813 }
814
815 const uint8_t* planes[]{reinterpret_cast<uint8_t*>(gainmap_image_ptr->data)};
816 if (kUseMultiChannelGainMap) {
817 const size_t strides[]{gainmap_image_ptr->width * 3};
818 if (!jpeg_enc_obj_ptr->compressImage(planes, strides, gainmap_image_ptr->width,
819 gainmap_image_ptr->height, UHDR_IMG_FMT_24bppRGB888,
820 kMapCompressQuality, nullptr, 0)) {
821 return ERROR_JPEGR_ENCODE_ERROR;
822 }
823 } else {
824 const size_t strides[]{gainmap_image_ptr->width};
825 // Don't need to convert YUV to Bt601 since single channel
826 if (!jpeg_enc_obj_ptr->compressImage(planes, strides, gainmap_image_ptr->width,
827 gainmap_image_ptr->height, UHDR_IMG_FMT_8bppYCbCr400,
828 kMapCompressQuality, nullptr, 0)) {
829 return ERROR_JPEGR_ENCODE_ERROR;
830 }
831 }
832
833 return JPEGR_NO_ERROR;
834 }
835
836 const int kJobSzInRows = 16;
837 static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
838 "align job size to kMapDimensionScaleFactor");
839
840 class JobQueue {
841 public:
842 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
843 void enqueueJob(size_t rowStart, size_t rowEnd);
844 void markQueueForEnd();
845 void reset();
846
847 private:
848 bool mQueuedAllJobs = false;
849 std::deque<std::tuple<size_t, size_t>> mJobs;
850 std::mutex mMutex;
851 std::condition_variable mCv;
852 };
853
dequeueJob(size_t & rowStart,size_t & rowEnd)854 bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
855 std::unique_lock<std::mutex> lock{mMutex};
856 while (true) {
857 if (mJobs.empty()) {
858 if (mQueuedAllJobs) {
859 return false;
860 } else {
861 mCv.wait_for(lock, std::chrono::milliseconds(100));
862 }
863 } else {
864 auto it = mJobs.begin();
865 rowStart = std::get<0>(*it);
866 rowEnd = std::get<1>(*it);
867 mJobs.erase(it);
868 return true;
869 }
870 }
871 return false;
872 }
873
enqueueJob(size_t rowStart,size_t rowEnd)874 void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
875 std::unique_lock<std::mutex> lock{mMutex};
876 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
877 lock.unlock();
878 mCv.notify_one();
879 }
880
markQueueForEnd()881 void JobQueue::markQueueForEnd() {
882 std::unique_lock<std::mutex> lock{mMutex};
883 mQueuedAllJobs = true;
884 lock.unlock();
885 mCv.notify_all();
886 }
887
reset()888 void JobQueue::reset() {
889 std::unique_lock<std::mutex> lock{mMutex};
890 mJobs.clear();
891 mQueuedAllJobs = false;
892 }
893
generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,jr_uncompressed_ptr p010_image_ptr,ultrahdr_transfer_function hdr_tf,ultrahdr_metadata_ptr metadata,jr_uncompressed_ptr dest,bool sdr_is_601)894 status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
895 jr_uncompressed_ptr p010_image_ptr,
896 ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
897 jr_uncompressed_ptr dest, bool sdr_is_601) {
898 /*if (kUseMultiChannelGainMap) {
899 static_assert(kWriteIso21496_1Metadata && !kWriteXmpMetadata,
900 "Multi-channel gain map now is only supported for ISO 21496-1 metadata");
901 }*/
902
903 int gainMapChannelCount = kUseMultiChannelGainMap ? 3 : 1;
904
905 if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
906 dest == nullptr || yuv420_image_ptr->data == nullptr ||
907 yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
908 p010_image_ptr->chroma_data == nullptr) {
909 return ERROR_JPEGR_BAD_PTR;
910 }
911 if (yuv420_image_ptr->width != p010_image_ptr->width ||
912 yuv420_image_ptr->height != p010_image_ptr->height) {
913 return ERROR_JPEGR_RESOLUTION_MISMATCH;
914 }
915 if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
916 p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
917 return ERROR_JPEGR_INVALID_COLORGAMUT;
918 }
919
920 size_t image_width = yuv420_image_ptr->width;
921 size_t image_height = yuv420_image_ptr->height;
922 size_t map_width = image_width / kMapDimensionScaleFactor;
923 size_t map_height = image_height / kMapDimensionScaleFactor;
924
925 dest->data = new uint8_t[map_width * map_height * gainMapChannelCount];
926 dest->width = map_width;
927 dest->height = map_height;
928 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
929 dest->luma_stride = map_width;
930 dest->chroma_data = nullptr;
931 dest->chroma_stride = 0;
932 std::unique_ptr<uint8_t[]> map_data;
933 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
934
935 ColorTransformFn hdrInvOetf = nullptr;
936 float hdr_white_nits;
937 switch (hdr_tf) {
938 case ULTRAHDR_TF_LINEAR:
939 hdrInvOetf = identityConversion;
940 // Note: this will produce clipping if the input exceeds kHlgMaxNits.
941 // TODO: TF LINEAR will be deprecated.
942 hdr_white_nits = kHlgMaxNits;
943 break;
944 case ULTRAHDR_TF_HLG:
945 #if USE_HLG_INVOETF_LUT
946 hdrInvOetf = hlgInvOetfLUT;
947 #else
948 hdrInvOetf = hlgInvOetf;
949 #endif
950 hdr_white_nits = kHlgMaxNits;
951 break;
952 case ULTRAHDR_TF_PQ:
953 #if USE_PQ_INVOETF_LUT
954 hdrInvOetf = pqInvOetfLUT;
955 #else
956 hdrInvOetf = pqInvOetf;
957 #endif
958 hdr_white_nits = kPqMaxNits;
959 break;
960 default:
961 // Should be impossible to hit after input validation.
962 return ERROR_JPEGR_INVALID_TRANS_FUNC;
963 }
964
965 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
966 metadata->minContentBoost = 1.0f;
967 metadata->gamma = 1.0f;
968 metadata->offsetSdr = 0.0f;
969 metadata->offsetHdr = 0.0f;
970 metadata->hdrCapacityMin = 1.0f;
971 metadata->hdrCapacityMax = metadata->maxContentBoost;
972
973 float log2MinBoost = log2(metadata->minContentBoost);
974 float log2MaxBoost = log2(metadata->maxContentBoost);
975
976 ColorTransformFn hdrGamutConversionFn =
977 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
978
979 ColorCalculationFn luminanceFn = nullptr;
980 ColorTransformFn sdrYuvToRgbFn = nullptr;
981 switch (yuv420_image_ptr->colorGamut) {
982 case ULTRAHDR_COLORGAMUT_BT709:
983 luminanceFn = srgbLuminance;
984 sdrYuvToRgbFn = srgbYuvToRgb;
985 break;
986 case ULTRAHDR_COLORGAMUT_P3:
987 luminanceFn = p3Luminance;
988 sdrYuvToRgbFn = p3YuvToRgb;
989 break;
990 case ULTRAHDR_COLORGAMUT_BT2100:
991 luminanceFn = bt2100Luminance;
992 sdrYuvToRgbFn = bt2100YuvToRgb;
993 break;
994 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
995 // Should be impossible to hit after input validation.
996 return ERROR_JPEGR_INVALID_COLORGAMUT;
997 }
998 if (sdr_is_601) {
999 sdrYuvToRgbFn = p3YuvToRgb;
1000 }
1001
1002 ColorTransformFn hdrYuvToRgbFn = nullptr;
1003 switch (p010_image_ptr->colorGamut) {
1004 case ULTRAHDR_COLORGAMUT_BT709:
1005 hdrYuvToRgbFn = srgbYuvToRgb;
1006 break;
1007 case ULTRAHDR_COLORGAMUT_P3:
1008 hdrYuvToRgbFn = p3YuvToRgb;
1009 break;
1010 case ULTRAHDR_COLORGAMUT_BT2100:
1011 hdrYuvToRgbFn = bt2100YuvToRgb;
1012 break;
1013 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
1014 // Should be impossible to hit after input validation.
1015 return ERROR_JPEGR_INVALID_COLORGAMUT;
1016 }
1017
1018 const int threads = (std::min)(GetCPUCoreCount(), 4);
1019 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
1020 JobQueue jobQueue;
1021 std::function<void()> generateMap;
1022
1023 if (kUseMultiChannelGainMap) {
1024 generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
1025 hdrGamutConversionFn, sdrYuvToRgbFn, gainMapChannelCount, hdrYuvToRgbFn,
1026 hdr_white_nits, log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
1027 size_t rowStart, rowEnd;
1028 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1029 for (size_t y = rowStart; y < rowEnd; ++y) {
1030 for (size_t x = 0; x < dest->width; ++x) {
1031 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
1032 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
1033 // We are assuming the SDR input is always sRGB transfer.
1034 #if USE_SRGB_INVOETF_LUT
1035 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
1036 #else
1037 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
1038 #endif
1039 Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
1040
1041 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
1042 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
1043 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
1044 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
1045 Color hdr_rgb_nits = hdr_rgb * hdr_white_nits;
1046
1047 size_t pixel_idx = (x + y * dest->width) * gainMapChannelCount;
1048
1049 // R
1050 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
1051 encodeGain(sdr_rgb_nits.r, hdr_rgb_nits.r, metadata, log2MinBoost, log2MaxBoost);
1052 // G
1053 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx + 1] =
1054 encodeGain(sdr_rgb_nits.g, hdr_rgb_nits.g, metadata, log2MinBoost, log2MaxBoost);
1055 // B
1056 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx + 2] =
1057 encodeGain(sdr_rgb_nits.b, hdr_rgb_nits.b, metadata, log2MinBoost, log2MaxBoost);
1058 }
1059 }
1060 }
1061 };
1062 } else {
1063 generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
1064 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits,
1065 log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
1066 size_t rowStart, rowEnd;
1067 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1068 for (size_t y = rowStart; y < rowEnd; ++y) {
1069 for (size_t x = 0; x < dest->width; ++x) {
1070 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
1071 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
1072 // We are assuming the SDR input is always sRGB transfer.
1073 #if USE_SRGB_INVOETF_LUT
1074 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
1075 #else
1076 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
1077 #endif
1078 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
1079
1080 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
1081 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
1082 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
1083 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
1084 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
1085
1086 size_t pixel_idx = x + y * dest->width;
1087 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
1088 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
1089 }
1090 }
1091 }
1092 };
1093 }
1094
1095 // generate map
1096 std::vector<std::thread> workers;
1097 for (int th = 0; th < threads - 1; th++) {
1098 workers.push_back(std::thread(generateMap));
1099 }
1100
1101 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
1102 for (size_t rowStart = 0; rowStart < map_height;) {
1103 size_t rowEnd = (std::min)(rowStart + rowStep, map_height);
1104 jobQueue.enqueueJob(rowStart, rowEnd);
1105 rowStart = rowEnd;
1106 }
1107 jobQueue.markQueueForEnd();
1108 generateMap();
1109 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
1110
1111 map_data.release();
1112
1113 return JPEGR_NO_ERROR;
1114 }
1115
applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,jr_uncompressed_ptr gainmap_image_ptr,ultrahdr_metadata_ptr metadata,ultrahdr_output_format output_format,float max_display_boost,jr_uncompressed_ptr dest)1116 status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
1117 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
1118 ultrahdr_output_format output_format, float max_display_boost,
1119 jr_uncompressed_ptr dest) {
1120 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
1121 dest == nullptr || yuv420_image_ptr->data == nullptr ||
1122 yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
1123 return ERROR_JPEGR_BAD_PTR;
1124 }
1125 if (metadata->version.compare(kJpegrVersion)) {
1126 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
1127 return ERROR_JPEGR_BAD_METADATA;
1128 }
1129 if (metadata->gamma != 1.0f) {
1130 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
1131 return ERROR_JPEGR_BAD_METADATA;
1132 }
1133 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
1134 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
1135 return ERROR_JPEGR_BAD_METADATA;
1136 }
1137 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
1138 metadata->hdrCapacityMax != metadata->maxContentBoost) {
1139 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
1140 metadata->hdrCapacityMax);
1141 return ERROR_JPEGR_BAD_METADATA;
1142 }
1143
1144 {
1145 float primary_aspect_ratio = (float) yuv420_image_ptr->width / yuv420_image_ptr->height;
1146 float gainmap_aspect_ratio = (float) gainmap_image_ptr->width / gainmap_image_ptr->height;
1147 float delta_aspect_ratio = fabs(primary_aspect_ratio - gainmap_aspect_ratio);
1148 // Allow 1% delta
1149 const float delta_tolerance = 0.01;
1150 if (delta_aspect_ratio / primary_aspect_ratio > delta_tolerance) {
1151 ALOGE(
1152 "gain map dimensions scale factor values for height and width are different, \n primary "
1153 "image resolution is %zux%zu, received gain map resolution is %zux%zu",
1154 yuv420_image_ptr->width, yuv420_image_ptr->height, gainmap_image_ptr->width,
1155 gainmap_image_ptr->height);
1156 return ERROR_JPEGR_UNSUPPORTED_MAP_SCALE_FACTOR;
1157 }
1158 }
1159
1160 float map_scale_factor = (float) yuv420_image_ptr->width / gainmap_image_ptr->width;
1161
1162 dest->width = yuv420_image_ptr->width;
1163 dest->height = yuv420_image_ptr->height;
1164 dest->colorGamut = yuv420_image_ptr->colorGamut;
1165 // Table will only be used when map scale factor is integer.
1166 ShepardsIDW idwTable(static_cast<int>(map_scale_factor));
1167 float display_boost = (std::min)(max_display_boost, metadata->maxContentBoost);
1168 GainLUT gainLUT(metadata, display_boost);
1169
1170 JobQueue jobQueue;
1171 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, dest, &jobQueue,
1172 &idwTable, output_format, &gainLUT, display_boost,
1173 map_scale_factor]() -> void {
1174 size_t width = yuv420_image_ptr->width;
1175
1176 size_t rowStart, rowEnd;
1177 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1178 for (size_t y = rowStart; y < rowEnd; ++y) {
1179 for (size_t x = 0; x < width; ++x) {
1180 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
1181 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1182 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1183 // We are assuming the SDR base image is always sRGB transfer.
1184 #if USE_SRGB_INVOETF_LUT
1185 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1186 #else
1187 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
1188 #endif
1189 Color rgb_hdr;
1190 if (gainmap_image_ptr->pixelFormat == UHDR_IMG_FMT_8bppYCbCr400) {
1191 float gain;
1192
1193 if (map_scale_factor != floorf(map_scale_factor)) {
1194 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
1195 } else {
1196 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
1197 }
1198
1199 #if USE_APPLY_GAIN_LUT
1200 rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
1201 #else
1202 rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
1203 #endif
1204 } else {
1205 Color gain;
1206
1207 if (map_scale_factor != floorf(map_scale_factor)) {
1208 gain =
1209 sampleMap3Channel(gainmap_image_ptr, map_scale_factor, x, y,
1210 gainmap_image_ptr->pixelFormat == UHDR_IMG_FMT_32bppRGBA8888);
1211 } else {
1212 gain =
1213 sampleMap3Channel(gainmap_image_ptr, map_scale_factor, x, y, idwTable,
1214 gainmap_image_ptr->pixelFormat == UHDR_IMG_FMT_32bppRGBA8888);
1215 }
1216
1217 #if USE_APPLY_GAIN_LUT
1218 rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
1219 #else
1220 rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
1221 #endif
1222 }
1223
1224 rgb_hdr = rgb_hdr / display_boost;
1225 size_t pixel_idx = x + y * width;
1226
1227 switch (output_format) {
1228 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
1229 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1230 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1231 break;
1232 }
1233 case ULTRAHDR_OUTPUT_HDR_HLG: {
1234 #if USE_HLG_OETF_LUT
1235 ColorTransformFn hdrOetf = hlgOetfLUT;
1236 #else
1237 ColorTransformFn hdrOetf = hlgOetf;
1238 #endif
1239 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1240 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1241 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1242 break;
1243 }
1244 case ULTRAHDR_OUTPUT_HDR_PQ: {
1245 #if USE_PQ_OETF_LUT
1246 ColorTransformFn hdrOetf = pqOetfLUT;
1247 #else
1248 ColorTransformFn hdrOetf = pqOetf;
1249 #endif
1250 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1251 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1252 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1253 break;
1254 }
1255 default: {
1256 }
1257 // Should be impossible to hit after input validation.
1258 }
1259 }
1260 }
1261 }
1262 };
1263
1264 const int threads = (std::min)(GetCPUCoreCount(), 4);
1265 std::vector<std::thread> workers;
1266 for (int th = 0; th < threads - 1; th++) {
1267 workers.push_back(std::thread(applyRecMap));
1268 }
1269 const int rowStep = threads == 1 ? yuv420_image_ptr->height : map_scale_factor;
1270 for (size_t rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1271 int rowEnd = (std::min)(rowStart + rowStep, yuv420_image_ptr->height);
1272 jobQueue.enqueueJob(rowStart, rowEnd);
1273 rowStart = rowEnd;
1274 }
1275 jobQueue.markQueueForEnd();
1276 applyRecMap();
1277 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
1278 return JPEGR_NO_ERROR;
1279 }
1280
extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,jr_compressed_ptr primary_jpg_image_ptr,jr_compressed_ptr gainmap_jpg_image_ptr)1281 status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1282 jr_compressed_ptr primary_jpg_image_ptr,
1283 jr_compressed_ptr gainmap_jpg_image_ptr) {
1284 if (jpegr_image_ptr == nullptr) {
1285 return ERROR_JPEGR_BAD_PTR;
1286 }
1287
1288 MessageHandler msg_handler;
1289 msg_handler.SetMessageWriter(make_unique<AlogMessageWriter>(AlogMessageWriter()));
1290 std::shared_ptr<DataSegment> seg = DataSegment::Create(
1291 DataRange(0, jpegr_image_ptr->length), static_cast<const uint8_t*>(jpegr_image_ptr->data),
1292 DataSegment::BufferDispositionPolicy::kDontDelete);
1293 DataSegmentDataSource data_source(seg);
1294 JpegInfoBuilder jpeg_info_builder;
1295 jpeg_info_builder.SetImageLimit(2);
1296 JpegScanner jpeg_scanner(&msg_handler);
1297 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1298 data_source.Reset();
1299
1300 if (jpeg_scanner.HasError()) {
1301 return JPEGR_UNKNOWN_ERROR;
1302 }
1303
1304 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1305 const auto& image_ranges = jpeg_info.GetImageRanges();
1306
1307 if (image_ranges.empty()) {
1308 return ERROR_JPEGR_NO_IMAGES_FOUND;
1309 }
1310
1311 if (primary_jpg_image_ptr != nullptr) {
1312 primary_jpg_image_ptr->data =
1313 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1314 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
1315 }
1316
1317 if (image_ranges.size() == 1) {
1318 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1319 }
1320
1321 if (gainmap_jpg_image_ptr != nullptr) {
1322 gainmap_jpg_image_ptr->data =
1323 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1324 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
1325 }
1326
1327 // TODO: choose primary image and gain map image carefully
1328 if (image_ranges.size() > 2) {
1329 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1330 (int)image_ranges.size());
1331 }
1332
1333 return JPEGR_NO_ERROR;
1334 }
1335
parseJpegInfo(jr_compressed_ptr jpeg_image_ptr,j_info_ptr jpeg_image_info_ptr,size_t * img_width,size_t * img_height)1336 status_t JpegR::parseJpegInfo(jr_compressed_ptr jpeg_image_ptr, j_info_ptr jpeg_image_info_ptr,
1337 size_t* img_width, size_t* img_height) {
1338 JpegDecoderHelper jpeg_dec_obj;
1339 if (!jpeg_dec_obj.parseImage(jpeg_image_ptr->data, jpeg_image_ptr->length)) {
1340 return ERROR_JPEGR_DECODE_ERROR;
1341 }
1342 size_t imgWidth, imgHeight;
1343 imgWidth = jpeg_dec_obj.getDecompressedImageWidth();
1344 imgHeight = jpeg_dec_obj.getDecompressedImageHeight();
1345
1346 if (jpeg_image_info_ptr != nullptr) {
1347 jpeg_image_info_ptr->width = imgWidth;
1348 jpeg_image_info_ptr->height = imgHeight;
1349 jpeg_image_info_ptr->imgData.resize(jpeg_image_ptr->length, 0);
1350 memcpy(static_cast<void*>(jpeg_image_info_ptr->imgData.data()), jpeg_image_ptr->data,
1351 jpeg_image_ptr->length);
1352 if (jpeg_dec_obj.getICCSize() != 0) {
1353 jpeg_image_info_ptr->iccData.resize(jpeg_dec_obj.getICCSize(), 0);
1354 memcpy(static_cast<void*>(jpeg_image_info_ptr->iccData.data()), jpeg_dec_obj.getICCPtr(),
1355 jpeg_dec_obj.getICCSize());
1356 }
1357 if (jpeg_dec_obj.getEXIFSize() != 0) {
1358 jpeg_image_info_ptr->exifData.resize(jpeg_dec_obj.getEXIFSize(), 0);
1359 memcpy(static_cast<void*>(jpeg_image_info_ptr->exifData.data()), jpeg_dec_obj.getEXIFPtr(),
1360 jpeg_dec_obj.getEXIFSize());
1361 }
1362 if (jpeg_dec_obj.getXMPSize() != 0) {
1363 jpeg_image_info_ptr->xmpData.resize(jpeg_dec_obj.getXMPSize(), 0);
1364 memcpy(static_cast<void*>(jpeg_image_info_ptr->xmpData.data()), jpeg_dec_obj.getXMPPtr(),
1365 jpeg_dec_obj.getXMPSize());
1366 }
1367 }
1368 if (img_width != nullptr && img_height != nullptr) {
1369 *img_width = imgWidth;
1370 *img_height = imgHeight;
1371 }
1372 return JPEGR_NO_ERROR;
1373 }
1374
1375 // JPEG/R structure:
1376 // SOI (ff d8)
1377 //
1378 // (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
1379 // in the JPEG input (Encode API-2, API-3, API-4))
1380 // APP1 (ff e1)
1381 // 2 bytes of length (2 + length of exif package)
1382 // EXIF package (this includes the first two bytes representing the package length)
1383 //
1384 // (Required, XMP package) APP1 (ff e1)
1385 // 2 bytes of length (2 + 29 + length of xmp package)
1386 // name space ("http://ns.adobe.com/xap/1.0/\0")
1387 // XMP
1388 //
1389 // (Required, ISO 21496-1 metadata, version only) APP2 (ff e2)
1390 // 2 bytes of length
1391 // name space (""urn:iso:std:iso:ts:21496:-1\0")
1392 // 2 bytes minimum_version: (00 00)
1393 // 2 bytes writer_version: (00 00)
1394 //
1395 // (Required, MPF package) APP2 (ff e2)
1396 // 2 bytes of length
1397 // MPF
1398 //
1399 // (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
1400 //
1401 // SOI (ff d8)
1402 //
1403 // (Required, XMP package) APP1 (ff e1)
1404 // 2 bytes of length (2 + 29 + length of xmp package)
1405 // name space ("http://ns.adobe.com/xap/1.0/\0")
1406 // XMP
1407 //
1408 // (Required, ISO 21496-1 metadata) APP2 (ff e2)
1409 // 2 bytes of length
1410 // name space (""urn:iso:std:iso:ts:21496:-1\0")
1411 // metadata
1412 //
1413 // (Required) secondary image (the gain map, without the first two bytes (SOI))
1414 //
1415 // Metadata versions we are using:
1416 // ECMA TR-98 for JFIF marker
1417 // Exif 2.2 spec for EXIF marker
1418 // Adobe XMP spec part 3 for XMP marker
1419 // ICC v4.3 spec for ICC
appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,jr_compressed_ptr gainmap_jpg_image_ptr,jr_exif_ptr pExif,void * pIcc,size_t icc_size,ultrahdr_metadata_ptr metadata,jr_compressed_ptr dest)1420 status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
1421 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
1422 void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
1423 jr_compressed_ptr dest) {
1424 static_assert(kWriteXmpMetadata || kWriteIso21496_1Metadata,
1425 "Must write gain map metadata in XMP format, or iso 21496-1 format, or both.");
1426 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1427 dest == nullptr) {
1428 return ERROR_JPEGR_BAD_PTR;
1429 }
1430 if (metadata->version.compare("1.0")) {
1431 ALOGE("received bad value for version: %s", metadata->version.c_str());
1432 return ERROR_JPEGR_BAD_METADATA;
1433 }
1434 if (metadata->maxContentBoost < metadata->minContentBoost) {
1435 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
1436 metadata->maxContentBoost);
1437 return ERROR_JPEGR_BAD_METADATA;
1438 }
1439 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1440 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
1441 metadata->hdrCapacityMax);
1442 return ERROR_JPEGR_BAD_METADATA;
1443 }
1444 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
1445 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
1446 return ERROR_JPEGR_BAD_METADATA;
1447 }
1448 if (metadata->gamma <= 0.0f) {
1449 ALOGE("received bad value for gamma %f", metadata->gamma);
1450 return ERROR_JPEGR_BAD_METADATA;
1451 }
1452
1453 const int xmpNameSpaceLength = kXmpNameSpace.size() + 1; // need to count the null terminator
1454 const int isoNameSpaceLength = kIsoNameSpace.size() + 1; // need to count the null terminator
1455
1456 /////////////////////////////////////////////////////////////////////////////////////////////////
1457 // calculate secondary image length first, because the length will be written into the primary //
1458 // image xmp //
1459 /////////////////////////////////////////////////////////////////////////////////////////////////
1460 // XMP
1461 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
1462 // xmp_secondary_length = 2 bytes representing the length of the package +
1463 // + xmpNameSpaceLength = 29 bytes length
1464 // + length of xmp packet = xmp_secondary.size()
1465 const int xmp_secondary_length = 2 + xmpNameSpaceLength + xmp_secondary.size();
1466 // ISO
1467 gain_map_metadata iso_secondary_metadata;
1468 std::vector<uint8_t> iso_secondary_data;
1469 gain_map_metadata::gainmapMetadataFloatToFraction(metadata, &iso_secondary_metadata);
1470
1471 gain_map_metadata::encodeGainmapMetadata(&iso_secondary_metadata, iso_secondary_data);
1472
1473 // iso_secondary_length = 2 bytes representing the length of the package +
1474 // + isoNameSpaceLength = 28 bytes length
1475 // + length of iso metadata packet = iso_secondary_data.size()
1476 const int iso_secondary_length = 2 + isoNameSpaceLength + iso_secondary_data.size();
1477
1478 int secondary_image_size = 2 /* 2 bytes length of APP1 sign */ + gainmap_jpg_image_ptr->length;
1479 if (kWriteXmpMetadata) {
1480 secondary_image_size += xmp_secondary_length;
1481 }
1482 if (kWriteIso21496_1Metadata) {
1483 secondary_image_size += iso_secondary_length;
1484 }
1485
1486 // Check if EXIF package presents in the JPEG input.
1487 // If so, extract and remove the EXIF package.
1488 JpegDecoderHelper decoder;
1489 if (!decoder.parseImage(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
1490 return ERROR_JPEGR_DECODE_ERROR;
1491 }
1492 jpegr_exif_struct exif_from_jpg;
1493 exif_from_jpg.data = nullptr;
1494 exif_from_jpg.length = 0;
1495 jpegr_compressed_struct new_jpg_image;
1496 new_jpg_image.data = nullptr;
1497 new_jpg_image.length = 0;
1498 new_jpg_image.maxLength = 0;
1499 new_jpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
1500 std::unique_ptr<uint8_t[]> dest_data;
1501 if (decoder.getEXIFPos() >= 0) {
1502 if (pExif != nullptr) {
1503 ALOGE("received EXIF from outside while the primary image already contains EXIF");
1504 return ERROR_JPEGR_MULTIPLE_EXIFS_RECEIVED;
1505 }
1506 copyJpegWithoutExif(&new_jpg_image, primary_jpg_image_ptr, decoder.getEXIFPos(),
1507 decoder.getEXIFSize());
1508 dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
1509 exif_from_jpg.data = decoder.getEXIFPtr();
1510 exif_from_jpg.length = decoder.getEXIFSize();
1511 pExif = &exif_from_jpg;
1512 }
1513
1514 jr_compressed_ptr final_primary_jpg_image_ptr =
1515 new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
1516
1517 int pos = 0;
1518 // Begin primary image
1519 // Write SOI
1520 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1521 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1522
1523 // Write EXIF
1524 if (pExif != nullptr) {
1525 const int length = 2 + pExif->length;
1526 const uint8_t lengthH = ((length >> 8) & 0xff);
1527 const uint8_t lengthL = (length & 0xff);
1528 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1529 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1530 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1531 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1532 JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
1533 }
1534
1535 // Prepare and write XMP
1536 if (kWriteXmpMetadata) {
1537 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
1538 const int length = 2 + xmpNameSpaceLength + xmp_primary.size();
1539 const uint8_t lengthH = ((length >> 8) & 0xff);
1540 const uint8_t lengthL = (length & 0xff);
1541 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1542 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1543 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1544 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1545 JPEGR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos));
1546 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1547 }
1548
1549 // Write ICC
1550 if (pIcc != nullptr && icc_size > 0) {
1551 const int length = icc_size + 2;
1552 const uint8_t lengthH = ((length >> 8) & 0xff);
1553 const uint8_t lengthL = (length & 0xff);
1554 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1555 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1556 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1557 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1558 JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
1559 }
1560
1561 // Prepare and write ISO 21496-1 metadata
1562 if (kWriteIso21496_1Metadata) {
1563 const int length = 2 + isoNameSpaceLength + 4;
1564 uint8_t zero = 0;
1565 const uint8_t lengthH = ((length >> 8) & 0xff);
1566 const uint8_t lengthL = (length & 0xff);
1567 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1568 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1569 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1570 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1571 JPEGR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos));
1572 JPEGR_CHECK(Write(dest, &zero, 1, pos));
1573 JPEGR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes minimum_version: (00 00)
1574 JPEGR_CHECK(Write(dest, &zero, 1, pos));
1575 JPEGR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes writer_version: (00 00)
1576 }
1577
1578 // Prepare and write MPF
1579 {
1580 const int length = 2 + calculateMpfSize();
1581 const uint8_t lengthH = ((length >> 8) & 0xff);
1582 const uint8_t lengthL = (length & 0xff);
1583 int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
1584 // between APP2 + package size + signature
1585 // ff e2 00 58 4d 50 46 00
1586 // 2 + 2 + 4 = 8 (bytes)
1587 // and ff d8 sign of the secondary image
1588 int secondary_image_offset = primary_image_size - pos - 8;
1589 std::shared_ptr<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1590 secondary_image_size, secondary_image_offset);
1591 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1592 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1593 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1594 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1595 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
1596 }
1597
1598 // Write primary image
1599 JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
1600 final_primary_jpg_image_ptr->length - 2, pos));
1601 // Finish primary image
1602
1603 // Begin secondary image (gain map)
1604 // Write SOI
1605 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1606 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1607
1608 // Prepare and write XMP
1609 if (kWriteXmpMetadata) {
1610 const int length = xmp_secondary_length;
1611 const uint8_t lengthH = ((length >> 8) & 0xff);
1612 const uint8_t lengthL = (length & 0xff);
1613 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1614 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1615 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1616 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1617 JPEGR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos));
1618 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1619 }
1620
1621 // Prepare and write ISO 21496-1 metadata
1622 if (kWriteIso21496_1Metadata) {
1623 const int length = iso_secondary_length;
1624 const uint8_t lengthH = ((length >> 8) & 0xff);
1625 const uint8_t lengthL = (length & 0xff);
1626 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1627 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1628 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1629 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1630 JPEGR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos));
1631 JPEGR_CHECK(Write(dest, (void*)iso_secondary_data.data(), iso_secondary_data.size(), pos));
1632 }
1633
1634 // Write secondary image
1635 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1636 gainmap_jpg_image_ptr->length - 2, pos));
1637
1638 // Set back length
1639 dest->length = pos;
1640
1641 // Done!
1642 return JPEGR_NO_ERROR;
1643 }
1644
convertYuv(jr_uncompressed_ptr image,ultrahdr_color_gamut src_encoding,ultrahdr_color_gamut dest_encoding)1645 status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
1646 ultrahdr_color_gamut dest_encoding) {
1647 if (image == nullptr) {
1648 return ERROR_JPEGR_BAD_PTR;
1649 }
1650 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1651 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
1652 return ERROR_JPEGR_INVALID_COLORGAMUT;
1653 }
1654
1655 const std::array<float, 9>* coeffs_ptr = nullptr;
1656 switch (src_encoding) {
1657 case ULTRAHDR_COLORGAMUT_BT709:
1658 switch (dest_encoding) {
1659 case ULTRAHDR_COLORGAMUT_BT709:
1660 return JPEGR_NO_ERROR;
1661 case ULTRAHDR_COLORGAMUT_P3:
1662 coeffs_ptr = &kYuvBt709ToBt601;
1663 break;
1664 case ULTRAHDR_COLORGAMUT_BT2100:
1665 coeffs_ptr = &kYuvBt709ToBt2100;
1666 break;
1667 default:
1668 // Should be impossible to hit after input validation
1669 return ERROR_JPEGR_INVALID_COLORGAMUT;
1670 }
1671 break;
1672 case ULTRAHDR_COLORGAMUT_P3:
1673 switch (dest_encoding) {
1674 case ULTRAHDR_COLORGAMUT_BT709:
1675 coeffs_ptr = &kYuvBt601ToBt709;
1676 break;
1677 case ULTRAHDR_COLORGAMUT_P3:
1678 return JPEGR_NO_ERROR;
1679 case ULTRAHDR_COLORGAMUT_BT2100:
1680 coeffs_ptr = &kYuvBt601ToBt2100;
1681 break;
1682 default:
1683 // Should be impossible to hit after input validation
1684 return ERROR_JPEGR_INVALID_COLORGAMUT;
1685 }
1686 break;
1687 case ULTRAHDR_COLORGAMUT_BT2100:
1688 switch (dest_encoding) {
1689 case ULTRAHDR_COLORGAMUT_BT709:
1690 coeffs_ptr = &kYuvBt2100ToBt709;
1691 break;
1692 case ULTRAHDR_COLORGAMUT_P3:
1693 coeffs_ptr = &kYuvBt2100ToBt601;
1694 break;
1695 case ULTRAHDR_COLORGAMUT_BT2100:
1696 return JPEGR_NO_ERROR;
1697 default:
1698 // Should be impossible to hit after input validation
1699 return ERROR_JPEGR_INVALID_COLORGAMUT;
1700 }
1701 break;
1702 default:
1703 // Should be impossible to hit after input validation
1704 return ERROR_JPEGR_INVALID_COLORGAMUT;
1705 }
1706
1707 if (coeffs_ptr == nullptr) {
1708 // Should be impossible to hit after input validation
1709 return ERROR_JPEGR_INVALID_COLORGAMUT;
1710 }
1711
1712 transformYuv420(image, *coeffs_ptr);
1713 return JPEGR_NO_ERROR;
1714 }
1715
1716 namespace {
ReinhardMap(float y_hdr,float headroom)1717 float ReinhardMap(float y_hdr, float headroom) {
1718 float out = 1.0 + y_hdr / (headroom * headroom);
1719 out /= 1.0 + y_hdr;
1720 return out * y_hdr;
1721 }
1722 } // namespace
1723
hlgGlobalTonemap(const std::array<float,3> & rgb_in,float headroom)1724 GlobalTonemapOutputs hlgGlobalTonemap(const std::array<float, 3>& rgb_in, float headroom) {
1725 constexpr float kRgbToYBt2020[3] = {0.2627f, 0.6780f, 0.0593f};
1726 constexpr float kOotfGamma = 1.2f;
1727
1728 // Apply OOTF and Scale to Headroom to get HDR values that are referenced to
1729 // SDR white. The range [0.0, 1.0] is linearly stretched to [0.0, headroom]
1730 // after the OOTF.
1731 const float y_in =
1732 rgb_in[0] * kRgbToYBt2020[0] + rgb_in[1] * kRgbToYBt2020[1] + rgb_in[2] * kRgbToYBt2020[2];
1733 const float y_ootf_div_y_in = std::pow(y_in, kOotfGamma - 1.0f);
1734 std::array<float, 3> rgb_hdr;
1735 std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(),
1736 [&](float x) { return x * headroom * y_ootf_div_y_in; });
1737
1738 // Apply a tone mapping to compress the range [0, headroom] to [0, 1] by
1739 // keeping the shadows the same and crushing the highlights.
1740 float max_hdr = *std::max_element(rgb_hdr.begin(), rgb_hdr.end());
1741 float max_sdr = ReinhardMap(max_hdr, headroom);
1742 std::array<float, 3> rgb_sdr;
1743 std::transform(rgb_hdr.begin(), rgb_hdr.end(), rgb_sdr.begin(), [&](float x) {
1744 if (x > 0.0f) {
1745 return x * max_sdr / max_hdr;
1746 }
1747 return 0.0f;
1748 });
1749
1750 GlobalTonemapOutputs tonemap_outputs;
1751 tonemap_outputs.rgb_out = rgb_sdr;
1752 tonemap_outputs.y_hdr = max_hdr;
1753 tonemap_outputs.y_sdr = max_sdr;
1754 return tonemap_outputs;
1755 }
1756
ScaleTo8Bit(float value)1757 uint8_t ScaleTo8Bit(float value) {
1758 constexpr float kMaxValFloat = 255.0f;
1759 constexpr int kMaxValInt = 255;
1760 return std::clamp(static_cast<int>(std::round(value * kMaxValFloat)), 0, kMaxValInt);
1761 }
1762
toneMap(jr_uncompressed_ptr src,jr_uncompressed_ptr dest,ultrahdr_transfer_function hdr_tf)1763 status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest,
1764 ultrahdr_transfer_function hdr_tf) {
1765 if (src == nullptr || dest == nullptr) {
1766 return ERROR_JPEGR_BAD_PTR;
1767 }
1768 if (src->width != dest->width || src->height != dest->height) {
1769 return ERROR_JPEGR_RESOLUTION_MISMATCH;
1770 }
1771
1772 dest->colorGamut = ULTRAHDR_COLORGAMUT_P3;
1773
1774 size_t height = src->height;
1775
1776 ColorTransformFn hdrYuvToRgbFn = nullptr;
1777 switch (src->colorGamut) {
1778 case ULTRAHDR_COLORGAMUT_BT709:
1779 hdrYuvToRgbFn = srgbYuvToRgb;
1780 break;
1781 case ULTRAHDR_COLORGAMUT_P3:
1782 hdrYuvToRgbFn = p3YuvToRgb;
1783 break;
1784 case ULTRAHDR_COLORGAMUT_BT2100:
1785 hdrYuvToRgbFn = bt2100YuvToRgb;
1786 break;
1787 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
1788 // Should be impossible to hit after input validation.
1789 return ERROR_JPEGR_INVALID_COLORGAMUT;
1790 }
1791
1792 ColorTransformFn hdrInvOetf = nullptr;
1793 switch (hdr_tf) {
1794 case ULTRAHDR_TF_HLG:
1795 #if USE_HLG_INVOETF_LUT
1796 hdrInvOetf = hlgInvOetfLUT;
1797 #else
1798 hdrInvOetf = hlgInvOetf;
1799 #endif
1800 break;
1801 case ULTRAHDR_TF_PQ:
1802 #if USE_PQ_INVOETF_LUT
1803 hdrInvOetf = pqInvOetfLUT;
1804 #else
1805 hdrInvOetf = pqInvOetf;
1806 #endif
1807 break;
1808 default:
1809 // Should be impossible to hit after input validation.
1810 return ERROR_JPEGR_INVALID_TRANS_FUNC;
1811 }
1812
1813 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(dest->colorGamut, src->colorGamut);
1814
1815 size_t luma_stride = dest->luma_stride == 0 ? dest->width : dest->luma_stride;
1816 size_t chroma_stride = dest->chroma_stride == 0 ? luma_stride / 2 : dest->chroma_stride;
1817 if (dest->chroma_data == nullptr) {
1818 uint8_t* data = reinterpret_cast<uint8_t*>(dest->data);
1819 dest->chroma_data = data + luma_stride * dest->height;
1820 }
1821 uint8_t* luma_data = reinterpret_cast<uint8_t*>(dest->data);
1822 uint8_t* chroma_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1823
1824 const int threads = (std::min)(GetCPUCoreCount(), 4);
1825 size_t rowStep = threads == 1 ? height : kJobSzInRows;
1826 JobQueue jobQueue;
1827 std::function<void()> toneMapInternal;
1828
1829 toneMapInternal = [src, dest, luma_data, chroma_data, hdrInvOetf, hdrGamutConversionFn,
1830 hdrYuvToRgbFn, luma_stride, chroma_stride, &jobQueue]() -> void {
1831 size_t rowStart, rowEnd;
1832 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1833 for (size_t y = rowStart; y < rowEnd; y += 2) {
1834 for (size_t x = 0; x < dest->width; x += 2) {
1835 // We assume the input is P010, and output is YUV420
1836 float sdr_u_gamma = 0.0f;
1837 float sdr_v_gamma = 0.0f;
1838 for (int i = 0; i < 2; i++) {
1839 for (int j = 0; j < 2; j++) {
1840 Color hdr_yuv_gamma = getP010Pixel(src, x + j, y + i);
1841 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
1842
1843 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
1844
1845 GlobalTonemapOutputs tonemap_outputs =
1846 hlgGlobalTonemap({hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, kHlgHeadroom);
1847 Color sdr_rgb_linear_bt2100 = {{{tonemap_outputs.rgb_out[0],
1848 tonemap_outputs.rgb_out[1],
1849 tonemap_outputs.rgb_out[2]}}};
1850 Color sdr_rgb = hdrGamutConversionFn(sdr_rgb_linear_bt2100);
1851
1852 // Hard clip out-of-gamut values;
1853 sdr_rgb = clampPixelFloat(sdr_rgb);
1854
1855 Color sdr_rgb_gamma = srgbOetf(sdr_rgb);
1856 Color sdr_yuv_gamma = srgbRgbToYuv(sdr_rgb_gamma);
1857
1858 sdr_yuv_gamma += {{{0.0f, 0.5f, 0.5f}}};
1859
1860 size_t out_y_idx = (y + i) * luma_stride + x + j;
1861 luma_data[out_y_idx] = ScaleTo8Bit(sdr_yuv_gamma.y);
1862
1863 sdr_u_gamma += sdr_yuv_gamma.u * 0.25f;
1864 sdr_v_gamma += sdr_yuv_gamma.v * 0.25f;
1865 }
1866 }
1867 size_t out_chroma_idx = x / 2 + (y / 2) * chroma_stride;
1868 size_t offset_cr = chroma_stride * (dest->height / 2);
1869 chroma_data[out_chroma_idx] = ScaleTo8Bit(sdr_u_gamma);
1870 chroma_data[out_chroma_idx + offset_cr] = ScaleTo8Bit(sdr_v_gamma);
1871 }
1872 }
1873 }
1874 };
1875
1876 // tone map
1877 std::vector<std::thread> workers;
1878 for (int th = 0; th < threads - 1; th++) {
1879 workers.push_back(std::thread(toneMapInternal));
1880 }
1881
1882 rowStep = (threads == 1 ? height : kJobSzInRows) / kMapDimensionScaleFactor;
1883 for (size_t rowStart = 0; rowStart < height;) {
1884 size_t rowEnd = (std::min)(rowStart + rowStep, height);
1885 jobQueue.enqueueJob(rowStart, rowEnd);
1886 rowStart = rowEnd;
1887 }
1888 jobQueue.markQueueForEnd();
1889 toneMapInternal();
1890 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
1891
1892 return JPEGR_NO_ERROR;
1893 }
1894
1895 } // namespace ultrahdr
1896