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