xref: /aosp_15_r20/external/libultrahdr/lib/src/jpegr.cpp (revision 89a0ef05262152531a00a15832a2d3b1e3990773)
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/editorhelper.h"
31 #include "ultrahdr/gainmapmetadata.h"
32 #include "ultrahdr/ultrahdrcommon.h"
33 #include "ultrahdr/jpegr.h"
34 #include "ultrahdr/icc.h"
35 #include "ultrahdr/multipictureformat.h"
36 
37 #include "image_io/base/data_segment_data_source.h"
38 #include "image_io/jpeg/jpeg_info.h"
39 #include "image_io/jpeg/jpeg_info_builder.h"
40 #include "image_io/jpeg/jpeg_marker.h"
41 #include "image_io/jpeg/jpeg_scanner.h"
42 
43 using namespace std;
44 using namespace photos_editing_formats::image_io;
45 
46 namespace ultrahdr {
47 
48 #ifdef UHDR_ENABLE_GLES
49 uhdr_error_info_t applyGainMapGLES(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img,
50                                    uhdr_gainmap_metadata_ext_t* gainmap_metadata,
51                                    uhdr_color_transfer_t output_ct, float display_boost,
52                                    uhdr_raw_image_t* dest, uhdr_opengl_ctxt_t* opengl_ctxt);
53 #endif
54 
55 // Gain map metadata
56 static const bool kWriteXmpMetadata = true;
57 static const bool kWriteIso21496_1Metadata = false;
58 
59 static const string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
60 static const string kIsoNameSpace = "urn:iso:std:iso:ts:21496:-1";
61 
62 static_assert(kWriteXmpMetadata || kWriteIso21496_1Metadata,
63               "Must write gain map metadata in XMP format, or iso 21496-1 format, or both.");
64 
65 class JobQueue {
66  public:
67   bool dequeueJob(unsigned int& rowStart, unsigned int& rowEnd);
68   void enqueueJob(unsigned int rowStart, unsigned int rowEnd);
69   void markQueueForEnd();
70   void reset();
71 
72  private:
73   bool mQueuedAllJobs = false;
74   std::deque<std::tuple<unsigned int, unsigned int>> mJobs;
75   std::mutex mMutex;
76   std::condition_variable mCv;
77 };
78 
dequeueJob(unsigned int & rowStart,unsigned int & rowEnd)79 bool JobQueue::dequeueJob(unsigned int& rowStart, unsigned int& rowEnd) {
80   std::unique_lock<std::mutex> lock{mMutex};
81   while (true) {
82     if (mJobs.empty()) {
83       if (mQueuedAllJobs) {
84         return false;
85       } else {
86         mCv.wait_for(lock, std::chrono::milliseconds(100));
87       }
88     } else {
89       auto it = mJobs.begin();
90       rowStart = std::get<0>(*it);
91       rowEnd = std::get<1>(*it);
92       mJobs.erase(it);
93       return true;
94     }
95   }
96   return false;
97 }
98 
enqueueJob(unsigned int rowStart,unsigned int rowEnd)99 void JobQueue::enqueueJob(unsigned int rowStart, unsigned int rowEnd) {
100   std::unique_lock<std::mutex> lock{mMutex};
101   mJobs.push_back(std::make_tuple(rowStart, rowEnd));
102   lock.unlock();
103   mCv.notify_one();
104 }
105 
markQueueForEnd()106 void JobQueue::markQueueForEnd() {
107   std::unique_lock<std::mutex> lock{mMutex};
108   mQueuedAllJobs = true;
109   lock.unlock();
110   mCv.notify_all();
111 }
112 
reset()113 void JobQueue::reset() {
114   std::unique_lock<std::mutex> lock{mMutex};
115   mJobs.clear();
116   mQueuedAllJobs = false;
117 }
118 
119 /*
120  * MessageWriter implementation for ALOG functions.
121  */
122 class AlogMessageWriter : public MessageWriter {
123  public:
WriteMessage(const Message & message)124   void WriteMessage(const Message& message) override {
125     std::string log = GetFormattedMessage(message);
126     ALOGD("%s", log.c_str());
127   }
128 };
129 
GetCPUCoreCount()130 unsigned int GetCPUCoreCount() { return (std::max)(1u, std::thread::hardware_concurrency()); }
131 
JpegR(void * uhdrGLESCtxt,int mapDimensionScaleFactor,int mapCompressQuality,bool useMultiChannelGainMap,float gamma,uhdr_enc_preset_t preset,float minContentBoost,float maxContentBoost,float targetDispPeakBrightness)132 JpegR::JpegR(void* uhdrGLESCtxt, int mapDimensionScaleFactor, int mapCompressQuality,
133              bool useMultiChannelGainMap, float gamma, uhdr_enc_preset_t preset,
134              float minContentBoost, float maxContentBoost, float targetDispPeakBrightness) {
135   mUhdrGLESCtxt = uhdrGLESCtxt;
136   mMapDimensionScaleFactor = mapDimensionScaleFactor;
137   mMapCompressQuality = mapCompressQuality;
138   mUseMultiChannelGainMap = useMultiChannelGainMap;
139   mGamma = gamma;
140   mEncPreset = preset;
141   mMinContentBoost = minContentBoost;
142   mMaxContentBoost = maxContentBoost;
143   mTargetDispPeakBrightness = targetDispPeakBrightness;
144 }
145 
146 /*
147  * Helper function copies the JPEG image from without EXIF.
148  *
149  * @param pDest destination of the data to be written.
150  * @param pSource source of data being written.
151  * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
152  *                 (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
153  * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
154  */
copyJpegWithoutExif(uhdr_compressed_image_t * pDest,uhdr_compressed_image_t * pSource,size_t exif_pos,size_t exif_size)155 static void copyJpegWithoutExif(uhdr_compressed_image_t* pDest, uhdr_compressed_image_t* pSource,
156                                 size_t exif_pos, size_t exif_size) {
157   const size_t exif_offset = 4;  // exif_pos has 4 bytes offset to the FF sign
158   pDest->data_sz = pSource->data_sz - exif_size - exif_offset;
159   pDest->data = new uint8_t[pDest->data_sz];
160   pDest->capacity = pDest->data_sz;
161   pDest->cg = pSource->cg;
162   pDest->ct = pSource->ct;
163   pDest->range = pSource->range;
164   memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
165   memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
166          (uint8_t*)pSource->data + exif_pos + exif_size, pSource->data_sz - exif_pos - exif_size);
167 }
168 
169 /* Encode API-0 */
encodeJPEGR(uhdr_raw_image_t * hdr_intent,uhdr_compressed_image_t * dest,int quality,uhdr_mem_block_t * exif)170 uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest,
171                                      int quality, uhdr_mem_block_t* exif) {
172   uhdr_img_fmt_t sdr_intent_fmt;
173   if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
174     sdr_intent_fmt = UHDR_IMG_FMT_12bppYCbCr420;
175   } else if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
176     sdr_intent_fmt = UHDR_IMG_FMT_24bppYCbCr444;
177   } else if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
178              hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
179     sdr_intent_fmt = UHDR_IMG_FMT_32bppRGBA8888;
180   } else {
181     uhdr_error_info_t status;
182     status.error_code = UHDR_CODEC_INVALID_PARAM;
183     status.has_detail = 1;
184     snprintf(status.detail, sizeof status.detail, "unsupported hdr intent color format %d",
185              hdr_intent->fmt);
186     return status;
187   }
188   std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent = std::make_unique<uhdr_raw_image_ext_t>(
189       sdr_intent_fmt, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, hdr_intent->w,
190       hdr_intent->h, 64);
191 
192   // tone map
193   UHDR_ERR_CHECK(toneMap(hdr_intent, sdr_intent.get()));
194 
195   // If hdr intent is tonemapped internally, it is observed from quality pov,
196   // generateGainMapOnePass() is sufficient
197   mEncPreset = UHDR_USAGE_REALTIME;  // overriding the config option
198 
199   // generate gain map
200   uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
201   std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
202   UHDR_ERR_CHECK(generateGainMap(sdr_intent.get(), hdr_intent, &metadata, gainmap,
203                                  /* sdr_is_601 */ false,
204                                  /* use_luminance */ false));
205 
206   // compress gain map
207   JpegEncoderHelper jpeg_enc_obj_gm;
208   UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
209   uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
210 
211   std::shared_ptr<DataStruct> icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg);
212 
213   // compress sdr image
214   std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext;
215   uhdr_raw_image_t* sdr_intent_yuv = sdr_intent.get();
216   if (isPixelFormatRgb(sdr_intent->fmt)) {
217 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
218     sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent.get());
219 #else
220     sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent.get());
221 #endif
222     sdr_intent_yuv = sdr_intent_yuv_ext.get();
223   }
224 
225   JpegEncoderHelper jpeg_enc_obj_sdr;
226   UHDR_ERR_CHECK(
227       jpeg_enc_obj_sdr.compressImage(sdr_intent_yuv, quality, icc->getData(), icc->getLength()));
228   uhdr_compressed_image_t sdr_intent_compressed = jpeg_enc_obj_sdr.getCompressedImage();
229   sdr_intent_compressed.cg = sdr_intent_yuv->cg;
230 
231   // append gain map, no ICC since JPEG encode already did it
232   UHDR_ERR_CHECK(appendGainMap(&sdr_intent_compressed, &gainmap_compressed, exif, /* icc */ nullptr,
233                                /* icc size */ 0, &metadata, dest));
234   return g_no_error;
235 }
236 
237 /* Encode API-1 */
encodeJPEGR(uhdr_raw_image_t * hdr_intent,uhdr_raw_image_t * sdr_intent,uhdr_compressed_image_t * dest,int quality,uhdr_mem_block_t * exif)238 uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
239                                      uhdr_compressed_image_t* dest, int quality,
240                                      uhdr_mem_block_t* exif) {
241   // generate gain map
242   uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
243   std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
244   UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap));
245 
246   // compress gain map
247   JpegEncoderHelper jpeg_enc_obj_gm;
248   UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
249   uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
250 
251   std::shared_ptr<DataStruct> icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg);
252 
253   std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext;
254   uhdr_raw_image_t* sdr_intent_yuv = sdr_intent;
255   if (isPixelFormatRgb(sdr_intent->fmt)) {
256 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
257     sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent);
258 #else
259     sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent);
260 #endif
261     sdr_intent_yuv = sdr_intent_yuv_ext.get();
262   }
263 
264   // convert to bt601 YUV encoding for JPEG encode
265 #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
266   UHDR_ERR_CHECK(convertYuv_neon(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3));
267 #else
268   UHDR_ERR_CHECK(convertYuv(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3));
269 #endif
270 
271   // compress sdr image
272   JpegEncoderHelper jpeg_enc_obj_sdr;
273   UHDR_ERR_CHECK(
274       jpeg_enc_obj_sdr.compressImage(sdr_intent_yuv, quality, icc->getData(), icc->getLength()));
275   uhdr_compressed_image_t sdr_intent_compressed = jpeg_enc_obj_sdr.getCompressedImage();
276   sdr_intent_compressed.cg = sdr_intent_yuv->cg;
277 
278   // append gain map, no ICC since JPEG encode already did it
279   UHDR_ERR_CHECK(appendGainMap(&sdr_intent_compressed, &gainmap_compressed, exif, /* icc */ nullptr,
280                                /* icc size */ 0, &metadata, dest));
281   return g_no_error;
282 }
283 
284 /* Encode API-2 */
encodeJPEGR(uhdr_raw_image_t * hdr_intent,uhdr_raw_image_t * sdr_intent,uhdr_compressed_image_t * sdr_intent_compressed,uhdr_compressed_image_t * dest)285 uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
286                                      uhdr_compressed_image_t* sdr_intent_compressed,
287                                      uhdr_compressed_image_t* dest) {
288   JpegDecoderHelper jpeg_dec_obj_sdr;
289   UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(sdr_intent_compressed->data,
290                                                   sdr_intent_compressed->data_sz, PARSE_STREAM));
291   if (hdr_intent->w != jpeg_dec_obj_sdr.getDecompressedImageWidth() ||
292       hdr_intent->h != jpeg_dec_obj_sdr.getDecompressedImageHeight()) {
293     uhdr_error_info_t status;
294     status.error_code = UHDR_CODEC_INVALID_PARAM;
295     status.has_detail = 1;
296     snprintf(
297         status.detail, sizeof status.detail,
298         "sdr intent resolution %dx%d and compressed image sdr intent resolution %dx%d do not match",
299         sdr_intent->w, sdr_intent->h, (int)jpeg_dec_obj_sdr.getDecompressedImageWidth(),
300         (int)jpeg_dec_obj_sdr.getDecompressedImageHeight());
301     return status;
302   }
303 
304   // generate gain map
305   uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
306   std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
307   UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap));
308 
309   // compress gain map
310   JpegEncoderHelper jpeg_enc_obj_gm;
311   UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
312   uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
313 
314   return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest);
315 }
316 
317 /* Encode API-3 */
encodeJPEGR(uhdr_raw_image_t * hdr_intent,uhdr_compressed_image_t * sdr_intent_compressed,uhdr_compressed_image_t * dest)318 uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent,
319                                      uhdr_compressed_image_t* sdr_intent_compressed,
320                                      uhdr_compressed_image_t* dest) {
321   // decode input jpeg, gamut is going to be bt601.
322   JpegDecoderHelper jpeg_dec_obj_sdr;
323   UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(sdr_intent_compressed->data,
324                                                   sdr_intent_compressed->data_sz));
325 
326   uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage();
327   if (jpeg_dec_obj_sdr.getICCSize() > 0) {
328     uhdr_color_gamut_t cg =
329         IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize());
330     if (cg == UHDR_CG_UNSPECIFIED ||
331         (sdr_intent_compressed->cg != UHDR_CG_UNSPECIFIED && sdr_intent_compressed->cg != cg)) {
332       uhdr_error_info_t status;
333       status.error_code = UHDR_CODEC_INVALID_PARAM;
334       status.has_detail = 1;
335       snprintf(status.detail, sizeof status.detail,
336                "configured color gamut %d does not match with color gamut specified in icc box %d",
337                sdr_intent_compressed->cg, cg);
338       return status;
339     }
340     sdr_intent.cg = cg;
341   } else {
342     if (sdr_intent_compressed->cg <= UHDR_CG_UNSPECIFIED ||
343         sdr_intent_compressed->cg > UHDR_CG_BT_2100) {
344       uhdr_error_info_t status;
345       status.error_code = UHDR_CODEC_INVALID_PARAM;
346       status.has_detail = 1;
347       snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d",
348                sdr_intent_compressed->cg);
349       return status;
350     }
351     sdr_intent.cg = sdr_intent_compressed->cg;
352   }
353 
354   if (hdr_intent->w != sdr_intent.w || hdr_intent->h != sdr_intent.h) {
355     uhdr_error_info_t status;
356     status.error_code = UHDR_CODEC_INVALID_PARAM;
357     status.has_detail = 1;
358     snprintf(status.detail, sizeof status.detail,
359              "sdr intent resolution %dx%d and hdr intent resolution %dx%d do not match",
360              sdr_intent.w, sdr_intent.h, hdr_intent->w, hdr_intent->h);
361     return status;
362   }
363 
364   // generate gain map
365   uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
366   std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
367   UHDR_ERR_CHECK(
368       generateGainMap(&sdr_intent, hdr_intent, &metadata, gainmap, true /* sdr_is_601 */));
369 
370   // compress gain map
371   JpegEncoderHelper jpeg_enc_obj_gm;
372   UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
373   uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
374 
375   return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest);
376 }
377 
378 /* Encode API-4 */
encodeJPEGR(uhdr_compressed_image_t * base_img_compressed,uhdr_compressed_image_t * gainmap_img_compressed,uhdr_gainmap_metadata_ext_t * metadata,uhdr_compressed_image_t * dest)379 uhdr_error_info_t JpegR::encodeJPEGR(uhdr_compressed_image_t* base_img_compressed,
380                                      uhdr_compressed_image_t* gainmap_img_compressed,
381                                      uhdr_gainmap_metadata_ext_t* metadata,
382                                      uhdr_compressed_image_t* dest) {
383   // We just want to check if ICC is present, so don't do a full decode. Note,
384   // this doesn't verify that the ICC is valid.
385   JpegDecoderHelper decoder;
386   UHDR_ERR_CHECK(decoder.parseImage(base_img_compressed->data, base_img_compressed->data_sz));
387 
388   // Add ICC if not already present.
389   if (decoder.getICCSize() > 0) {
390     UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr,
391                                  /* icc */ nullptr, /* icc size */ 0, metadata, dest));
392   } else {
393     if (base_img_compressed->cg <= UHDR_CG_UNSPECIFIED ||
394         base_img_compressed->cg > UHDR_CG_BT_2100) {
395       uhdr_error_info_t status;
396       status.error_code = UHDR_CODEC_INVALID_PARAM;
397       status.has_detail = 1;
398       snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d",
399                base_img_compressed->cg);
400       return status;
401     }
402     std::shared_ptr<DataStruct> newIcc =
403         IccHelper::writeIccProfile(UHDR_CT_SRGB, base_img_compressed->cg);
404     UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr,
405                                  newIcc->getData(), newIcc->getLength(), metadata, dest));
406   }
407 
408   return g_no_error;
409 }
410 
convertYuv(uhdr_raw_image_t * image,uhdr_color_gamut_t src_encoding,uhdr_color_gamut_t dst_encoding)411 uhdr_error_info_t JpegR::convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t src_encoding,
412                                     uhdr_color_gamut_t dst_encoding) {
413   const std::array<float, 9>* coeffs_ptr = nullptr;
414   uhdr_error_info_t status = g_no_error;
415 
416   switch (src_encoding) {
417     case UHDR_CG_BT_709:
418       switch (dst_encoding) {
419         case UHDR_CG_BT_709:
420           return status;
421         case UHDR_CG_DISPLAY_P3:
422           coeffs_ptr = &kYuvBt709ToBt601;
423           break;
424         case UHDR_CG_BT_2100:
425           coeffs_ptr = &kYuvBt709ToBt2100;
426           break;
427         default:
428           status.error_code = UHDR_CODEC_INVALID_PARAM;
429           status.has_detail = 1;
430           snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d",
431                    dst_encoding);
432           return status;
433       }
434       break;
435     case UHDR_CG_DISPLAY_P3:
436       switch (dst_encoding) {
437         case UHDR_CG_BT_709:
438           coeffs_ptr = &kYuvBt601ToBt709;
439           break;
440         case UHDR_CG_DISPLAY_P3:
441           return status;
442         case UHDR_CG_BT_2100:
443           coeffs_ptr = &kYuvBt601ToBt2100;
444           break;
445         default:
446           status.error_code = UHDR_CODEC_INVALID_PARAM;
447           status.has_detail = 1;
448           snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d",
449                    dst_encoding);
450           return status;
451       }
452       break;
453     case UHDR_CG_BT_2100:
454       switch (dst_encoding) {
455         case UHDR_CG_BT_709:
456           coeffs_ptr = &kYuvBt2100ToBt709;
457           break;
458         case UHDR_CG_DISPLAY_P3:
459           coeffs_ptr = &kYuvBt2100ToBt601;
460           break;
461         case UHDR_CG_BT_2100:
462           return status;
463         default:
464           status.error_code = UHDR_CODEC_INVALID_PARAM;
465           status.has_detail = 1;
466           snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d",
467                    dst_encoding);
468           return status;
469       }
470       break;
471     default:
472       status.error_code = UHDR_CODEC_INVALID_PARAM;
473       status.has_detail = 1;
474       snprintf(status.detail, sizeof status.detail, "Unrecognized src color gamut %d",
475                src_encoding);
476       return status;
477   }
478 
479   if (image->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
480     transformYuv420(image, *coeffs_ptr);
481   } else if (image->fmt == UHDR_IMG_FMT_24bppYCbCr444) {
482     transformYuv444(image, *coeffs_ptr);
483   } else {
484     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
485     status.has_detail = 1;
486     snprintf(status.detail, sizeof status.detail,
487              "No implementation available for performing gamut conversion for color format %d",
488              image->fmt);
489     return status;
490   }
491 
492   return status;
493 }
494 
compressGainMap(uhdr_raw_image_t * gainmap_img,JpegEncoderHelper * jpeg_enc_obj)495 uhdr_error_info_t JpegR::compressGainMap(uhdr_raw_image_t* gainmap_img,
496                                          JpegEncoderHelper* jpeg_enc_obj) {
497   return jpeg_enc_obj->compressImage(gainmap_img, mMapCompressQuality, nullptr, 0);
498 }
499 
generateGainMap(uhdr_raw_image_t * sdr_intent,uhdr_raw_image_t * hdr_intent,uhdr_gainmap_metadata_ext_t * gainmap_metadata,std::unique_ptr<uhdr_raw_image_ext_t> & gainmap_img,bool sdr_is_601,bool use_luminance)500 uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* hdr_intent,
501                                          uhdr_gainmap_metadata_ext_t* gainmap_metadata,
502                                          std::unique_ptr<uhdr_raw_image_ext_t>& gainmap_img,
503                                          bool sdr_is_601, bool use_luminance) {
504   uhdr_error_info_t status = g_no_error;
505 
506   if (sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444 &&
507       sdr_intent->fmt != UHDR_IMG_FMT_16bppYCbCr422 &&
508       sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420 &&
509       sdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA8888) {
510     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
511     status.has_detail = 1;
512     snprintf(status.detail, sizeof status.detail,
513              "generate gainmap method expects sdr intent color format to be one of "
514              "{UHDR_IMG_FMT_24bppYCbCr444, UHDR_IMG_FMT_16bppYCbCr422, "
515              "UHDR_IMG_FMT_12bppYCbCr420, UHDR_IMG_FMT_32bppRGBA8888}. Received %d",
516              sdr_intent->fmt);
517     return status;
518   }
519   if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 &&
520       hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 &&
521       hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 &&
522       hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) {
523     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
524     status.has_detail = 1;
525     snprintf(status.detail, sizeof status.detail,
526              "generate gainmap method expects hdr intent color format to be one of "
527              "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, "
528              "UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d",
529              hdr_intent->fmt);
530     return status;
531   }
532 
533   /*if (mUseMultiChannelGainMap) {
534     if (!kWriteIso21496_1Metadata || kWriteXmpMetadata) {
535       status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
536       status.has_detail = 1;
537       snprintf(status.detail, sizeof status.detail,
538                "Multi-channel gain map is only supported for ISO 21496-1 metadata");
539       return status;
540     }
541   }*/
542 
543   ColorTransformFn hdrInvOetf = getInverseOetfFn(hdr_intent->ct);
544   if (hdrInvOetf == nullptr) {
545     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
546     status.has_detail = 1;
547     snprintf(status.detail, sizeof status.detail,
548              "No implementation available for converting transfer characteristics %d to linear",
549              hdr_intent->ct);
550     return status;
551   }
552 
553   LuminanceFn hdrLuminanceFn = getLuminanceFn(hdr_intent->cg);
554   if (hdrLuminanceFn == nullptr) {
555     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
556     status.has_detail = 1;
557     snprintf(status.detail, sizeof status.detail,
558              "No implementation available for calculating luminance for color gamut %d",
559              hdr_intent->cg);
560     return status;
561   }
562 
563   SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct);
564   if (hdrOotfFn == nullptr) {
565     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
566     status.has_detail = 1;
567     snprintf(status.detail, sizeof status.detail,
568              "No implementation available for calculating Ootf for color transfer %d",
569              hdr_intent->ct);
570     return status;
571   }
572 
573   float hdr_white_nits = getReferenceDisplayPeakLuminanceInNits(hdr_intent->ct);
574   if (hdr_white_nits == -1.0f) {
575     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
576     status.has_detail = 1;
577     snprintf(status.detail, sizeof status.detail,
578              "received invalid peak brightness %f nits for hdr reference display with color "
579              "transfer %d ",
580              hdr_white_nits, hdr_intent->ct);
581     return status;
582   }
583 
584   ColorTransformFn hdrGamutConversionFn = getGamutConversionFn(sdr_intent->cg, hdr_intent->cg);
585   if (hdrGamutConversionFn == nullptr) {
586     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
587     status.has_detail = 1;
588     snprintf(status.detail, sizeof status.detail,
589              "No implementation available for gamut conversion from %d to %d", hdr_intent->cg,
590              sdr_intent->cg);
591     return status;
592   }
593 
594   ColorTransformFn sdrYuvToRgbFn = getYuvToRgbFn(sdr_intent->cg);
595   if (sdrYuvToRgbFn == nullptr) {
596     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
597     status.has_detail = 1;
598     snprintf(status.detail, sizeof status.detail,
599              "No implementation available for converting yuv to rgb for color gamut %d",
600              sdr_intent->cg);
601     return status;
602   }
603 
604   ColorTransformFn hdrYuvToRgbFn = getYuvToRgbFn(hdr_intent->cg);
605   if (hdrYuvToRgbFn == nullptr) {
606     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
607     status.has_detail = 1;
608     snprintf(status.detail, sizeof status.detail,
609              "No implementation available for converting yuv to rgb for color gamut %d",
610              hdr_intent->cg);
611     return status;
612   }
613 
614   LuminanceFn luminanceFn = getLuminanceFn(sdr_intent->cg);
615   if (luminanceFn == nullptr) {
616     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
617     status.has_detail = 1;
618     snprintf(status.detail, sizeof status.detail,
619              "No implementation available for computing luminance for color gamut %d",
620              sdr_intent->cg);
621     return status;
622   }
623 
624   SamplePixelFn sdr_sample_pixel_fn = getSamplePixelFn(sdr_intent->fmt);
625   if (sdr_sample_pixel_fn == nullptr) {
626     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
627     status.has_detail = 1;
628     snprintf(status.detail, sizeof status.detail,
629              "No implementation available for reading pixels for color format %d", sdr_intent->fmt);
630     return status;
631   }
632 
633   SamplePixelFn hdr_sample_pixel_fn = getSamplePixelFn(hdr_intent->fmt);
634   if (hdr_sample_pixel_fn == nullptr) {
635     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
636     status.has_detail = 1;
637     snprintf(status.detail, sizeof status.detail,
638              "No implementation available for reading pixels for color format %d", hdr_intent->fmt);
639     return status;
640   }
641 
642   if (sdr_is_601) {
643     sdrYuvToRgbFn = p3YuvToRgb;
644   }
645 
646   unsigned int image_width = sdr_intent->w;
647   unsigned int image_height = sdr_intent->h;
648   unsigned int map_width = image_width / mMapDimensionScaleFactor;
649   unsigned int map_height = image_height / mMapDimensionScaleFactor;
650   if (map_width == 0 || map_height == 0) {
651     int scaleFactor = (std::min)(image_width, image_height);
652     scaleFactor = (scaleFactor >= DCTSIZE) ? (scaleFactor / DCTSIZE) : 1;
653     ALOGW(
654         "configured gainmap scale factor is resulting in gainmap width and/or height to be zero, "
655         "image width %u, image height %u, scale factor %d. Modifying gainmap scale factor to %d ",
656         image_width, image_height, mMapDimensionScaleFactor, scaleFactor);
657     setMapDimensionScaleFactor(scaleFactor);
658     map_width = image_width / mMapDimensionScaleFactor;
659     map_height = image_height / mMapDimensionScaleFactor;
660   }
661 
662   gainmap_img = std::make_unique<uhdr_raw_image_ext_t>(
663       mUseMultiChannelGainMap ? UHDR_IMG_FMT_24bppRGB888 : UHDR_IMG_FMT_8bppYCbCr400,
664       UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, map_width, map_height, 64);
665   uhdr_raw_image_ext_t* dest = gainmap_img.get();
666 
667   auto generateGainMapOnePass = [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_height,
668                                  hdrInvOetf, hdrLuminanceFn, hdrOotfFn, hdrGamutConversionFn,
669                                  luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, sdr_sample_pixel_fn,
670                                  hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void {
671     gainmap_metadata->max_content_boost = hdr_white_nits / kSdrWhiteNits;
672     gainmap_metadata->min_content_boost = 1.0f;
673     gainmap_metadata->gamma = mGamma;
674     gainmap_metadata->offset_sdr = 0.0f;
675     gainmap_metadata->offset_hdr = 0.0f;
676     gainmap_metadata->hdr_capacity_min = 1.0f;
677     if (this->mTargetDispPeakBrightness != -1.0f) {
678       gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits;
679     } else {
680       gainmap_metadata->hdr_capacity_max = gainmap_metadata->max_content_boost;
681     }
682 
683     float log2MinBoost = log2(gainmap_metadata->min_content_boost);
684     float log2MaxBoost = log2(gainmap_metadata->max_content_boost);
685 
686     const int threads = (std::min)(GetCPUCoreCount(), 4u);
687     const int jobSizeInRows = 1;
688     unsigned int rowStep = threads == 1 ? map_height : jobSizeInRows;
689     JobQueue jobQueue;
690     std::function<void()> generateMap =
691         [this, sdr_intent, hdr_intent, gainmap_metadata, dest, hdrInvOetf, hdrLuminanceFn,
692          hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn,
693          sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, log2MinBoost, log2MaxBoost,
694          use_luminance, &jobQueue]() -> void {
695       unsigned int rowStart, rowEnd;
696       const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
697       const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
698       const float hdrSampleToNitsFactor =
699           hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
700       ColorTransformFn clampPixel = hdr_intent->ct == UHDR_CT_LINEAR
701                                         ? static_cast<ColorTransformFn>(clampPixelFloatLinear)
702                                         : static_cast<ColorTransformFn>(clampPixelFloat);
703       while (jobQueue.dequeueJob(rowStart, rowEnd)) {
704         for (size_t y = rowStart; y < rowEnd; ++y) {
705           for (size_t x = 0; x < dest->w; ++x) {
706             Color sdr_rgb_gamma;
707 
708             if (isSdrIntentRgb) {
709               sdr_rgb_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
710             } else {
711               Color sdr_yuv_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
712               sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
713             }
714 
715             // We are assuming the SDR input is always sRGB transfer.
716 #if USE_SRGB_INVOETF_LUT
717             Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
718 #else
719             Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
720 #endif
721 
722             Color hdr_rgb_gamma;
723 
724             if (isHdrIntentRgb) {
725               hdr_rgb_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
726             } else {
727               Color hdr_yuv_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
728               hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
729             }
730             Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
731             hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
732             hdr_rgb = hdrGamutConversionFn(hdr_rgb);
733             hdr_rgb = clampPixel(hdr_rgb);
734 
735             if (mUseMultiChannelGainMap) {
736               Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
737               Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor;
738               size_t pixel_idx = (x + y * dest->stride[UHDR_PLANE_PACKED]) * 3;
739 
740               reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = encodeGain(
741                   sdr_rgb_nits.r, hdr_rgb_nits.r, gainmap_metadata, log2MinBoost, log2MaxBoost);
742               reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 1] =
743                   encodeGain(sdr_rgb_nits.g, hdr_rgb_nits.g, gainmap_metadata, log2MinBoost,
744                              log2MaxBoost);
745               reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 2] =
746                   encodeGain(sdr_rgb_nits.b, hdr_rgb_nits.b, gainmap_metadata, log2MinBoost,
747                              log2MaxBoost);
748             } else {
749               float sdr_y_nits;
750               float hdr_y_nits;
751               if (use_luminance) {
752                 sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
753                 hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor;
754               } else {
755                 sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits;
756                 hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor;
757               }
758 
759               size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_Y];
760 
761               reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_Y])[pixel_idx] =
762                   encodeGain(sdr_y_nits, hdr_y_nits, gainmap_metadata, log2MinBoost, log2MaxBoost);
763             }
764           }
765         }
766       }
767     };
768 
769     // generate map
770     std::vector<std::thread> workers;
771     for (int th = 0; th < threads - 1; th++) {
772       workers.push_back(std::thread(generateMap));
773     }
774 
775     for (unsigned int rowStart = 0; rowStart < map_height;) {
776       unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height);
777       jobQueue.enqueueJob(rowStart, rowEnd);
778       rowStart = rowEnd;
779     }
780     jobQueue.markQueueForEnd();
781     generateMap();
782     std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
783   };
784 
785   auto generateGainMapTwoPass =
786       [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_width, map_height, hdrInvOetf,
787        hdrLuminanceFn, hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn,
788        sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void {
789     uhdr_memory_block_t gainmap_mem((size_t)map_width * map_height * sizeof(float) *
790                                     (mUseMultiChannelGainMap ? 3 : 1));
791     float* gainmap_data = reinterpret_cast<float*>(gainmap_mem.m_buffer.get());
792     float gainmap_min[3] = {127.0f, 127.0f, 127.0f};
793     float gainmap_max[3] = {-128.0f, -128.0f, -128.0f};
794     std::mutex gainmap_minmax;
795 
796     const int threads = (std::min)(GetCPUCoreCount(), 4u);
797     const int jobSizeInRows = 1;
798     unsigned int rowStep = threads == 1 ? map_height : jobSizeInRows;
799     JobQueue jobQueue;
800     std::function<void()> generateMap =
801         [this, sdr_intent, hdr_intent, gainmap_data, map_width, hdrInvOetf, hdrLuminanceFn,
802          hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn,
803          sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, use_luminance, &gainmap_min,
804          &gainmap_max, &gainmap_minmax, &jobQueue]() -> void {
805       unsigned int rowStart, rowEnd;
806       const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
807       const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
808       const float hdrSampleToNitsFactor =
809           hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
810       ColorTransformFn clampPixel = hdr_intent->ct == UHDR_CT_LINEAR
811                                         ? static_cast<ColorTransformFn>(clampPixelFloatLinear)
812                                         : static_cast<ColorTransformFn>(clampPixelFloat);
813       float gainmap_min_th[3] = {127.0f, 127.0f, 127.0f};
814       float gainmap_max_th[3] = {-128.0f, -128.0f, -128.0f};
815 
816       while (jobQueue.dequeueJob(rowStart, rowEnd)) {
817         for (size_t y = rowStart; y < rowEnd; ++y) {
818           for (size_t x = 0; x < map_width; ++x) {
819             Color sdr_rgb_gamma;
820 
821             if (isSdrIntentRgb) {
822               sdr_rgb_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
823             } else {
824               Color sdr_yuv_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
825               sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
826             }
827 
828             // We are assuming the SDR input is always sRGB transfer.
829 #if USE_SRGB_INVOETF_LUT
830             Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
831 #else
832             Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
833 #endif
834 
835             Color hdr_rgb_gamma;
836 
837             if (isHdrIntentRgb) {
838               hdr_rgb_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
839             } else {
840               Color hdr_yuv_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
841               hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
842             }
843             Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
844             hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
845             hdr_rgb = hdrGamutConversionFn(hdr_rgb);
846             hdr_rgb = clampPixel(hdr_rgb);
847 
848             if (mUseMultiChannelGainMap) {
849               Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
850               Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor;
851               size_t pixel_idx = (x + y * map_width) * 3;
852 
853               gainmap_data[pixel_idx] = computeGain(sdr_rgb_nits.r, hdr_rgb_nits.r);
854               gainmap_data[pixel_idx + 1] = computeGain(sdr_rgb_nits.g, hdr_rgb_nits.g);
855               gainmap_data[pixel_idx + 2] = computeGain(sdr_rgb_nits.b, hdr_rgb_nits.b);
856               for (int i = 0; i < 3; i++) {
857                 gainmap_min_th[i] = (std::min)(gainmap_data[pixel_idx + i], gainmap_min_th[i]);
858                 gainmap_max_th[i] = (std::max)(gainmap_data[pixel_idx + i], gainmap_max_th[i]);
859               }
860             } else {
861               float sdr_y_nits;
862               float hdr_y_nits;
863 
864               if (use_luminance) {
865                 sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
866                 hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor;
867               } else {
868                 sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits;
869                 hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor;
870               }
871 
872               size_t pixel_idx = x + y * map_width;
873               gainmap_data[pixel_idx] = computeGain(sdr_y_nits, hdr_y_nits);
874               gainmap_min_th[0] = (std::min)(gainmap_data[pixel_idx], gainmap_min_th[0]);
875               gainmap_max_th[0] = (std::max)(gainmap_data[pixel_idx], gainmap_max_th[0]);
876             }
877           }
878         }
879       }
880       {
881         std::unique_lock<std::mutex> lock{gainmap_minmax};
882         for (int index = 0; index < (mUseMultiChannelGainMap ? 3 : 1); index++) {
883           gainmap_min[index] = (std::min)(gainmap_min[index], gainmap_min_th[index]);
884           gainmap_max[index] = (std::max)(gainmap_max[index], gainmap_max_th[index]);
885         }
886       }
887     };
888 
889     // generate map
890     std::vector<std::thread> workers;
891     for (int th = 0; th < threads - 1; th++) {
892       workers.push_back(std::thread(generateMap));
893     }
894 
895     for (unsigned int rowStart = 0; rowStart < map_height;) {
896       unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height);
897       jobQueue.enqueueJob(rowStart, rowEnd);
898       rowStart = rowEnd;
899     }
900     jobQueue.markQueueForEnd();
901     generateMap();
902     std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
903 
904     float min_content_boost_log2 = gainmap_min[0];
905     float max_content_boost_log2 = gainmap_max[0];
906     for (int index = 1; index < (mUseMultiChannelGainMap ? 3 : 1); index++) {
907       min_content_boost_log2 = (std::min)(gainmap_min[index], min_content_boost_log2);
908       max_content_boost_log2 = (std::max)(gainmap_max[index], max_content_boost_log2);
909     }
910     // -13.0 emphirically is a small enough gain factor that is capable of representing hdr
911     // black from any sdr luminance. Allowing further excursion might not offer any benefit and on
912     // the downside can cause bigger error during affine map and inverse map.
913     min_content_boost_log2 = (std::max)(-13.0f, min_content_boost_log2);
914     if (this->mMaxContentBoost != FLT_MAX) {
915       float suggestion = log2(this->mMaxContentBoost);
916       max_content_boost_log2 = (std::min)(max_content_boost_log2, suggestion);
917     }
918     if (this->mMinContentBoost != FLT_MIN) {
919       float suggestion = log2(this->mMinContentBoost);
920       min_content_boost_log2 = (std::max)(min_content_boost_log2, suggestion);
921     }
922     if (fabs(max_content_boost_log2 - min_content_boost_log2) < FLT_EPSILON) {
923       max_content_boost_log2 += 0.1;  // to avoid div by zero during affine transform
924     }
925 
926     std::function<void()> encodeMap = [this, gainmap_data, map_width, dest, min_content_boost_log2,
927                                        max_content_boost_log2, &jobQueue]() -> void {
928       unsigned int rowStart, rowEnd;
929 
930       while (jobQueue.dequeueJob(rowStart, rowEnd)) {
931         if (mUseMultiChannelGainMap) {
932           for (size_t j = rowStart; j < rowEnd; j++) {
933             size_t dst_pixel_idx = j * dest->stride[UHDR_PLANE_PACKED] * 3;
934             size_t src_pixel_idx = j * map_width * 3;
935             for (size_t i = 0; i < map_width * 3; i++) {
936               reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[dst_pixel_idx + i] =
937                   affineMapGain(gainmap_data[src_pixel_idx + i], min_content_boost_log2,
938                                 max_content_boost_log2, this->mGamma);
939             }
940           }
941         } else {
942           for (size_t j = rowStart; j < rowEnd; j++) {
943             size_t dst_pixel_idx = j * dest->stride[UHDR_PLANE_Y];
944             size_t src_pixel_idx = j * map_width;
945             for (size_t i = 0; i < map_width; i++) {
946               reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_Y])[dst_pixel_idx + i] =
947                   affineMapGain(gainmap_data[src_pixel_idx + i], min_content_boost_log2,
948                                 max_content_boost_log2, this->mGamma);
949             }
950           }
951         }
952       }
953     };
954     workers.clear();
955     jobQueue.reset();
956     rowStep = threads == 1 ? map_height : 1;
957     for (int th = 0; th < threads - 1; th++) {
958       workers.push_back(std::thread(encodeMap));
959     }
960     for (unsigned int rowStart = 0; rowStart < map_height;) {
961       unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height);
962       jobQueue.enqueueJob(rowStart, rowEnd);
963       rowStart = rowEnd;
964     }
965     jobQueue.markQueueForEnd();
966     encodeMap();
967     std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
968 
969     gainmap_metadata->max_content_boost = exp2(max_content_boost_log2);
970     gainmap_metadata->min_content_boost = exp2(min_content_boost_log2);
971     gainmap_metadata->gamma = this->mGamma;
972     gainmap_metadata->offset_sdr = 0.0f;
973     gainmap_metadata->offset_hdr = 0.0f;
974     gainmap_metadata->hdr_capacity_min = 1.0f;
975     if (this->mTargetDispPeakBrightness != -1.0f) {
976       gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits;
977     } else {
978       gainmap_metadata->hdr_capacity_max = hdr_white_nits / kSdrWhiteNits;
979     }
980   };
981 
982   if (mEncPreset == UHDR_USAGE_REALTIME) {
983     generateGainMapOnePass();
984   } else {
985     generateGainMapTwoPass();
986   }
987 
988   return status;
989 }
990 
991 // JPEG/R structure:
992 // SOI (ff d8)
993 //
994 // (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
995 // in the JPEG input (Encode API-2, API-3, API-4))
996 // APP1 (ff e1)
997 // 2 bytes of length (2 + length of exif package)
998 // EXIF package (this includes the first two bytes representing the package length)
999 //
1000 // (Required, XMP package) APP1 (ff e1)
1001 // 2 bytes of length (2 + 29 + length of xmp package)
1002 // name space ("http://ns.adobe.com/xap/1.0/\0")
1003 // XMP
1004 //
1005 // (Required, ISO 21496-1 metadata, version only) APP2 (ff e2)
1006 // 2 bytes of length
1007 // name space (""urn:iso:std:iso:ts:21496:-1\0")
1008 // 2 bytes minimum_version: (00 00)
1009 // 2 bytes writer_version: (00 00)
1010 //
1011 // (Required, MPF package) APP2 (ff e2)
1012 // 2 bytes of length
1013 // MPF
1014 //
1015 // (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
1016 //
1017 // SOI (ff d8)
1018 //
1019 // (Required, XMP package) APP1 (ff e1)
1020 // 2 bytes of length (2 + 29 + length of xmp package)
1021 // name space ("http://ns.adobe.com/xap/1.0/\0")
1022 // XMP
1023 //
1024 // (Required, ISO 21496-1 metadata) APP2 (ff e2)
1025 // 2 bytes of length
1026 // name space (""urn:iso:std:iso:ts:21496:-1\0")
1027 // metadata
1028 //
1029 // (Required) secondary image (the gain map, without the first two bytes (SOI))
1030 //
1031 // Metadata versions we are using:
1032 // ECMA TR-98 for JFIF marker
1033 // Exif 2.2 spec for EXIF marker
1034 // Adobe XMP spec part 3 for XMP marker
1035 // ICC v4.3 spec for ICC
appendGainMap(uhdr_compressed_image_t * sdr_intent_compressed,uhdr_compressed_image_t * gainmap_compressed,uhdr_mem_block_t * pExif,void * pIcc,size_t icc_size,uhdr_gainmap_metadata_ext_t * metadata,uhdr_compressed_image_t * dest)1036 uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compressed,
1037                                        uhdr_compressed_image_t* gainmap_compressed,
1038                                        uhdr_mem_block_t* pExif, void* pIcc, size_t icc_size,
1039                                        uhdr_gainmap_metadata_ext_t* metadata,
1040                                        uhdr_compressed_image_t* dest) {
1041   const size_t xmpNameSpaceLength = kXmpNameSpace.size() + 1;  // need to count the null terminator
1042   const size_t isoNameSpaceLength = kIsoNameSpace.size() + 1;  // need to count the null terminator
1043 
1044   /////////////////////////////////////////////////////////////////////////////////////////////////
1045   // calculate secondary image length first, because the length will be written into the primary //
1046   // image xmp                                                                                   //
1047   /////////////////////////////////////////////////////////////////////////////////////////////////
1048 
1049   // XMP
1050   string xmp_secondary;
1051   size_t xmp_secondary_length;
1052   if (kWriteXmpMetadata) {
1053     xmp_secondary = generateXmpForSecondaryImage(*metadata);
1054     // xmp_secondary_length = 2 bytes representing the length of the package +
1055     //  + xmpNameSpaceLength = 29 bytes length
1056     //  + length of xmp packet = xmp_secondary.size()
1057     xmp_secondary_length = 2 + xmpNameSpaceLength + xmp_secondary.size();
1058   }
1059 
1060   // ISO
1061   uhdr_gainmap_metadata_frac iso_secondary_metadata;
1062   std::vector<uint8_t> iso_secondary_data;
1063   size_t iso_secondary_length;
1064   if (kWriteIso21496_1Metadata) {
1065     UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction(
1066         metadata, &iso_secondary_metadata));
1067 
1068     UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::encodeGainmapMetadata(&iso_secondary_metadata,
1069                                                                      iso_secondary_data));
1070     // iso_secondary_length = 2 bytes representing the length of the package +
1071     //  + isoNameSpaceLength = 28 bytes length
1072     //  + length of iso metadata packet = iso_secondary_data.size()
1073     iso_secondary_length = 2 + isoNameSpaceLength + iso_secondary_data.size();
1074   }
1075 
1076   size_t secondary_image_size = 2 /* 2 bytes length of APP1 sign */ + gainmap_compressed->data_sz;
1077   if (kWriteXmpMetadata) {
1078     secondary_image_size += xmp_secondary_length;
1079   }
1080   if (kWriteIso21496_1Metadata) {
1081     secondary_image_size += iso_secondary_length;
1082   }
1083 
1084   // Check if EXIF package presents in the JPEG input.
1085   // If so, extract and remove the EXIF package.
1086   JpegDecoderHelper decoder;
1087   UHDR_ERR_CHECK(decoder.parseImage(sdr_intent_compressed->data, sdr_intent_compressed->data_sz));
1088 
1089   uhdr_mem_block_t exif_from_jpg;
1090   exif_from_jpg.data = nullptr;
1091   exif_from_jpg.data_sz = 0;
1092 
1093   uhdr_compressed_image_t new_jpg_image;
1094   new_jpg_image.data = nullptr;
1095   new_jpg_image.data_sz = 0;
1096   new_jpg_image.capacity = 0;
1097   new_jpg_image.cg = UHDR_CG_UNSPECIFIED;
1098   new_jpg_image.ct = UHDR_CT_UNSPECIFIED;
1099   new_jpg_image.range = UHDR_CR_UNSPECIFIED;
1100 
1101   std::unique_ptr<uint8_t[]> dest_data;
1102   if (decoder.getEXIFPos() >= 0) {
1103     if (pExif != nullptr) {
1104       uhdr_error_info_t status;
1105       status.error_code = UHDR_CODEC_INVALID_PARAM;
1106       status.has_detail = 1;
1107       snprintf(status.detail, sizeof status.detail,
1108                "received exif from uhdr_enc_set_exif_data() while the base image intent already "
1109                "contains exif, unsure which one to use");
1110       return status;
1111     }
1112     copyJpegWithoutExif(&new_jpg_image, sdr_intent_compressed, decoder.getEXIFPos(),
1113                         decoder.getEXIFSize());
1114     dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
1115     exif_from_jpg.data = decoder.getEXIFPtr();
1116     exif_from_jpg.data_sz = decoder.getEXIFSize();
1117     pExif = &exif_from_jpg;
1118   }
1119 
1120   uhdr_compressed_image_t* final_primary_jpg_image_ptr =
1121       new_jpg_image.data_sz == 0 ? sdr_intent_compressed : &new_jpg_image;
1122 
1123   size_t pos = 0;
1124   // Begin primary image
1125   // Write SOI
1126   UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1127   UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1128 
1129   // Write EXIF
1130   if (pExif != nullptr) {
1131     const size_t length = 2 + pExif->data_sz;
1132     const uint8_t lengthH = ((length >> 8) & 0xff);
1133     const uint8_t lengthL = (length & 0xff);
1134     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1135     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1136     UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
1137     UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
1138     UHDR_ERR_CHECK(Write(dest, pExif->data, pExif->data_sz, pos));
1139   }
1140 
1141   // Prepare and write XMP
1142   if (kWriteXmpMetadata) {
1143     const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
1144     const size_t length = 2 + xmpNameSpaceLength + xmp_primary.size();
1145     const uint8_t lengthH = ((length >> 8) & 0xff);
1146     const uint8_t lengthL = (length & 0xff);
1147     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1148     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1149     UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
1150     UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
1151     UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos));
1152     UHDR_ERR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1153   }
1154 
1155   // Write ICC
1156   if (pIcc != nullptr && icc_size > 0) {
1157     const size_t length = icc_size + 2;
1158     const uint8_t lengthH = ((length >> 8) & 0xff);
1159     const uint8_t lengthL = (length & 0xff);
1160     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1161     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1162     UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
1163     UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
1164     UHDR_ERR_CHECK(Write(dest, pIcc, icc_size, pos));
1165   }
1166 
1167   // Prepare and write ISO 21496-1 metadata
1168   if (kWriteIso21496_1Metadata) {
1169     const size_t length = 2 + isoNameSpaceLength + 4;
1170     uint8_t zero = 0;
1171     const uint8_t lengthH = ((length >> 8) & 0xff);
1172     const uint8_t lengthL = (length & 0xff);
1173     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1174     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1175     UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
1176     UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
1177     UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos));
1178     UHDR_ERR_CHECK(Write(dest, &zero, 1, pos));
1179     UHDR_ERR_CHECK(Write(dest, &zero, 1, pos));  // 2 bytes minimum_version: (00 00)
1180     UHDR_ERR_CHECK(Write(dest, &zero, 1, pos));
1181     UHDR_ERR_CHECK(Write(dest, &zero, 1, pos));  // 2 bytes writer_version: (00 00)
1182   }
1183 
1184   // Prepare and write MPF
1185   {
1186     const size_t length = 2 + calculateMpfSize();
1187     const uint8_t lengthH = ((length >> 8) & 0xff);
1188     const uint8_t lengthL = (length & 0xff);
1189     size_t primary_image_size = pos + length + final_primary_jpg_image_ptr->data_sz;
1190     // between APP2 + package size + signature
1191     // ff e2 00 58 4d 50 46 00
1192     // 2 + 2 + 4 = 8 (bytes)
1193     // and ff d8 sign of the secondary image
1194     size_t secondary_image_offset = primary_image_size - pos - 8;
1195     std::shared_ptr<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1196                                                   secondary_image_size, secondary_image_offset);
1197     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1198     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1199     UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
1200     UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
1201     UHDR_ERR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
1202   }
1203 
1204   // Write primary image
1205   UHDR_ERR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
1206                        final_primary_jpg_image_ptr->data_sz - 2, pos));
1207   // Finish primary image
1208 
1209   // Begin secondary image (gain map)
1210   // Write SOI
1211   UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1212   UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1213 
1214   // Prepare and write XMP
1215   if (kWriteXmpMetadata) {
1216     const size_t length = xmp_secondary_length;
1217     const uint8_t lengthH = ((length >> 8) & 0xff);
1218     const uint8_t lengthL = (length & 0xff);
1219     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1220     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1221     UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
1222     UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
1223     UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos));
1224     UHDR_ERR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1225   }
1226 
1227   // Prepare and write ISO 21496-1 metadata
1228   if (kWriteIso21496_1Metadata) {
1229     const size_t length = iso_secondary_length;
1230     const uint8_t lengthH = ((length >> 8) & 0xff);
1231     const uint8_t lengthL = (length & 0xff);
1232     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1233     UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1234     UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
1235     UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
1236     UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos));
1237     UHDR_ERR_CHECK(Write(dest, (void*)iso_secondary_data.data(), iso_secondary_data.size(), pos));
1238   }
1239 
1240   // Write secondary image
1241   UHDR_ERR_CHECK(
1242       Write(dest, (uint8_t*)gainmap_compressed->data + 2, gainmap_compressed->data_sz - 2, pos));
1243 
1244   // Set back length
1245   dest->data_sz = pos;
1246 
1247   // Done!
1248   return g_no_error;
1249 }
1250 
getJPEGRInfo(uhdr_compressed_image_t * uhdr_compressed_img,jr_info_ptr uhdr_image_info)1251 uhdr_error_info_t JpegR::getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img,
1252                                       jr_info_ptr uhdr_image_info) {
1253   uhdr_compressed_image_t primary_image, gainmap;
1254 
1255   UHDR_ERR_CHECK(extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_image, &gainmap))
1256 
1257   UHDR_ERR_CHECK(parseJpegInfo(&primary_image, uhdr_image_info->primaryImgInfo,
1258                                &uhdr_image_info->width, &uhdr_image_info->height))
1259   if (uhdr_image_info->gainmapImgInfo != nullptr) {
1260     UHDR_ERR_CHECK(parseJpegInfo(&gainmap, uhdr_image_info->gainmapImgInfo))
1261   }
1262 
1263   return g_no_error;
1264 }
1265 
parseGainMapMetadata(uint8_t * iso_data,size_t iso_size,uint8_t * xmp_data,size_t xmp_size,uhdr_gainmap_metadata_ext_t * uhdr_metadata)1266 uhdr_error_info_t JpegR::parseGainMapMetadata(uint8_t* iso_data, size_t iso_size, uint8_t* xmp_data,
1267                                               size_t xmp_size,
1268                                               uhdr_gainmap_metadata_ext_t* uhdr_metadata) {
1269   if (iso_size > 0) {
1270     if (iso_size < kIsoNameSpace.size() + 1) {
1271       uhdr_error_info_t status;
1272       status.error_code = UHDR_CODEC_ERROR;
1273       status.has_detail = 1;
1274       snprintf(status.detail, sizeof status.detail,
1275                "iso block size needs to be atleast %zd but got %zd", kIsoNameSpace.size() + 1,
1276                iso_size);
1277       return status;
1278     }
1279     uhdr_gainmap_metadata_frac decodedMetadata;
1280     std::vector<uint8_t> iso_vec;
1281     for (size_t i = kIsoNameSpace.size() + 1; i < iso_size; i++) {
1282       iso_vec.push_back(iso_data[i]);
1283     }
1284 
1285     UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::decodeGainmapMetadata(iso_vec, &decodedMetadata));
1286     UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(&decodedMetadata,
1287                                                                               uhdr_metadata));
1288   } else if (xmp_size > 0) {
1289     UHDR_ERR_CHECK(getMetadataFromXMP(xmp_data, xmp_size, uhdr_metadata));
1290   } else {
1291     uhdr_error_info_t status;
1292     status.error_code = UHDR_CODEC_INVALID_PARAM;
1293     status.has_detail = 1;
1294     snprintf(status.detail, sizeof status.detail,
1295              "received no valid buffer to parse gainmap metadata");
1296     return status;
1297   }
1298 
1299   return g_no_error;
1300 }
1301 
1302 /* Decode API */
decodeJPEGR(uhdr_compressed_image_t * uhdr_compressed_img,uhdr_raw_image_t * dest,float max_display_boost,uhdr_color_transfer_t output_ct,uhdr_img_fmt_t output_format,uhdr_raw_image_t * gainmap_img,uhdr_gainmap_metadata_t * gainmap_metadata)1303 uhdr_error_info_t JpegR::decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_img,
1304                                      uhdr_raw_image_t* dest, float max_display_boost,
1305                                      uhdr_color_transfer_t output_ct, uhdr_img_fmt_t output_format,
1306                                      uhdr_raw_image_t* gainmap_img,
1307                                      uhdr_gainmap_metadata_t* gainmap_metadata) {
1308   uhdr_compressed_image_t primary_jpeg_image, gainmap_jpeg_image;
1309   UHDR_ERR_CHECK(
1310       extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_jpeg_image, &gainmap_jpeg_image))
1311 
1312   JpegDecoderHelper jpeg_dec_obj_sdr;
1313   UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(
1314       primary_jpeg_image.data, primary_jpeg_image.data_sz,
1315       (output_ct == UHDR_CT_SRGB) ? DECODE_TO_RGB_CS : DECODE_TO_YCBCR_CS));
1316 
1317   JpegDecoderHelper jpeg_dec_obj_gm;
1318   uhdr_raw_image_t gainmap;
1319   if (gainmap_img != nullptr || output_ct != UHDR_CT_SRGB) {
1320     UHDR_ERR_CHECK(jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data,
1321                                                    gainmap_jpeg_image.data_sz, DECODE_STREAM));
1322     gainmap = jpeg_dec_obj_gm.getDecompressedImage();
1323     if (gainmap_img != nullptr) {
1324       UHDR_ERR_CHECK(copy_raw_image(&gainmap, gainmap_img));
1325     }
1326   }
1327 
1328   uhdr_gainmap_metadata_ext_t uhdr_metadata;
1329   if (gainmap_metadata != nullptr || output_ct != UHDR_CT_SRGB) {
1330     UHDR_ERR_CHECK(parseGainMapMetadata(static_cast<uint8_t*>(jpeg_dec_obj_gm.getIsoMetadataPtr()),
1331                                         jpeg_dec_obj_gm.getIsoMetadataSize(),
1332                                         static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
1333                                         jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata))
1334     if (gainmap_metadata != nullptr) {
1335       gainmap_metadata->min_content_boost = uhdr_metadata.min_content_boost;
1336       gainmap_metadata->max_content_boost = uhdr_metadata.max_content_boost;
1337       gainmap_metadata->gamma = uhdr_metadata.gamma;
1338       gainmap_metadata->offset_sdr = uhdr_metadata.offset_sdr;
1339       gainmap_metadata->offset_hdr = uhdr_metadata.offset_hdr;
1340       gainmap_metadata->hdr_capacity_min = uhdr_metadata.hdr_capacity_min;
1341       gainmap_metadata->hdr_capacity_max = uhdr_metadata.hdr_capacity_max;
1342     }
1343   }
1344 
1345   uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage();
1346   sdr_intent.cg =
1347       IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize());
1348   if (output_ct == UHDR_CT_SRGB) {
1349     UHDR_ERR_CHECK(copy_raw_image(&sdr_intent, dest));
1350     return g_no_error;
1351   }
1352 
1353   UHDR_ERR_CHECK(applyGainMap(&sdr_intent, &gainmap, &uhdr_metadata, output_ct, output_format,
1354                               max_display_boost, dest));
1355 
1356   return g_no_error;
1357 }
1358 
applyGainMap(uhdr_raw_image_t * sdr_intent,uhdr_raw_image_t * gainmap_img,uhdr_gainmap_metadata_ext_t * gainmap_metadata,uhdr_color_transfer_t output_ct,uhdr_img_fmt_t output_format,float max_display_boost,uhdr_raw_image_t * dest)1359 uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img,
1360                                       uhdr_gainmap_metadata_ext_t* gainmap_metadata,
1361                                       uhdr_color_transfer_t output_ct,
1362                                       [[maybe_unused]] uhdr_img_fmt_t output_format,
1363                                       float max_display_boost, uhdr_raw_image_t* dest) {
1364   if (gainmap_metadata->version.compare(kJpegrVersion)) {
1365     uhdr_error_info_t status;
1366     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1367     status.has_detail = 1;
1368     snprintf(status.detail, sizeof status.detail,
1369              "Unsupported gainmap metadata, version. Expected %s, Got %s", kJpegrVersion,
1370              gainmap_metadata->version.c_str());
1371     return status;
1372   }
1373   UHDR_ERR_CHECK(uhdr_validate_gainmap_metadata_descriptor(gainmap_metadata));
1374   if (sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444 &&
1375       sdr_intent->fmt != UHDR_IMG_FMT_16bppYCbCr422 &&
1376       sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
1377     uhdr_error_info_t status;
1378     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1379     status.has_detail = 1;
1380     snprintf(status.detail, sizeof status.detail,
1381              "apply gainmap method expects base image color format to be one of "
1382              "{UHDR_IMG_FMT_24bppYCbCr444, UHDR_IMG_FMT_16bppYCbCr422, "
1383              "UHDR_IMG_FMT_12bppYCbCr420}. Received %d",
1384              sdr_intent->fmt);
1385     return status;
1386   }
1387   if (gainmap_img->fmt != UHDR_IMG_FMT_8bppYCbCr400 &&
1388       gainmap_img->fmt != UHDR_IMG_FMT_24bppRGB888 &&
1389       gainmap_img->fmt != UHDR_IMG_FMT_32bppRGBA8888) {
1390     uhdr_error_info_t status;
1391     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1392     status.has_detail = 1;
1393     snprintf(status.detail, sizeof status.detail,
1394              "apply gainmap method expects gainmap image color format to be one of "
1395              "{UHDR_IMG_FMT_8bppYCbCr400, UHDR_IMG_FMT_24bppRGB888, UHDR_IMG_FMT_32bppRGBA8888}. "
1396              "Received %d",
1397              gainmap_img->fmt);
1398     return status;
1399   }
1400 
1401 #ifdef UHDR_ENABLE_GLES
1402   if (mUhdrGLESCtxt != nullptr) {
1403     if (((sdr_intent->fmt == UHDR_IMG_FMT_12bppYCbCr420 && sdr_intent->w % 2 == 0 &&
1404           sdr_intent->h % 2 == 0) ||
1405          (sdr_intent->fmt == UHDR_IMG_FMT_16bppYCbCr422 && sdr_intent->w % 2 == 0) ||
1406          (sdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCr444)) &&
1407         isBufferDataContiguous(sdr_intent) && isBufferDataContiguous(gainmap_img) &&
1408         isBufferDataContiguous(dest)) {
1409       // TODO: both inputs and outputs of GLES implementation assumes that raw image is contiguous
1410       // and without strides. If not, handle the same by using temp copy
1411       float display_boost = (std::min)(max_display_boost, gainmap_metadata->hdr_capacity_max);
1412 
1413       return applyGainMapGLES(sdr_intent, gainmap_img, gainmap_metadata, output_ct, display_boost,
1414                               dest, static_cast<uhdr_opengl_ctxt_t*>(mUhdrGLESCtxt));
1415     }
1416   }
1417 #endif
1418 
1419   std::unique_ptr<uhdr_raw_image_ext_t> resized_gainmap = nullptr;
1420   {
1421     float primary_aspect_ratio = (float)sdr_intent->w / sdr_intent->h;
1422     float gainmap_aspect_ratio = (float)gainmap_img->w / gainmap_img->h;
1423     float delta_aspect_ratio = fabs(primary_aspect_ratio - gainmap_aspect_ratio);
1424     // Allow 1% delta
1425     const float delta_tolerance = 0.01;
1426     if (delta_aspect_ratio / primary_aspect_ratio > delta_tolerance) {
1427       resized_gainmap = resize_image(gainmap_img, sdr_intent->w, sdr_intent->h);
1428       if (resized_gainmap == nullptr) {
1429         uhdr_error_info_t status;
1430         status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1431         status.has_detail = 1;
1432         snprintf(status.detail, sizeof status.detail,
1433                  "encountered error while resizing the gainmap image from %ux%u to %ux%u",
1434                  gainmap_img->w, gainmap_img->h, sdr_intent->w, sdr_intent->h);
1435         return status;
1436       }
1437       gainmap_img = resized_gainmap.get();
1438     }
1439   }
1440 
1441   float map_scale_factor = (float)sdr_intent->w / gainmap_img->w;
1442   int map_scale_factor_rnd = (std::max)(1, (int)std::roundf(map_scale_factor));
1443 
1444   dest->cg = sdr_intent->cg;
1445   // Table will only be used when map scale factor is integer.
1446   ShepardsIDW idwTable(map_scale_factor_rnd);
1447   float display_boost = (std::min)(max_display_boost, gainmap_metadata->hdr_capacity_max);
1448 
1449   float gainmap_weight;
1450   if (display_boost != gainmap_metadata->hdr_capacity_max) {
1451     gainmap_weight =
1452         (log2(display_boost) - log2(gainmap_metadata->hdr_capacity_min)) /
1453         (log2(gainmap_metadata->hdr_capacity_max) - log2(gainmap_metadata->hdr_capacity_min));
1454     // avoid extrapolating the gain map to fill the displayable range
1455     gainmap_weight = CLIP3(0.0f, gainmap_weight, 1.0f);
1456   } else {
1457     gainmap_weight = 1.0f;
1458   }
1459   GainLUT gainLUT(gainmap_metadata, gainmap_weight);
1460 
1461   GetPixelFn get_pixel_fn = getPixelFn(sdr_intent->fmt);
1462   if (get_pixel_fn == nullptr) {
1463     uhdr_error_info_t status;
1464     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1465     status.has_detail = 1;
1466     snprintf(status.detail, sizeof status.detail,
1467              "No implementation available for reading pixels for color format %d", sdr_intent->fmt);
1468     return status;
1469   }
1470 
1471   JobQueue jobQueue;
1472   std::function<void()> applyRecMap = [sdr_intent, gainmap_img, dest, &jobQueue, &idwTable,
1473                                        output_ct, &gainLUT, gainmap_metadata,
1474 #if !USE_APPLY_GAIN_LUT
1475                                        gainmap_weight,
1476 #endif
1477                                        map_scale_factor, get_pixel_fn]() -> void {
1478     unsigned int width = sdr_intent->w;
1479     unsigned int rowStart, rowEnd;
1480 
1481     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1482       for (size_t y = rowStart; y < rowEnd; ++y) {
1483         for (size_t x = 0; x < width; ++x) {
1484           Color yuv_gamma_sdr = get_pixel_fn(sdr_intent, x, y);
1485           // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1486           Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1487           // We are assuming the SDR base image is always sRGB transfer.
1488 #if USE_SRGB_INVOETF_LUT
1489           Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1490 #else
1491           Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
1492 #endif
1493           Color rgb_hdr;
1494           if (gainmap_img->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
1495             float gain;
1496 
1497             if (map_scale_factor != floorf(map_scale_factor)) {
1498               gain = sampleMap(gainmap_img, map_scale_factor, x, y);
1499             } else {
1500               gain = sampleMap(gainmap_img, map_scale_factor, x, y, idwTable);
1501             }
1502 
1503 #if USE_APPLY_GAIN_LUT
1504             rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT, gainmap_metadata);
1505 #else
1506             rgb_hdr = applyGain(rgb_sdr, gain, gainmap_metadata, gainmap_weight);
1507 #endif
1508           } else {
1509             Color gain;
1510 
1511             if (map_scale_factor != floorf(map_scale_factor)) {
1512               gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y,
1513                                        gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888);
1514             } else {
1515               gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y, idwTable,
1516                                        gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888);
1517             }
1518 
1519 #if USE_APPLY_GAIN_LUT
1520             rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT, gainmap_metadata);
1521 #else
1522             rgb_hdr = applyGain(rgb_sdr, gain, gainmap_metadata, gainmap_weight);
1523 #endif
1524           }
1525 
1526           size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_PACKED];
1527 
1528           switch (output_ct) {
1529             case UHDR_CT_LINEAR: {
1530               uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1531               reinterpret_cast<uint64_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = rgba_f16;
1532               break;
1533             }
1534             case UHDR_CT_HLG: {
1535 #if USE_HLG_OETF_LUT
1536               ColorTransformFn hdrOetf = hlgOetfLUT;
1537 #else
1538               ColorTransformFn hdrOetf = hlgOetf;
1539 #endif
1540               rgb_hdr = rgb_hdr * kSdrWhiteNits / kHlgMaxNits;
1541               rgb_hdr = hlgInverseOotfApprox(rgb_hdr);
1542               Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1543               uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1544               reinterpret_cast<uint32_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] =
1545                   rgba_1010102;
1546               break;
1547             }
1548             case UHDR_CT_PQ: {
1549 #if USE_PQ_OETF_LUT
1550               ColorTransformFn hdrOetf = pqOetfLUT;
1551 #else
1552               ColorTransformFn hdrOetf = pqOetf;
1553 #endif
1554               rgb_hdr = rgb_hdr * kSdrWhiteNits / kPqMaxNits;
1555               Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1556               uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1557               reinterpret_cast<uint32_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] =
1558                   rgba_1010102;
1559               break;
1560             }
1561             default: {
1562             }
1563               // Should be impossible to hit after input validation.
1564           }
1565         }
1566       }
1567     }
1568   };
1569 
1570   const int threads = (std::min)(GetCPUCoreCount(), 4u);
1571   std::vector<std::thread> workers;
1572   for (int th = 0; th < threads - 1; th++) {
1573     workers.push_back(std::thread(applyRecMap));
1574   }
1575   const unsigned int rowStep = threads == 1 ? sdr_intent->h : map_scale_factor_rnd;
1576   for (unsigned int rowStart = 0; rowStart < sdr_intent->h;) {
1577     unsigned int rowEnd = (std::min)(rowStart + rowStep, sdr_intent->h);
1578     jobQueue.enqueueJob(rowStart, rowEnd);
1579     rowStart = rowEnd;
1580   }
1581   jobQueue.markQueueForEnd();
1582   applyRecMap();
1583   std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
1584 
1585   return g_no_error;
1586 }
1587 
extractPrimaryImageAndGainMap(uhdr_compressed_image_t * jpegr_image,uhdr_compressed_image_t * primary_image,uhdr_compressed_image_t * gainmap_image)1588 uhdr_error_info_t JpegR::extractPrimaryImageAndGainMap(uhdr_compressed_image_t* jpegr_image,
1589                                                        uhdr_compressed_image_t* primary_image,
1590                                                        uhdr_compressed_image_t* gainmap_image) {
1591   MessageHandler msg_handler;
1592   msg_handler.SetMessageWriter(make_unique<AlogMessageWriter>(AlogMessageWriter()));
1593 
1594   std::shared_ptr<DataSegment> seg = DataSegment::Create(
1595       DataRange(0, jpegr_image->data_sz), static_cast<const uint8_t*>(jpegr_image->data),
1596       DataSegment::BufferDispositionPolicy::kDontDelete);
1597   DataSegmentDataSource data_source(seg);
1598 
1599   JpegInfoBuilder jpeg_info_builder;
1600   jpeg_info_builder.SetImageLimit(2);
1601 
1602   JpegScanner jpeg_scanner(&msg_handler);
1603   jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1604   data_source.Reset();
1605 
1606   if (jpeg_scanner.HasError()) {
1607     uhdr_error_info_t status;
1608     status.error_code = UHDR_CODEC_ERROR;
1609     status.has_detail = 1;
1610     auto messages = msg_handler.GetMessages();
1611     std::string append{};
1612     for (auto message : messages) append += message.GetText();
1613     snprintf(status.detail, sizeof status.detail, "%s", append.c_str());
1614     return status;
1615   }
1616 
1617   const auto& jpeg_info = jpeg_info_builder.GetInfo();
1618   const auto& image_ranges = jpeg_info.GetImageRanges();
1619 
1620   if (image_ranges.empty()) {
1621     uhdr_error_info_t status;
1622     status.error_code = UHDR_CODEC_INVALID_PARAM;
1623     status.has_detail = 1;
1624     snprintf(status.detail, sizeof status.detail, "input uhdr image does not any valid images");
1625     return status;
1626   }
1627 
1628   if (primary_image != nullptr) {
1629     primary_image->data = static_cast<uint8_t*>(jpegr_image->data) + image_ranges[0].GetBegin();
1630     primary_image->data_sz = image_ranges[0].GetLength();
1631   }
1632 
1633   if (image_ranges.size() == 1) {
1634     uhdr_error_info_t status;
1635     status.error_code = UHDR_CODEC_INVALID_PARAM;
1636     status.has_detail = 1;
1637     snprintf(status.detail, sizeof status.detail,
1638              "input uhdr image does not contain gainmap image");
1639     return status;
1640   }
1641 
1642   if (gainmap_image != nullptr) {
1643     gainmap_image->data = static_cast<uint8_t*>(jpegr_image->data) + image_ranges[1].GetBegin();
1644     gainmap_image->data_sz = image_ranges[1].GetLength();
1645   }
1646 
1647   // TODO: choose primary image and gain map image carefully
1648   if (image_ranges.size() > 2) {
1649     ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1650           (int)image_ranges.size());
1651   }
1652 
1653   return g_no_error;
1654 }
1655 
parseJpegInfo(uhdr_compressed_image_t * jpeg_image,j_info_ptr image_info,unsigned int * img_width,unsigned int * img_height)1656 uhdr_error_info_t JpegR::parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_info_ptr image_info,
1657                                        unsigned int* img_width, unsigned int* img_height) {
1658   JpegDecoderHelper jpeg_dec_obj;
1659   UHDR_ERR_CHECK(jpeg_dec_obj.parseImage(jpeg_image->data, jpeg_image->data_sz))
1660   unsigned int imgWidth, imgHeight, numComponents;
1661   imgWidth = jpeg_dec_obj.getDecompressedImageWidth();
1662   imgHeight = jpeg_dec_obj.getDecompressedImageHeight();
1663   numComponents = jpeg_dec_obj.getNumComponentsInImage();
1664 
1665   if (image_info != nullptr) {
1666     image_info->width = imgWidth;
1667     image_info->height = imgHeight;
1668     image_info->numComponents = numComponents;
1669     image_info->imgData.resize(jpeg_image->data_sz, 0);
1670     memcpy(static_cast<void*>(image_info->imgData.data()), jpeg_image->data, jpeg_image->data_sz);
1671     if (jpeg_dec_obj.getICCSize() != 0) {
1672       image_info->iccData.resize(jpeg_dec_obj.getICCSize(), 0);
1673       memcpy(static_cast<void*>(image_info->iccData.data()), jpeg_dec_obj.getICCPtr(),
1674              jpeg_dec_obj.getICCSize());
1675     }
1676     if (jpeg_dec_obj.getEXIFSize() != 0) {
1677       image_info->exifData.resize(jpeg_dec_obj.getEXIFSize(), 0);
1678       memcpy(static_cast<void*>(image_info->exifData.data()), jpeg_dec_obj.getEXIFPtr(),
1679              jpeg_dec_obj.getEXIFSize());
1680     }
1681     if (jpeg_dec_obj.getXMPSize() != 0) {
1682       image_info->xmpData.resize(jpeg_dec_obj.getXMPSize(), 0);
1683       memcpy(static_cast<void*>(image_info->xmpData.data()), jpeg_dec_obj.getXMPPtr(),
1684              jpeg_dec_obj.getXMPSize());
1685     }
1686     if (jpeg_dec_obj.getIsoMetadataSize() != 0) {
1687       image_info->isoData.resize(jpeg_dec_obj.getIsoMetadataSize(), 0);
1688       memcpy(static_cast<void*>(image_info->isoData.data()), jpeg_dec_obj.getIsoMetadataPtr(),
1689              jpeg_dec_obj.getIsoMetadataSize());
1690     }
1691   }
1692   if (img_width != nullptr && img_height != nullptr) {
1693     *img_width = imgWidth;
1694     *img_height = imgHeight;
1695   }
1696   return g_no_error;
1697 }
1698 
ReinhardMap(float y_hdr,float headroom)1699 static float ReinhardMap(float y_hdr, float headroom) {
1700   float out = 1.0 + y_hdr / (headroom * headroom);
1701   out /= 1.0 + y_hdr;
1702   return out * y_hdr;
1703 }
1704 
globalTonemap(const std::array<float,3> & rgb_in,float headroom,bool is_normalized)1705 GlobalTonemapOutputs globalTonemap(const std::array<float, 3>& rgb_in, float headroom,
1706                                    bool is_normalized) {
1707   // Scale to Headroom to get HDR values that are referenced to SDR white. The range [0.0, 1.0] is
1708   // linearly stretched to [0.0, headroom].
1709   std::array<float, 3> rgb_hdr;
1710   std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(),
1711                  [&](float x) { return is_normalized ? x * headroom : x; });
1712 
1713   // Apply a tone mapping to compress the range [0, headroom] to [0, 1] by
1714   // keeping the shadows the same and crushing the highlights.
1715   float max_hdr = *std::max_element(rgb_hdr.begin(), rgb_hdr.end());
1716   float max_sdr = ReinhardMap(max_hdr, headroom);
1717   std::array<float, 3> rgb_sdr;
1718   std::transform(rgb_hdr.begin(), rgb_hdr.end(), rgb_sdr.begin(), [&](float x) {
1719     if (x > 0.0f) {
1720       return x * max_sdr / max_hdr;
1721     }
1722     return 0.0f;
1723   });
1724 
1725   GlobalTonemapOutputs tonemap_outputs;
1726   tonemap_outputs.rgb_out = rgb_sdr;
1727   tonemap_outputs.y_hdr = max_hdr;
1728   tonemap_outputs.y_sdr = max_sdr;
1729 
1730   return tonemap_outputs;
1731 }
1732 
ScaleTo8Bit(float value)1733 uint8_t ScaleTo8Bit(float value) {
1734   constexpr float kMaxValFloat = 255.0f;
1735   constexpr int kMaxValInt = 255;
1736   return std::clamp(static_cast<int>(std::round(value * kMaxValFloat)), 0, kMaxValInt);
1737 }
1738 
toneMap(uhdr_raw_image_t * hdr_intent,uhdr_raw_image_t * sdr_intent)1739 uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent) {
1740   if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 &&
1741       hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 &&
1742       hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 &&
1743       hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) {
1744     uhdr_error_info_t status;
1745     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1746     status.has_detail = 1;
1747     snprintf(status.detail, sizeof status.detail,
1748              "tonemap method expects hdr intent color format to be one of "
1749              "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, "
1750              "UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d",
1751              hdr_intent->fmt);
1752     return status;
1753   }
1754 
1755   if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 &&
1756       sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
1757     uhdr_error_info_t status;
1758     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1759     status.has_detail = 1;
1760     snprintf(status.detail, sizeof status.detail,
1761              "tonemap method expects sdr intent color format to be UHDR_IMG_FMT_12bppYCbCr420, if "
1762              "hdr intent color format is UHDR_IMG_FMT_24bppYCbCrP010. Received %d",
1763              sdr_intent->fmt);
1764     return status;
1765   }
1766 
1767   if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444 &&
1768       sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444) {
1769     uhdr_error_info_t status;
1770     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1771     status.has_detail = 1;
1772     snprintf(status.detail, sizeof status.detail,
1773              "tonemap method expects sdr intent color format to be UHDR_IMG_FMT_24bppYCbCr444, if "
1774              "hdr intent color format is UHDR_IMG_FMT_30bppYCbCr444. Received %d",
1775              sdr_intent->fmt);
1776     return status;
1777   }
1778 
1779   if ((hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
1780        hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) &&
1781       sdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA8888) {
1782     uhdr_error_info_t status;
1783     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1784     status.has_detail = 1;
1785     snprintf(status.detail, sizeof status.detail,
1786              "tonemap method expects sdr intent color format to be UHDR_IMG_FMT_32bppRGBA8888, if "
1787              "hdr intent color format is UHDR_IMG_FMT_32bppRGBA1010102 or "
1788              "UHDR_IMG_FMT_64bppRGBAHalfFloat. Received %d",
1789              sdr_intent->fmt);
1790     return status;
1791   }
1792 
1793   ColorTransformFn hdrYuvToRgbFn = getYuvToRgbFn(hdr_intent->cg);
1794   if (hdrYuvToRgbFn == nullptr) {
1795     uhdr_error_info_t status;
1796     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1797     status.has_detail = 1;
1798     snprintf(status.detail, sizeof status.detail,
1799              "No implementation available for converting yuv to rgb for color gamut %d",
1800              hdr_intent->cg);
1801     return status;
1802   }
1803 
1804   LuminanceFn hdrLuminanceFn = getLuminanceFn(hdr_intent->cg);
1805   if (hdrLuminanceFn == nullptr) {
1806     uhdr_error_info_t status;
1807     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1808     status.has_detail = 1;
1809     snprintf(status.detail, sizeof status.detail,
1810              "No implementation available for calculating luminance for color gamut %d",
1811              hdr_intent->cg);
1812     return status;
1813   }
1814 
1815   SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct);
1816   if (hdrOotfFn == nullptr) {
1817     uhdr_error_info_t status;
1818     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1819     status.has_detail = 1;
1820     snprintf(status.detail, sizeof status.detail,
1821              "No implementation available for calculating Ootf for color transfer %d",
1822              hdr_intent->ct);
1823     return status;
1824   }
1825 
1826   ColorTransformFn hdrInvOetf = getInverseOetfFn(hdr_intent->ct);
1827   if (hdrInvOetf == nullptr) {
1828     uhdr_error_info_t status;
1829     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1830     status.has_detail = 1;
1831     snprintf(status.detail, sizeof status.detail,
1832              "No implementation available for converting transfer characteristics %d to linear",
1833              hdr_intent->ct);
1834     return status;
1835   }
1836 
1837   float hdr_white_nits = getReferenceDisplayPeakLuminanceInNits(hdr_intent->ct);
1838   if (hdr_white_nits == -1.0f) {
1839     uhdr_error_info_t status;
1840     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1841     status.has_detail = 1;
1842     snprintf(status.detail, sizeof status.detail,
1843              "received invalid peak brightness %f nits for hdr reference display with color "
1844              "transfer %d ",
1845              hdr_white_nits, hdr_intent->ct);
1846     return status;
1847   }
1848 
1849   GetPixelFn get_pixel_fn = getPixelFn(hdr_intent->fmt);
1850   if (get_pixel_fn == nullptr) {
1851     uhdr_error_info_t status;
1852     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1853     status.has_detail = 1;
1854     snprintf(status.detail, sizeof status.detail,
1855              "No implementation available for reading pixels for color format %d", hdr_intent->fmt);
1856     return status;
1857   }
1858 
1859   PutPixelFn put_pixel_fn = putPixelFn(sdr_intent->fmt);
1860   // for subsampled formats, we are writing to raw image buffers directly instead of using
1861   // put_pixel_fn
1862   if (put_pixel_fn == nullptr && sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
1863     uhdr_error_info_t status;
1864     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1865     status.has_detail = 1;
1866     snprintf(status.detail, sizeof status.detail,
1867              "No implementation available for writing pixels for color format %d", sdr_intent->fmt);
1868     return status;
1869   }
1870 
1871   sdr_intent->cg = UHDR_CG_DISPLAY_P3;
1872   sdr_intent->ct = UHDR_CT_SRGB;
1873   sdr_intent->range = UHDR_CR_FULL_RANGE;
1874 
1875   ColorTransformFn hdrGamutConversionFn = getGamutConversionFn(sdr_intent->cg, hdr_intent->cg);
1876 
1877   unsigned int height = hdr_intent->h;
1878   const int threads = (std::min)(GetCPUCoreCount(), 4u);
1879   // for 420 subsampling, process 2 rows at once
1880   const int jobSizeInRows = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1;
1881   unsigned int rowStep = threads == 1 ? height : jobSizeInRows;
1882   JobQueue jobQueue;
1883   std::function<void()> toneMapInternal;
1884 
1885   toneMapInternal = [hdr_intent, sdr_intent, hdrInvOetf, hdrGamutConversionFn, hdrYuvToRgbFn,
1886                      hdr_white_nits, get_pixel_fn, put_pixel_fn, hdrLuminanceFn, hdrOotfFn,
1887                      &jobQueue]() -> void {
1888     unsigned int rowStart, rowEnd;
1889     const int hfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1;
1890     const int vfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1;
1891     const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
1892     const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
1893     const bool is_normalized = hdr_intent->ct != UHDR_CT_LINEAR;
1894     uint8_t* luma_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_Y]);
1895     uint8_t* cb_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_U]);
1896     uint8_t* cr_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_V]);
1897     size_t luma_stride = sdr_intent->stride[UHDR_PLANE_Y];
1898     size_t cb_stride = sdr_intent->stride[UHDR_PLANE_U];
1899     size_t cr_stride = sdr_intent->stride[UHDR_PLANE_V];
1900 
1901     while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1902       for (size_t y = rowStart; y < rowEnd; y += vfactor) {
1903         for (size_t x = 0; x < hdr_intent->w; x += hfactor) {
1904           // meant for p010 input
1905           float sdr_u_gamma = 0.0f;
1906           float sdr_v_gamma = 0.0f;
1907 
1908           for (int i = 0; i < vfactor; i++) {
1909             for (int j = 0; j < hfactor; j++) {
1910               Color hdr_rgb_gamma;
1911 
1912               if (isHdrIntentRgb) {
1913                 hdr_rgb_gamma = get_pixel_fn(hdr_intent, x + j, y + i);
1914               } else {
1915                 Color hdr_yuv_gamma = get_pixel_fn(hdr_intent, x + j, y + i);
1916                 hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
1917               }
1918               Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
1919               hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
1920 
1921               GlobalTonemapOutputs tonemap_outputs = globalTonemap(
1922                   {hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, hdr_white_nits / kSdrWhiteNits, is_normalized);
1923               Color sdr_rgb_linear_bt2100 = {
1924                   {{tonemap_outputs.rgb_out[0], tonemap_outputs.rgb_out[1],
1925                     tonemap_outputs.rgb_out[2]}}};
1926               Color sdr_rgb = hdrGamutConversionFn(sdr_rgb_linear_bt2100);
1927 
1928               // Hard clip out-of-gamut values;
1929               sdr_rgb = clampPixelFloat(sdr_rgb);
1930 
1931               Color sdr_rgb_gamma = srgbOetf(sdr_rgb);
1932               if (isSdrIntentRgb) {
1933                 put_pixel_fn(sdr_intent, (x + j), (y + i), sdr_rgb_gamma);
1934               } else {
1935                 Color sdr_yuv_gamma = p3RgbToYuv(sdr_rgb_gamma);
1936                 sdr_yuv_gamma += {{{0.0f, 0.5f, 0.5f}}};
1937                 if (sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
1938                   put_pixel_fn(sdr_intent, (x + j), (y + i), sdr_yuv_gamma);
1939                 } else {
1940                   size_t out_y_idx = (y + i) * luma_stride + x + j;
1941                   luma_data[out_y_idx] = ScaleTo8Bit(sdr_yuv_gamma.y);
1942 
1943                   sdr_u_gamma += sdr_yuv_gamma.u;
1944                   sdr_v_gamma += sdr_yuv_gamma.v;
1945                 }
1946               }
1947             }
1948           }
1949           if (sdr_intent->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
1950             sdr_u_gamma /= (hfactor * vfactor);
1951             sdr_v_gamma /= (hfactor * vfactor);
1952             cb_data[x / hfactor + (y / vfactor) * cb_stride] = ScaleTo8Bit(sdr_u_gamma);
1953             cr_data[x / hfactor + (y / vfactor) * cr_stride] = ScaleTo8Bit(sdr_v_gamma);
1954           }
1955         }
1956       }
1957     }
1958   };
1959 
1960   // tone map
1961   std::vector<std::thread> workers;
1962   for (int th = 0; th < threads - 1; th++) {
1963     workers.push_back(std::thread(toneMapInternal));
1964   }
1965 
1966   for (unsigned int rowStart = 0; rowStart < height;) {
1967     unsigned int rowEnd = (std::min)(rowStart + rowStep, height);
1968     jobQueue.enqueueJob(rowStart, rowEnd);
1969     rowStart = rowEnd;
1970   }
1971   jobQueue.markQueueForEnd();
1972   toneMapInternal();
1973   std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
1974 
1975   return g_no_error;
1976 }
1977 
areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,jr_uncompressed_ptr yuv420_image_ptr,ultrahdr_transfer_function hdr_tf,jr_compressed_ptr dest_ptr)1978 status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
1979                                        jr_uncompressed_ptr yuv420_image_ptr,
1980                                        ultrahdr_transfer_function hdr_tf,
1981                                        jr_compressed_ptr dest_ptr) {
1982   if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
1983     ALOGE("Received nullptr for input p010 image");
1984     return ERROR_JPEGR_BAD_PTR;
1985   }
1986   if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
1987     ALOGE("Image dimensions cannot be odd, image dimensions %ux%u", p010_image_ptr->width,
1988           p010_image_ptr->height);
1989     return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
1990   }
1991   if ((int)p010_image_ptr->width < kMinWidth || (int)p010_image_ptr->height < kMinHeight) {
1992     ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %ux%u", kMinWidth,
1993           kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
1994     return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
1995   }
1996   if ((int)p010_image_ptr->width > kMaxWidth || (int)p010_image_ptr->height > kMaxHeight) {
1997     ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %ux%u", kMaxWidth,
1998           kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
1999     return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
2000   }
2001   if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
2002       p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
2003     ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
2004     return ERROR_JPEGR_INVALID_COLORGAMUT;
2005   }
2006   if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
2007     ALOGE("Luma stride must not be smaller than width, stride=%u, width=%u",
2008           p010_image_ptr->luma_stride, p010_image_ptr->width);
2009     return ERROR_JPEGR_INVALID_STRIDE;
2010   }
2011   if (p010_image_ptr->chroma_data != nullptr &&
2012       p010_image_ptr->chroma_stride < p010_image_ptr->width) {
2013     ALOGE("Chroma stride must not be smaller than width, stride=%u, width=%u",
2014           p010_image_ptr->chroma_stride, p010_image_ptr->width);
2015     return ERROR_JPEGR_INVALID_STRIDE;
2016   }
2017   if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
2018     ALOGE("Received nullptr for destination");
2019     return ERROR_JPEGR_BAD_PTR;
2020   }
2021   if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
2022     ALOGE("Invalid hdr transfer function %d", hdr_tf);
2023     return ERROR_JPEGR_INVALID_TRANS_FUNC;
2024   }
2025   if (mMapDimensionScaleFactor <= 0 || mMapDimensionScaleFactor > 128) {
2026     ALOGE("gainmap scale factor is ecpected to be in range (0, 128], received %d",
2027           mMapDimensionScaleFactor);
2028     return ERROR_JPEGR_UNSUPPORTED_MAP_SCALE_FACTOR;
2029   }
2030   if (mMapCompressQuality < 0 || mMapCompressQuality > 100) {
2031     ALOGE("invalid quality factor %d, expects in range [0-100]", mMapCompressQuality);
2032     return ERROR_JPEGR_INVALID_QUALITY_FACTOR;
2033   }
2034   if (!std::isfinite(mGamma) || mGamma <= 0.0f) {
2035     ALOGE("unsupported gainmap gamma %f, expects to be > 0", mGamma);
2036     return ERROR_JPEGR_INVALID_GAMMA;
2037   }
2038   if (mEncPreset != UHDR_USAGE_REALTIME && mEncPreset != UHDR_USAGE_BEST_QUALITY) {
2039     ALOGE("invalid preset %d, expects one of {UHDR_USAGE_REALTIME, UHDR_USAGE_BEST_QUALITY}",
2040           mEncPreset);
2041     return ERROR_JPEGR_INVALID_ENC_PRESET;
2042   }
2043   if (!std::isfinite(mMinContentBoost) || !std::isfinite(mMaxContentBoost) ||
2044       mMaxContentBoost < mMinContentBoost || mMinContentBoost <= 0.0f) {
2045     ALOGE("Invalid min boost / max boost configuration. Configured max boost %f, min boost %f",
2046           mMaxContentBoost, mMinContentBoost);
2047     return ERROR_JPEGR_INVALID_DISPLAY_BOOST;
2048   }
2049   if ((!std::isfinite(mTargetDispPeakBrightness) ||
2050        mTargetDispPeakBrightness < ultrahdr::kSdrWhiteNits ||
2051        mTargetDispPeakBrightness > ultrahdr::kPqMaxNits) &&
2052       mTargetDispPeakBrightness != -1.0f) {
2053     ALOGE("unexpected target display peak brightness nits %f, expects to be with in range [%f %f]",
2054           mTargetDispPeakBrightness, ultrahdr::kSdrWhiteNits, ultrahdr::kPqMaxNits);
2055     return ERROR_JPEGR_INVALID_TARGET_DISP_PEAK_BRIGHTNESS;
2056   }
2057   if (yuv420_image_ptr == nullptr) {
2058     return JPEGR_NO_ERROR;
2059   }
2060   if (yuv420_image_ptr->data == nullptr) {
2061     ALOGE("Received nullptr for uncompressed 420 image");
2062     return ERROR_JPEGR_BAD_PTR;
2063   }
2064   if (yuv420_image_ptr->luma_stride != 0 &&
2065       yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
2066     ALOGE("Luma stride must not be smaller than width, stride=%u, width=%u",
2067           yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
2068     return ERROR_JPEGR_INVALID_STRIDE;
2069   }
2070   if (yuv420_image_ptr->chroma_data != nullptr &&
2071       yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
2072     ALOGE("Chroma stride must not be smaller than (width / 2), stride=%u, width=%u",
2073           yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
2074     return ERROR_JPEGR_INVALID_STRIDE;
2075   }
2076   if (p010_image_ptr->width != yuv420_image_ptr->width ||
2077       p010_image_ptr->height != yuv420_image_ptr->height) {
2078     ALOGE("Image resolutions mismatch: P010: %ux%u, YUV420: %ux%u", p010_image_ptr->width,
2079           p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
2080     return ERROR_JPEGR_RESOLUTION_MISMATCH;
2081   }
2082   if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
2083       yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
2084     ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
2085     return ERROR_JPEGR_INVALID_COLORGAMUT;
2086   }
2087   return JPEGR_NO_ERROR;
2088 }
2089 
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)2090 status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
2091                                        jr_uncompressed_ptr yuv420_image_ptr,
2092                                        ultrahdr_transfer_function hdr_tf,
2093                                        jr_compressed_ptr dest_ptr, int quality) {
2094   if (quality < 0 || quality > 100) {
2095     ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
2096     return ERROR_JPEGR_INVALID_QUALITY_FACTOR;
2097   }
2098   return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
2099 }
2100 
map_legacy_ct_to_ct(ultrahdr::ultrahdr_transfer_function ct)2101 uhdr_color_transfer_t map_legacy_ct_to_ct(ultrahdr::ultrahdr_transfer_function ct) {
2102   switch (ct) {
2103     case ultrahdr::ULTRAHDR_TF_HLG:
2104       return UHDR_CT_HLG;
2105     case ultrahdr::ULTRAHDR_TF_PQ:
2106       return UHDR_CT_PQ;
2107     case ultrahdr::ULTRAHDR_TF_LINEAR:
2108       return UHDR_CT_LINEAR;
2109     case ultrahdr::ULTRAHDR_TF_SRGB:
2110       return UHDR_CT_SRGB;
2111     default:
2112       return UHDR_CT_UNSPECIFIED;
2113   }
2114 }
2115 
map_legacy_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg)2116 uhdr_color_gamut_t map_legacy_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg) {
2117   switch (cg) {
2118     case ultrahdr::ULTRAHDR_COLORGAMUT_BT2100:
2119       return UHDR_CG_BT_2100;
2120     case ultrahdr::ULTRAHDR_COLORGAMUT_BT709:
2121       return UHDR_CG_BT_709;
2122     case ultrahdr::ULTRAHDR_COLORGAMUT_P3:
2123       return UHDR_CG_DISPLAY_P3;
2124     default:
2125       return UHDR_CG_UNSPECIFIED;
2126   }
2127 }
2128 
map_cg_to_legacy_cg(uhdr_color_gamut_t cg)2129 ultrahdr::ultrahdr_color_gamut map_cg_to_legacy_cg(uhdr_color_gamut_t cg) {
2130   switch (cg) {
2131     case UHDR_CG_BT_2100:
2132       return ultrahdr::ULTRAHDR_COLORGAMUT_BT2100;
2133     case UHDR_CG_BT_709:
2134       return ultrahdr::ULTRAHDR_COLORGAMUT_BT709;
2135     case UHDR_CG_DISPLAY_P3:
2136       return ultrahdr::ULTRAHDR_COLORGAMUT_P3;
2137     default:
2138       return ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
2139   }
2140 }
2141 
2142 /* 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)2143 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
2144                             jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
2145   // validate input arguments
2146   JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality));
2147   if (exif != nullptr && exif->data == nullptr) {
2148     ALOGE("received nullptr for exif metadata");
2149     return ERROR_JPEGR_BAD_PTR;
2150   }
2151 
2152   // clean up input structure for later usage
2153   jpegr_uncompressed_struct p010_image = *p010_image_ptr;
2154   if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
2155   if (!p010_image.chroma_data) {
2156     uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
2157     p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
2158     p010_image.chroma_stride = p010_image.luma_stride;
2159   }
2160 
2161   uhdr_raw_image_t hdr_intent;
2162   hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
2163   hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
2164   hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
2165   hdr_intent.range = p010_image.colorRange;
2166   hdr_intent.w = p010_image.width;
2167   hdr_intent.h = p010_image.height;
2168   hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
2169   hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
2170   hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
2171   hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
2172   hdr_intent.planes[UHDR_PLANE_V] = nullptr;
2173   hdr_intent.stride[UHDR_PLANE_V] = 0;
2174 
2175   uhdr_compressed_image_t output;
2176   output.data = dest->data;
2177   output.data_sz = 0;
2178   output.capacity = dest->maxLength;
2179   output.cg = UHDR_CG_UNSPECIFIED;
2180   output.ct = UHDR_CT_UNSPECIFIED;
2181   output.range = UHDR_CR_UNSPECIFIED;
2182 
2183   uhdr_mem_block_t exifBlock;
2184   if (exif) {
2185     exifBlock.data = exif->data;
2186     exifBlock.data_sz = exifBlock.capacity = exif->length;
2187   }
2188 
2189   auto result = encodeJPEGR(&hdr_intent, &output, quality, exif ? &exifBlock : nullptr);
2190   if (result.error_code == UHDR_CODEC_OK) {
2191     dest->colorGamut = map_cg_to_legacy_cg(output.cg);
2192     dest->length = output.data_sz;
2193   }
2194 
2195   return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
2196 }
2197 
2198 /* 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)2199 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
2200                             jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
2201                             jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
2202   // validate input arguments
2203   if (yuv420_image_ptr == nullptr) {
2204     ALOGE("received nullptr for uncompressed 420 image");
2205     return ERROR_JPEGR_BAD_PTR;
2206   }
2207   if (exif != nullptr && exif->data == nullptr) {
2208     ALOGE("received nullptr for exif metadata");
2209     return ERROR_JPEGR_BAD_PTR;
2210   }
2211   JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality))
2212 
2213   // clean up input structure for later usage
2214   jpegr_uncompressed_struct p010_image = *p010_image_ptr;
2215   if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
2216   if (!p010_image.chroma_data) {
2217     uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
2218     p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
2219     p010_image.chroma_stride = p010_image.luma_stride;
2220   }
2221   uhdr_raw_image_t hdr_intent;
2222   hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
2223   hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
2224   hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
2225   hdr_intent.range = p010_image.colorRange;
2226   hdr_intent.w = p010_image.width;
2227   hdr_intent.h = p010_image.height;
2228   hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
2229   hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
2230   hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
2231   hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
2232   hdr_intent.planes[UHDR_PLANE_V] = nullptr;
2233   hdr_intent.stride[UHDR_PLANE_V] = 0;
2234 
2235   jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
2236   if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
2237   if (!yuv420_image.chroma_data) {
2238     uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
2239     yuv420_image.chroma_data = data + (size_t)yuv420_image.luma_stride * yuv420_image.height;
2240     yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
2241   }
2242   uhdr_raw_image_t sdrRawImg;
2243   sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420;
2244   sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut);
2245   sdrRawImg.ct = UHDR_CT_SRGB;
2246   sdrRawImg.range = yuv420_image.colorRange;
2247   sdrRawImg.w = yuv420_image.width;
2248   sdrRawImg.h = yuv420_image.height;
2249   sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data;
2250   sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride;
2251   sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data;
2252   sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride;
2253   uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
2254   data += (yuv420_image.height * yuv420_image.chroma_stride) / 2;
2255   sdrRawImg.planes[UHDR_PLANE_V] = data;
2256   sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride;
2257   auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg);
2258 
2259   uhdr_compressed_image_t output;
2260   output.data = dest->data;
2261   output.data_sz = 0;
2262   output.capacity = dest->maxLength;
2263   output.cg = UHDR_CG_UNSPECIFIED;
2264   output.ct = UHDR_CT_UNSPECIFIED;
2265   output.range = UHDR_CR_UNSPECIFIED;
2266 
2267   uhdr_mem_block_t exifBlock;
2268   if (exif) {
2269     exifBlock.data = exif->data;
2270     exifBlock.data_sz = exifBlock.capacity = exif->length;
2271   }
2272 
2273   auto result =
2274       encodeJPEGR(&hdr_intent, sdr_intent.get(), &output, quality, exif ? &exifBlock : nullptr);
2275   if (result.error_code == UHDR_CODEC_OK) {
2276     dest->colorGamut = map_cg_to_legacy_cg(output.cg);
2277     dest->length = output.data_sz;
2278   }
2279 
2280   return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
2281 }
2282 
2283 /* 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)2284 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
2285                             jr_uncompressed_ptr yuv420_image_ptr,
2286                             jr_compressed_ptr yuv420jpg_image_ptr,
2287                             ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
2288   // validate input arguments
2289   if (yuv420_image_ptr == nullptr) {
2290     ALOGE("received nullptr for uncompressed 420 image");
2291     return ERROR_JPEGR_BAD_PTR;
2292   }
2293   if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
2294     ALOGE("received nullptr for compressed jpeg image");
2295     return ERROR_JPEGR_BAD_PTR;
2296   }
2297   JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest))
2298 
2299   // clean up input structure for later usage
2300   jpegr_uncompressed_struct p010_image = *p010_image_ptr;
2301   if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
2302   if (!p010_image.chroma_data) {
2303     uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
2304     p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
2305     p010_image.chroma_stride = p010_image.luma_stride;
2306   }
2307   uhdr_raw_image_t hdr_intent;
2308   hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
2309   hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
2310   hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
2311   hdr_intent.range = p010_image.colorRange;
2312   hdr_intent.w = p010_image.width;
2313   hdr_intent.h = p010_image.height;
2314   hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
2315   hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
2316   hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
2317   hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
2318   hdr_intent.planes[UHDR_PLANE_V] = nullptr;
2319   hdr_intent.stride[UHDR_PLANE_V] = 0;
2320 
2321   jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
2322   if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
2323   if (!yuv420_image.chroma_data) {
2324     uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
2325     yuv420_image.chroma_data = data + (size_t)yuv420_image.luma_stride * p010_image.height;
2326     yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
2327   }
2328   uhdr_raw_image_t sdrRawImg;
2329   sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420;
2330   sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut);
2331   sdrRawImg.ct = UHDR_CT_SRGB;
2332   sdrRawImg.range = yuv420_image.colorRange;
2333   sdrRawImg.w = yuv420_image.width;
2334   sdrRawImg.h = yuv420_image.height;
2335   sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data;
2336   sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride;
2337   sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data;
2338   sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride;
2339   uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
2340   data += (yuv420_image.height * yuv420_image.chroma_stride) / 2;
2341   sdrRawImg.planes[UHDR_PLANE_V] = data;
2342   sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride;
2343   auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg);
2344 
2345   uhdr_compressed_image_t input;
2346   input.data = yuv420jpg_image_ptr->data;
2347   input.data_sz = yuv420jpg_image_ptr->length;
2348   input.capacity = yuv420jpg_image_ptr->maxLength;
2349   input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut);
2350   input.ct = UHDR_CT_UNSPECIFIED;
2351   input.range = UHDR_CR_UNSPECIFIED;
2352 
2353   uhdr_compressed_image_t output;
2354   output.data = dest->data;
2355   output.data_sz = 0;
2356   output.capacity = dest->maxLength;
2357   output.cg = UHDR_CG_UNSPECIFIED;
2358   output.ct = UHDR_CT_UNSPECIFIED;
2359   output.range = UHDR_CR_UNSPECIFIED;
2360 
2361   auto result = encodeJPEGR(&hdr_intent, sdr_intent.get(), &input, &output);
2362   if (result.error_code == UHDR_CODEC_OK) {
2363     dest->colorGamut = map_cg_to_legacy_cg(output.cg);
2364     dest->length = output.data_sz;
2365   }
2366 
2367   return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
2368 }
2369 
2370 /* 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)2371 status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
2372                             jr_compressed_ptr yuv420jpg_image_ptr,
2373                             ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
2374   // validate input arguments
2375   if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
2376     ALOGE("received nullptr for compressed jpeg image");
2377     return ERROR_JPEGR_BAD_PTR;
2378   }
2379   JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest))
2380 
2381   // clean up input structure for later usage
2382   jpegr_uncompressed_struct p010_image = *p010_image_ptr;
2383   if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
2384   if (!p010_image.chroma_data) {
2385     uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
2386     p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
2387     p010_image.chroma_stride = p010_image.luma_stride;
2388   }
2389   uhdr_raw_image_t hdr_intent;
2390   hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
2391   hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
2392   hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
2393   hdr_intent.range = p010_image.colorRange;
2394   hdr_intent.w = p010_image.width;
2395   hdr_intent.h = p010_image.height;
2396   hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
2397   hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
2398   hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
2399   hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
2400   hdr_intent.planes[UHDR_PLANE_V] = nullptr;
2401   hdr_intent.stride[UHDR_PLANE_V] = 0;
2402 
2403   uhdr_compressed_image_t input;
2404   input.data = yuv420jpg_image_ptr->data;
2405   input.data_sz = yuv420jpg_image_ptr->length;
2406   input.capacity = yuv420jpg_image_ptr->maxLength;
2407   input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut);
2408   input.ct = UHDR_CT_UNSPECIFIED;
2409   input.range = UHDR_CR_UNSPECIFIED;
2410 
2411   uhdr_compressed_image_t output;
2412   output.data = dest->data;
2413   output.data_sz = 0;
2414   output.capacity = dest->maxLength;
2415   output.cg = UHDR_CG_UNSPECIFIED;
2416   output.ct = UHDR_CT_UNSPECIFIED;
2417   output.range = UHDR_CR_UNSPECIFIED;
2418 
2419   auto result = encodeJPEGR(&hdr_intent, &input, &output);
2420   if (result.error_code == UHDR_CODEC_OK) {
2421     dest->colorGamut = map_cg_to_legacy_cg(output.cg);
2422     dest->length = output.data_sz;
2423   }
2424 
2425   return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
2426 }
2427 
2428 /* Encode API-4 */
encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,jr_compressed_ptr gainmapjpg_image_ptr,ultrahdr_metadata_ptr metadata,jr_compressed_ptr dest)2429 status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
2430                             jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
2431                             jr_compressed_ptr dest) {
2432   if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
2433     ALOGE("received nullptr for compressed jpeg image");
2434     return ERROR_JPEGR_BAD_PTR;
2435   }
2436   if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
2437     ALOGE("received nullptr for compressed gain map");
2438     return ERROR_JPEGR_BAD_PTR;
2439   }
2440   if (dest == nullptr || dest->data == nullptr) {
2441     ALOGE("received nullptr for destination");
2442     return ERROR_JPEGR_BAD_PTR;
2443   }
2444 
2445   uhdr_compressed_image_t input;
2446   input.data = yuv420jpg_image_ptr->data;
2447   input.data_sz = yuv420jpg_image_ptr->length;
2448   input.capacity = yuv420jpg_image_ptr->maxLength;
2449   input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut);
2450   input.ct = UHDR_CT_UNSPECIFIED;
2451   input.range = UHDR_CR_UNSPECIFIED;
2452 
2453   uhdr_compressed_image_t gainmap;
2454   gainmap.data = gainmapjpg_image_ptr->data;
2455   gainmap.data_sz = gainmapjpg_image_ptr->length;
2456   gainmap.capacity = gainmapjpg_image_ptr->maxLength;
2457   gainmap.cg = UHDR_CG_UNSPECIFIED;
2458   gainmap.ct = UHDR_CT_UNSPECIFIED;
2459   gainmap.range = UHDR_CR_UNSPECIFIED;
2460 
2461   uhdr_compressed_image_t output;
2462   output.data = dest->data;
2463   output.data_sz = 0;
2464   output.capacity = dest->maxLength;
2465   output.cg = UHDR_CG_UNSPECIFIED;
2466   output.ct = UHDR_CT_UNSPECIFIED;
2467   output.range = UHDR_CR_UNSPECIFIED;
2468 
2469   uhdr_gainmap_metadata_ext_t meta;
2470   meta.version = metadata->version;
2471   meta.hdr_capacity_max = metadata->hdrCapacityMax;
2472   meta.hdr_capacity_min = metadata->hdrCapacityMin;
2473   meta.gamma = metadata->gamma;
2474   meta.offset_sdr = metadata->offsetSdr;
2475   meta.offset_hdr = metadata->offsetHdr;
2476   meta.max_content_boost = metadata->maxContentBoost;
2477   meta.min_content_boost = metadata->minContentBoost;
2478 
2479   auto result = encodeJPEGR(&input, &gainmap, &meta, &output);
2480   if (result.error_code == UHDR_CODEC_OK) {
2481     dest->colorGamut = map_cg_to_legacy_cg(output.cg);
2482     dest->length = output.data_sz;
2483   }
2484 
2485   return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
2486 }
2487 
2488 /* Decode API */
getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr,jr_info_ptr jpegr_image_info_ptr)2489 status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr) {
2490   if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
2491     ALOGE("received nullptr for compressed jpegr image");
2492     return ERROR_JPEGR_BAD_PTR;
2493   }
2494   if (jpegr_image_info_ptr == nullptr) {
2495     ALOGE("received nullptr for compressed jpegr info struct");
2496     return ERROR_JPEGR_BAD_PTR;
2497   }
2498 
2499   uhdr_compressed_image_t input;
2500   input.data = jpegr_image_ptr->data;
2501   input.data_sz = jpegr_image_ptr->length;
2502   input.capacity = jpegr_image_ptr->maxLength;
2503   input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut);
2504   input.ct = UHDR_CT_UNSPECIFIED;
2505   input.range = UHDR_CR_UNSPECIFIED;
2506 
2507   auto result = getJPEGRInfo(&input, jpegr_image_info_ptr);
2508 
2509   return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
2510 }
2511 
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)2512 status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
2513                             float max_display_boost, jr_exif_ptr exif,
2514                             ultrahdr_output_format output_format,
2515                             jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
2516   if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
2517     ALOGE("received nullptr for compressed jpegr image");
2518     return ERROR_JPEGR_BAD_PTR;
2519   }
2520   if (dest == nullptr || dest->data == nullptr) {
2521     ALOGE("received nullptr for dest image");
2522     return ERROR_JPEGR_BAD_PTR;
2523   }
2524   if (max_display_boost < 1.0f) {
2525     ALOGE("received bad value for max_display_boost %f", max_display_boost);
2526     return ERROR_JPEGR_INVALID_DISPLAY_BOOST;
2527   }
2528   if (exif != nullptr && exif->data == nullptr) {
2529     ALOGE("received nullptr address for exif data");
2530     return ERROR_JPEGR_BAD_PTR;
2531   }
2532   if (gainmap_image_ptr != nullptr && gainmap_image_ptr->data == nullptr) {
2533     ALOGE("received nullptr address for gainmap data");
2534     return ERROR_JPEGR_BAD_PTR;
2535   }
2536   if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
2537     ALOGE("received bad value for output format %d", output_format);
2538     return ERROR_JPEGR_INVALID_OUTPUT_FORMAT;
2539   }
2540 
2541   uhdr_color_transfer_t ct;
2542   uhdr_img_fmt fmt;
2543   if (output_format == ULTRAHDR_OUTPUT_HDR_HLG) {
2544     fmt = UHDR_IMG_FMT_32bppRGBA1010102;
2545     ct = UHDR_CT_HLG;
2546   } else if (output_format == ULTRAHDR_OUTPUT_HDR_PQ) {
2547     fmt = UHDR_IMG_FMT_32bppRGBA1010102;
2548     ct = UHDR_CT_PQ;
2549   } else if (output_format == ULTRAHDR_OUTPUT_HDR_LINEAR) {
2550     fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat;
2551     ct = UHDR_CT_LINEAR;
2552   } else if (output_format == ULTRAHDR_OUTPUT_SDR) {
2553     fmt = UHDR_IMG_FMT_32bppRGBA8888;
2554     ct = UHDR_CT_SRGB;
2555   }
2556 
2557   uhdr_compressed_image_t input;
2558   input.data = jpegr_image_ptr->data;
2559   input.data_sz = jpegr_image_ptr->length;
2560   input.capacity = jpegr_image_ptr->maxLength;
2561   input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut);
2562   input.ct = UHDR_CT_UNSPECIFIED;
2563   input.range = UHDR_CR_UNSPECIFIED;
2564 
2565   jpeg_info_struct primary_image;
2566   jpeg_info_struct gainmap_image;
2567   jpegr_info_struct jpegr_info;
2568   jpegr_info.primaryImgInfo = &primary_image;
2569   jpegr_info.gainmapImgInfo = &gainmap_image;
2570   if (getJPEGRInfo(&input, &jpegr_info).error_code != UHDR_CODEC_OK) return JPEGR_UNKNOWN_ERROR;
2571 
2572   if (exif != nullptr) {
2573     if (exif->length < primary_image.exifData.size()) {
2574       return ERROR_JPEGR_BUFFER_TOO_SMALL;
2575     }
2576     memcpy(exif->data, primary_image.exifData.data(), primary_image.exifData.size());
2577     exif->length = primary_image.exifData.size();
2578   }
2579 
2580   uhdr_raw_image_t output;
2581   output.fmt = fmt;
2582   output.cg = UHDR_CG_UNSPECIFIED;
2583   output.ct = UHDR_CT_UNSPECIFIED;
2584   output.range = UHDR_CR_UNSPECIFIED;
2585   output.w = jpegr_info.width;
2586   output.h = jpegr_info.height;
2587   output.planes[UHDR_PLANE_PACKED] = dest->data;
2588   output.stride[UHDR_PLANE_PACKED] = jpegr_info.width;
2589   output.planes[UHDR_PLANE_U] = nullptr;
2590   output.stride[UHDR_PLANE_U] = 0;
2591   output.planes[UHDR_PLANE_V] = nullptr;
2592   output.stride[UHDR_PLANE_V] = 0;
2593 
2594   uhdr_raw_image_t output_gm;
2595   if (gainmap_image_ptr) {
2596     output.fmt =
2597         gainmap_image.numComponents == 1 ? UHDR_IMG_FMT_8bppYCbCr400 : UHDR_IMG_FMT_24bppRGB888;
2598     output.cg = UHDR_CG_UNSPECIFIED;
2599     output.ct = UHDR_CT_UNSPECIFIED;
2600     output.range = UHDR_CR_UNSPECIFIED;
2601     output.w = gainmap_image.width;
2602     output.h = gainmap_image.height;
2603     output.planes[UHDR_PLANE_PACKED] = gainmap_image_ptr->data;
2604     output.stride[UHDR_PLANE_PACKED] = gainmap_image.width;
2605     output.planes[UHDR_PLANE_U] = nullptr;
2606     output.stride[UHDR_PLANE_U] = 0;
2607     output.planes[UHDR_PLANE_V] = nullptr;
2608     output.stride[UHDR_PLANE_V] = 0;
2609   }
2610 
2611   uhdr_gainmap_metadata_ext_t meta;
2612   auto result = decodeJPEGR(&input, &output, max_display_boost, ct, fmt,
2613                             gainmap_image_ptr ? &output_gm : nullptr, metadata ? &meta : nullptr);
2614 
2615   if (result.error_code == UHDR_CODEC_OK) {
2616     dest->width = output.w;
2617     dest->height = output.h;
2618     dest->colorGamut = map_cg_to_legacy_cg(output.cg);
2619     dest->colorRange = output.range;
2620     dest->pixelFormat = output.fmt;
2621     dest->chroma_data = nullptr;
2622     if (gainmap_image_ptr) {
2623       gainmap_image_ptr->width = output_gm.w;
2624       gainmap_image_ptr->height = output_gm.h;
2625       gainmap_image_ptr->colorGamut = map_cg_to_legacy_cg(output_gm.cg);
2626       gainmap_image_ptr->colorRange = output_gm.range;
2627       gainmap_image_ptr->pixelFormat = output_gm.fmt;
2628       gainmap_image_ptr->chroma_data = nullptr;
2629     }
2630     if (metadata) {
2631       metadata->version = meta.version;
2632       metadata->hdrCapacityMax = meta.hdr_capacity_max;
2633       metadata->hdrCapacityMin = meta.hdr_capacity_min;
2634       metadata->gamma = meta.gamma;
2635       metadata->offsetSdr = meta.offset_sdr;
2636       metadata->offsetHdr = meta.offset_hdr;
2637       metadata->maxContentBoost = meta.max_content_boost;
2638       metadata->minContentBoost = meta.min_content_boost;
2639     }
2640   }
2641 
2642   return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
2643 }
2644 
2645 }  // namespace ultrahdr
2646