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