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