• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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