Coverage Report

Created: 2025-12-03 07:15

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