Coverage Report

Created: 2026-08-15 08:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libultrahdr/lib/src/avifultrahdr.cpp
Line
Count
Source
1
/*
2
 * Copyright 2024 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 UHDR_ENABLE_HEIF
18
19
#include <cstdlib>
20
#include <map>
21
22
#include "ultrahdr/avifultrahdr.h"
23
#include "ultrahdr/gainmapmath.h"
24
#include "ultrahdr/gainmapmetadata.h"
25
26
0
#define UHDR_CG_BT_601 static_cast<uhdr_color_gamut_t>(3)
27
28
namespace ultrahdr {
29
30
class MemoryWriter {
31
 public:
32
0
  MemoryWriter() : data_(nullptr), size_(0), capacity_(0) {}
33
34
0
  ~MemoryWriter() { free(data_); }
35
36
0
  const uint8_t* data() const { return data_; }
37
38
0
  size_t size() const { return size_; }
39
40
0
  void write(const void* data, size_t size) {
41
0
    if (capacity_ - size_ < size) {
42
0
      size_t new_capacity = capacity_ + size;
43
0
      uint8_t* new_data = static_cast<uint8_t*>(malloc(new_capacity));
44
0
      if (data_) {
45
0
        memcpy(new_data, data_, size_);
46
0
        free(data_);
47
0
      }
48
0
      data_ = new_data;
49
0
      capacity_ = new_capacity;
50
0
    }
51
0
    memcpy(&data_[size_], data, size);
52
0
    size_ += size;
53
0
  }
54
55
 public:
56
  uint8_t* data_;
57
  size_t size_;
58
  size_t capacity_;
59
};
60
61
static struct heif_error writer_write([[maybe_unused]] struct heif_context* ctx, const void* data,
62
0
                                      size_t size, void* userdata) {
63
0
  MemoryWriter* writer = static_cast<MemoryWriter*>(userdata);
64
0
  writer->write(data, size);
65
0
  struct heif_error err {
66
0
    heif_error_Ok, heif_suberror_Unspecified, nullptr
67
0
  };
68
0
  return err;
69
0
}
70
71
static struct heif_error fill_img_plane(heif_image* img, heif_channel channel, void* srcBuffer,
72
0
                                        int width, int height, int srcRowStride) {
73
0
  heif_error err = heif_image_add_plane(img, channel, width, height, 8 /*bitDepth */);
74
0
  if (err.code != heif_error_Ok) return err;
75
76
0
  int dstStride;
77
0
  uint8_t* dstBuffer = heif_image_get_plane(img, channel, &dstStride);
78
0
  uint8_t* srcRow = static_cast<uint8_t*>(srcBuffer);
79
80
0
  for (int y = 0; y < height; y++) {
81
0
    memcpy(dstBuffer, srcRow, width);
82
0
    srcRow += srcRowStride;
83
0
    dstBuffer += dstStride;
84
0
  }
85
0
  return err;
86
0
}
87
88
static struct heif_error copy_img_plane(heif_image* img, heif_channel channel, void* dstBuffer,
89
0
                                        int width, int height, int dstRowStride, int bpp) {
90
0
  if (channel != heif_channel_interleaved && channel != heif_channel_Y &&
91
0
      channel != heif_channel_Cb && channel != heif_channel_Cr) {
92
0
    return {heif_error_Invalid_input, heif_suberror_Nonexisting_image_channel_referenced,
93
0
            "supports copying only heif_channel_interleaved"};
94
0
  }
95
96
0
  int srcStride;
97
0
  uint8_t* srcBuffer = heif_image_get_plane(img, channel, &srcStride);
98
0
  uint8_t* srcRow = static_cast<uint8_t*>(srcBuffer);
99
0
  uint8_t* dstRow = static_cast<uint8_t*>(dstBuffer);
100
101
0
  for (int y = 0; y < height; y++) {
102
0
    memcpy(dstRow, srcRow, width * bpp);
103
0
    srcRow += srcStride;
104
0
    dstRow += dstRowStride;
105
0
  }
106
0
  return {heif_error_Ok, heif_suberror_Unspecified, nullptr};
107
0
}
108
109
/*!\brief map of uhdr color primaries and libheif primaries */
110
static const std::map<uhdr_color_gamut_t, heif_color_primaries> map_primaries = {
111
    {UHDR_CG_BT_709, heif_color_primaries_ITU_R_BT_709_5},
112
    {UHDR_CG_DISPLAY_P3, heif_color_primaries_SMPTE_EG_432_1},
113
    {UHDR_CG_BT_2100, heif_color_primaries_ITU_R_BT_2020_2_and_2100_0},
114
    {(uhdr_color_gamut_t)UHDR_CG_BT_601, heif_color_primaries_ITU_R_BT_601_6},
115
    {UHDR_CG_UNSPECIFIED, heif_color_primaries_unspecified},
116
};
117
118
/*!\brief map of uhdr color transfer and libheif transfer characteristics */
119
static const std::map<uhdr_color_transfer_t, heif_transfer_characteristics> map_transfer = {
120
    {UHDR_CT_LINEAR, heif_transfer_characteristic_linear},
121
    {UHDR_CT_PQ, heif_transfer_characteristic_ITU_R_BT_2100_0_PQ},
122
    {UHDR_CT_HLG, heif_transfer_characteristic_ITU_R_BT_2100_0_HLG},
123
    {UHDR_CT_SRGB, heif_transfer_characteristic_ITU_R_BT_709_5},
124
    {UHDR_CT_UNSPECIFIED, heif_transfer_characteristic_unspecified},
125
};
126
127
/*!\brief map of uhdr color gamut and libheif matrix coefficients */
128
static const std::map<uhdr_color_gamut_t, heif_matrix_coefficients> map_matrix = {
129
    {UHDR_CG_BT_709, heif_matrix_coefficients_ITU_R_BT_709_5},
130
    {UHDR_CG_DISPLAY_P3, heif_matrix_coefficients_chromaticity_derived_non_constant_luminance},
131
    {UHDR_CG_BT_2100, heif_matrix_coefficients_ITU_R_BT_2020_2_non_constant_luminance},
132
    {(uhdr_color_gamut_t)UHDR_CG_BT_601, heif_matrix_coefficients_ITU_R_BT_601_6},
133
    {UHDR_CG_UNSPECIFIED, heif_matrix_coefficients_unspecified},
134
};
135
136
0
static uhdr_color_gamut_t map_primaries_inverse(heif_color_primaries primaries) {
137
0
  for (const auto& [key, value] : map_primaries) {
138
0
    if (value == primaries) return key;
139
0
  }
140
0
  return UHDR_CG_UNSPECIFIED;
141
0
}
142
143
0
static uhdr_color_transfer_t map_transfer_inverse(heif_transfer_characteristics transfer) {
144
0
  for (const auto& [key, value] : map_transfer) {
145
0
    if (value == transfer) return key;
146
0
  }
147
0
  return UHDR_CT_UNSPECIFIED;
148
0
}
149
150
0
static uhdr_color_gamut_t map_matrix_inverse(heif_matrix_coefficients matrix) {
151
0
  for (const auto& [key, value] : map_matrix) {
152
0
    if (value == matrix) return key;
153
0
  }
154
0
  return UHDR_CG_UNSPECIFIED;
155
0
}
156
157
static heif_error map_fmt_to_heif_chroma_vars(uhdr_img_fmt_t fmt, unsigned int w, unsigned h,
158
                                              enum heif_chroma& heif_img_fmt, int& chromaWd,
159
0
                                              int& chromaHt) {
160
0
  if (fmt == UHDR_IMG_FMT_12bppYCbCr420) {
161
0
    heif_img_fmt = heif_chroma_420;
162
0
    chromaWd = w / 2;
163
0
    chromaHt = h / 2;
164
0
  } else if (fmt == UHDR_IMG_FMT_16bppYCbCr422) {
165
0
    heif_img_fmt = heif_chroma_422;
166
0
    chromaWd = w;
167
0
    chromaHt = h / 2;
168
0
  } else if (fmt == UHDR_IMG_FMT_24bppYCbCr444) {
169
0
    heif_img_fmt = heif_chroma_444;
170
0
    chromaWd = w;
171
0
    chromaHt = h;
172
0
  } else {
173
0
    heif_error status;
174
0
    status.code = heif_error_Unsupported_feature;
175
0
    status.subcode = heif_suberror_Unsupported_color_conversion;
176
0
    status.message = "mapping uhdr image format to heif chroma format failed";
177
0
    return status;
178
0
  }
179
0
  return {heif_error_Ok, heif_suberror_Unspecified, nullptr};
180
0
}
181
182
static heif_error map_heif_chroma_vars_to_fmt(enum heif_colorspace colorspace,
183
0
                                              enum heif_chroma chroma_fmt, uhdr_img_fmt_t& fmt) {
184
0
  if (colorspace == heif_colorspace_YCbCr) {
185
0
    if (chroma_fmt == heif_chroma_420) {
186
0
      fmt = UHDR_IMG_FMT_12bppYCbCr420;
187
0
    } else if (chroma_fmt == heif_chroma_422) {
188
0
      fmt = UHDR_IMG_FMT_16bppYCbCr422;
189
0
    } else if (chroma_fmt == heif_chroma_444) {
190
0
      fmt = UHDR_IMG_FMT_24bppYCbCr444;
191
0
    } else {
192
0
      heif_error status;
193
0
      status.code = heif_error_Unsupported_feature;
194
0
      status.subcode = heif_suberror_Unsupported_color_conversion;
195
0
      status.message = "mapping heif image format to uhdr img format failed";
196
0
      return status;
197
0
    }
198
0
  } else if (colorspace == heif_colorspace_monochrome) {
199
0
    fmt = UHDR_IMG_FMT_8bppYCbCr400;
200
0
  } else if (colorspace == heif_colorspace_RGB) {
201
0
    if (chroma_fmt == heif_chroma_interleaved_RGB) {
202
0
      fmt = UHDR_IMG_FMT_24bppRGB888;
203
0
    } else if (chroma_fmt == heif_chroma_interleaved_RGBA) {
204
0
      fmt = UHDR_IMG_FMT_32bppRGBA8888;
205
0
    } else {
206
0
      heif_error status;
207
0
      status.code = heif_error_Unsupported_feature;
208
0
      status.subcode = heif_suberror_Unsupported_color_conversion;
209
0
      status.message = "mapping heif image format to uhdr img format failed";
210
0
      return status;
211
0
    }
212
0
  } else {
213
0
    heif_error status;
214
0
    status.code = heif_error_Unsupported_feature;
215
0
    status.subcode = heif_suberror_Unsupported_color_conversion;
216
0
    status.message = "mapping heif image format to uhdr img format failed";
217
0
    return status;
218
0
  }
219
0
  return {heif_error_Ok, heif_suberror_Unspecified, nullptr};
220
0
}
221
222
0
static heif_error set_internal_color_format(heif_encoder* encoder, enum heif_chroma heif_img_fmt) {
223
0
  struct heif_error err {
224
0
    heif_error_Ok, heif_suberror_Unspecified, nullptr
225
0
  };
226
0
  switch (heif_img_fmt) {
227
0
    case heif_chroma_420:
228
0
    case heif_chroma_monochrome:
229
0
      err = heif_encoder_set_parameter(encoder, "chroma", "420");
230
0
      break;
231
0
    case heif_chroma_422:
232
0
      err = heif_encoder_set_parameter(encoder, "chroma", "422");
233
0
      break;
234
0
    case heif_chroma_444:
235
0
      err = heif_encoder_set_parameter(encoder, "chroma", "444");
236
0
      break;
237
0
    default:
238
0
      err.code = heif_error_Invalid_input;
239
0
      err.subcode = heif_suberror_Unsupported_parameter;
240
0
      err.message = "unsupported heif image format setting";
241
0
      break;
242
0
  }
243
0
  return err;
244
0
}
245
246
static uhdr_error_info_t heif_get_gainmap_metadata(struct heif_image_handle* base_handle,
247
0
                                                   uhdr_gainmap_metadata_ext_t* metadata) {
248
0
  size_t iso_vec_sz = heif_image_handle_get_gain_map_metadata_size(base_handle);
249
0
  std::vector<uint8_t> iso_vec(iso_vec_sz);
250
0
  uhdr_gainmap_metadata_frac metadata_frac;
251
252
0
  heif_error err = heif_image_handle_get_gain_map_metadata(base_handle, iso_vec.data());
253
0
  if (err.code != heif_error_Ok) {
254
0
    uhdr_error_info_t status;
255
0
    status.error_code = UHDR_CODEC_ERROR;
256
0
    status.has_detail = 1;
257
0
    snprintf(status.detail, sizeof status.detail, "%s", err.message);
258
0
    return status;
259
0
  }
260
0
  UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::decodeGainmapMetadata(iso_vec, &metadata_frac))
261
0
  UHDR_ERR_CHECK(
262
0
      uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(&metadata_frac, metadata))
263
0
  return g_no_error;
264
0
}
265
266
AvifUltraHdr::AvifUltraHdr(void* uhdrGLESCtxt, int mapDimensionScaleFactor, int mapCompressQuality,
267
             bool useMultiChannelGainMap, float gamma, uhdr_enc_preset_t preset,
268
             float minContentBoost, float maxContentBoost, float targetDispPeakBrightness,
269
             uhdr_codec_t codec)
270
0
    : UltraHdr(uhdrGLESCtxt, mapDimensionScaleFactor, mapCompressQuality, useMultiChannelGainMap,
271
0
               gamma, preset, minContentBoost, maxContentBoost, targetDispPeakBrightness),
272
0
      mCodec(codec) {}
273
274
/* Encode API-0 */
275
uhdr_error_info_t AvifUltraHdr::encodeAvifUltraHdr(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest,
276
0
                                     int quality, uhdr_mem_block_t* exif) {
277
0
  uhdr_img_fmt_t sdr_intent_fmt;
278
0
  if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
279
0
    sdr_intent_fmt = UHDR_IMG_FMT_12bppYCbCr420;
280
0
  } else if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
281
0
    sdr_intent_fmt = UHDR_IMG_FMT_24bppYCbCr444;
282
0
  } else if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
283
0
             hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
284
0
    sdr_intent_fmt = UHDR_IMG_FMT_32bppRGBA8888;
285
0
  } else {
286
0
    uhdr_error_info_t status;
287
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
288
0
    status.has_detail = 1;
289
0
    snprintf(status.detail, sizeof status.detail, "unsupported hdr intent color format %d",
290
0
             hdr_intent->fmt);
291
0
    return status;
292
0
  }
293
0
  std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent = std::make_unique<uhdr_raw_image_ext_t>(
294
0
      sdr_intent_fmt, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, hdr_intent->w,
295
0
      hdr_intent->h, 64);
296
297
  // tone map
298
0
  UHDR_ERR_CHECK(toneMap(hdr_intent, sdr_intent.get()));
299
300
  // If hdr intent is tonemapped internally, it is observed from quality pov,
301
  // generateGainMapOnePass() is sufficient
302
0
  mEncPreset = UHDR_USAGE_REALTIME;  // overriding the config option
303
304
  // generate gain map
305
0
  uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
306
0
  std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
307
0
  UHDR_ERR_CHECK(generateGainMap(sdr_intent.get(), hdr_intent, &metadata, gainmap,
308
0
                                 sdr_intent->cg == UHDR_CG_DISPLAY_P3 /* sdr_is_601 */,
309
0
                                 false /* use_luminance */));
310
311
  // compress sdr image
312
0
  std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext;
313
0
  uhdr_raw_image_t* sdr_intent_yuv = sdr_intent.get();
314
0
  if (isPixelFormatRgb(sdr_intent->fmt)) {
315
#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
316
    sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent.get());
317
#else
318
0
    sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent.get());
319
0
#endif
320
0
    sdr_intent_yuv = sdr_intent_yuv_ext.get();
321
0
  }
322
323
0
  std::shared_ptr<DataStruct> baseIcc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg);
324
0
  std::shared_ptr<DataStruct> alternateIcc =
325
0
      IccHelper::writeIccProfile(gainmap->ct, gainmap->cg);
326
327
0
  return encodeAvifUltraHdr(sdr_intent_yuv, gainmap.get(), &metadata, dest, quality, exif, baseIcc.get(),
328
0
                     alternateIcc.get());
329
0
}
330
331
/* Encode API-1 */
332
uhdr_error_info_t AvifUltraHdr::encodeAvifUltraHdr(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
333
                                     uhdr_compressed_image_t* dest, int quality,
334
0
                                     uhdr_mem_block_t* exif) {
335
  // generate gain map
336
0
  uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
337
0
  std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
338
0
  UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap,
339
0
                                 sdr_intent->cg == UHDR_CG_DISPLAY_P3 /* sdr_is_601 */));
340
341
0
  std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext;
342
0
  uhdr_raw_image_t* sdr_intent_yuv = sdr_intent;
343
0
  if (isPixelFormatRgb(sdr_intent->fmt)) {
344
#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
345
    sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent);
346
#else
347
0
    sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent);
348
0
#endif
349
0
    sdr_intent_yuv = sdr_intent_yuv_ext.get();
350
0
  }
351
352
0
  std::shared_ptr<DataStruct> baseIcc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg);
353
0
  std::shared_ptr<DataStruct> alternateIcc =
354
0
      IccHelper::writeIccProfile(gainmap->ct, gainmap->cg);
355
356
0
  return encodeAvifUltraHdr(sdr_intent_yuv, gainmap.get(), &metadata, dest, quality, exif, baseIcc.get(),
357
0
                     alternateIcc.get());
358
0
}
359
360
uhdr_error_info_t AvifUltraHdr::encodeAvifUltraHdr(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img,
361
                                     uhdr_gainmap_metadata_ext_t* metadata,
362
                                     uhdr_compressed_image_t* dest, int quality,
363
                                     uhdr_mem_block_t* exif, DataStruct* baseIcc,
364
0
                                     DataStruct* alternateIcc) {
365
0
  uhdr_error_info_t status = g_no_error;
366
0
  heif_encoder* encoder = nullptr;
367
0
  heif_encoding_options* options = nullptr;
368
0
  heif_color_profile_nclx* sdrNclx = nullptr;
369
0
  heif_color_profile_nclx* gainmapNclx = nullptr;
370
0
  heif_color_profile_nclx* hdrNclx = nullptr;
371
0
  heif_image* baseImage = nullptr;
372
0
  heif_image_handle* baseHandle = nullptr;
373
0
  heif_image* secondaryImage = nullptr;
374
0
  heif_image_handle* secondaryHandle = nullptr;
375
0
  int sdr_cg_matrix;
376
0
  uhdr_gainmap_metadata_frac metadata_frac;
377
0
  std::vector<uint8_t> iso_data;
378
379
0
  uhdr_raw_image_t* gainmap_img_yuv = gainmap_img;
380
0
  std::unique_ptr<uhdr_raw_image_ext_t> gainmap_yuv_ext;
381
382
0
  MemoryWriter writer;
383
0
  struct heif_writer w;
384
0
  w.writer_api_version = 1;
385
0
  w.write = writer_write;
386
387
0
  heif_context* ctx = heif_context_alloc();
388
0
  if (!ctx) {
389
0
    status.error_code = UHDR_CODEC_MEM_ERROR;
390
0
    status.has_detail = 1;
391
0
    snprintf(status.detail, sizeof status.detail, "failed to allocate heif context");
392
0
    return status;
393
0
  }
394
395
0
  HEIF_ERR_CHECK(heif_context_get_encoder_for_format(
396
0
      ctx, mCodec == UHDR_CODEC_AVIF ? heif_compression_AV1 : heif_compression_HEVC, &encoder));
397
398
  // set the encoder parameters
399
0
  HEIF_ERR_CHECK(heif_encoder_set_lossy_quality(encoder, quality));
400
401
  // encode the primary image
402
0
  enum heif_chroma heif_img_fmt;
403
0
  int chromaWd, chromaHt;
404
0
  HEIF_ERR_CHECK(map_fmt_to_heif_chroma_vars(sdr_intent->fmt, sdr_intent->w, sdr_intent->h,
405
0
                                             heif_img_fmt, chromaWd, chromaHt));
406
0
  HEIF_ERR_CHECK(heif_image_create(sdr_intent->w, sdr_intent->h, heif_colorspace_YCbCr,
407
0
                                   heif_img_fmt, &baseImage));
408
0
  HEIF_ERR_CHECK(fill_img_plane(baseImage, heif_channel_Y, sdr_intent->planes[UHDR_PLANE_Y],
409
0
                                sdr_intent->w, sdr_intent->h, sdr_intent->stride[UHDR_PLANE_Y]));
410
0
  HEIF_ERR_CHECK(fill_img_plane(baseImage, heif_channel_Cb, sdr_intent->planes[UHDR_PLANE_U],
411
0
                                chromaWd, chromaHt, sdr_intent->stride[UHDR_PLANE_U]));
412
0
  HEIF_ERR_CHECK(fill_img_plane(baseImage, heif_channel_Cr, sdr_intent->planes[UHDR_PLANE_V],
413
0
                                chromaWd, chromaHt, sdr_intent->stride[UHDR_PLANE_V]));
414
0
  if (baseIcc != nullptr) {
415
0
    void* ptr = (uint8_t*)baseIcc->getData() + kICCIdentifierSize;
416
0
    HEIF_ERR_CHECK(heif_image_set_raw_color_profile(baseImage, "prof", ptr,
417
0
                                                    baseIcc->getLength() - kICCIdentifierSize));
418
0
  }
419
0
  sdrNclx = heif_nclx_color_profile_alloc();
420
0
  if (!sdrNclx) {
421
0
    status.error_code = UHDR_CODEC_MEM_ERROR;
422
0
    status.has_detail = 1;
423
0
    snprintf(status.detail, sizeof status.detail,
424
0
             "failed to allocate nclx color profile for base image");
425
0
    goto CleanUp;
426
0
  }
427
0
  HEIF_ERR_CHECK(set_internal_color_format(encoder, heif_img_fmt));
428
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_color_primaries(
429
0
      sdrNclx, map_primaries.find(sdr_intent->cg)->second));
430
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_transfer_characteristics(
431
0
      sdrNclx, map_transfer.find(sdr_intent->ct)->second));
432
0
  sdr_cg_matrix = sdr_intent->cg == UHDR_CG_DISPLAY_P3 ? UHDR_CG_BT_601 : sdr_intent->cg;
433
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_matrix_coefficients(
434
0
      sdrNclx, map_matrix.find((uhdr_color_gamut_t)sdr_cg_matrix)->second));
435
0
  sdrNclx->full_range_flag = sdr_intent->range == UHDR_CR_FULL_RANGE ? true : false;
436
0
  options = heif_encoding_options_alloc();
437
0
  if (!options) {
438
0
    status.error_code = UHDR_CODEC_MEM_ERROR;
439
0
    status.has_detail = 1;
440
0
    snprintf(status.detail, sizeof status.detail,
441
0
             "failed to allocate nclx color profile for base image");
442
0
    goto CleanUp;
443
0
  }
444
0
  options->save_two_colr_boxes_when_ICC_and_nclx_available = 1;
445
0
  options->output_nclx_profile = sdrNclx;
446
0
  HEIF_ERR_CHECK(heif_image_set_nclx_color_profile(baseImage, sdrNclx));
447
0
  HEIF_ERR_CHECK(heif_context_encode_image(ctx, baseImage, encoder, options, &baseHandle));
448
0
  if (exif != nullptr) {
449
0
    HEIF_ERR_CHECK(heif_context_add_exif_metadata(ctx, baseHandle, exif->data, exif->data_sz));
450
0
  }
451
452
  // encode the gain map image
453
0
  if (isUsingMultiChannelGainMap()) {
454
0
    gainmap_yuv_ext = convert_raw_input_to_ycbcr(gainmap_img, true /* use bt601 */);
455
0
    gainmap_img_yuv = gainmap_yuv_ext.get();
456
0
  }
457
0
  if (isUsingMultiChannelGainMap()) {
458
0
    HEIF_ERR_CHECK(map_fmt_to_heif_chroma_vars(gainmap_img_yuv->fmt, gainmap_img_yuv->w,
459
0
                                               gainmap_img_yuv->h, heif_img_fmt, chromaWd,
460
0
                                               chromaHt));
461
0
    HEIF_ERR_CHECK(heif_image_create(gainmap_img_yuv->w, gainmap_img_yuv->h, heif_colorspace_YCbCr,
462
0
                                     heif_img_fmt, &secondaryImage));
463
0
    HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Y,
464
0
                                  gainmap_img_yuv->planes[UHDR_PLANE_Y], gainmap_img_yuv->w,
465
0
                                  gainmap_img_yuv->h, gainmap_img_yuv->stride[UHDR_PLANE_Y]));
466
0
    HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Cb,
467
0
                                  gainmap_img_yuv->planes[UHDR_PLANE_U], chromaWd, chromaHt,
468
0
                                  gainmap_img_yuv->stride[UHDR_PLANE_U]));
469
0
    HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Cr,
470
0
                                  gainmap_img_yuv->planes[UHDR_PLANE_V], chromaWd, chromaHt,
471
0
                                  gainmap_img_yuv->stride[UHDR_PLANE_V]));
472
0
    HEIF_ERR_CHECK(set_internal_color_format(encoder, heif_img_fmt));
473
0
  } else {
474
0
    HEIF_ERR_CHECK(heif_image_create(gainmap_img_yuv->w, gainmap_img_yuv->h,
475
0
                                     heif_colorspace_monochrome, heif_chroma_monochrome,
476
0
                                     &secondaryImage));
477
0
    HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Y,
478
0
                                  gainmap_img_yuv->planes[UHDR_PLANE_Y], gainmap_img_yuv->w,
479
0
                                  gainmap_img_yuv->h, gainmap_img_yuv->stride[UHDR_PLANE_Y]));
480
0
    HEIF_ERR_CHECK(heif_encoder_set_parameter(encoder, "chroma", "420"));
481
0
  }
482
0
  gainmapNclx = heif_nclx_color_profile_alloc();
483
0
  if (!gainmapNclx) {
484
0
    status.error_code = UHDR_CODEC_MEM_ERROR;
485
0
    status.has_detail = 1;
486
0
    snprintf(status.detail, sizeof status.detail,
487
0
             "failed to allocate nclx color profile for base image");
488
0
    goto CleanUp;
489
0
  }
490
0
  HEIF_ERR_CHECK(
491
0
      heif_nclx_color_profile_set_color_primaries(gainmapNclx, heif_color_primaries_unspecified));
492
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_transfer_characteristics(
493
0
      gainmapNclx, heif_transfer_characteristic_unspecified));
494
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_matrix_coefficients(
495
0
      gainmapNclx, map_matrix.find((uhdr_color_gamut_t)UHDR_CG_BT_601)->second));
496
0
  gainmapNclx->full_range_flag = gainmap_img_yuv->range == UHDR_CR_FULL_RANGE ? true : false;
497
0
  options->output_nclx_profile = gainmapNclx;
498
0
  HEIF_ERR_CHECK(heif_image_set_nclx_color_profile(secondaryImage, gainmapNclx));
499
500
0
  status = uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction(metadata, &metadata_frac);
501
0
  if (status.error_code != UHDR_CODEC_OK) goto CleanUp;
502
0
  status = uhdr_gainmap_metadata_frac::encodeGainmapMetadata(&metadata_frac, iso_data);
503
0
  if (status.error_code != UHDR_CODEC_OK) goto CleanUp;
504
  // TODO: pass alternateIcc write color properties of tmap item
505
  // TODO: write clli of tmap item
506
0
  hdrNclx = heif_nclx_color_profile_alloc();
507
0
  if (!hdrNclx) {
508
0
    status.error_code = UHDR_CODEC_MEM_ERROR;
509
0
    status.has_detail = 1;
510
0
    snprintf(status.detail, sizeof status.detail,
511
0
             "failed to allocate nclx color profile for tmap image");
512
0
    goto CleanUp;
513
0
  }
514
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_color_primaries(
515
0
      hdrNclx, map_primaries.find(gainmap_img->cg)->second));
516
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_transfer_characteristics(
517
0
      hdrNclx, map_transfer.find(gainmap_img->ct)->second));
518
0
  HEIF_ERR_CHECK(heif_nclx_color_profile_set_matrix_coefficients(
519
0
      hdrNclx, map_matrix.find(gainmap_img->cg)->second));
520
0
  hdrNclx->full_range_flag = gainmap_img->range == UHDR_CR_FULL_RANGE ? true : false;
521
0
  (void)alternateIcc;
522
0
  HEIF_ERR_CHECK(heif_encoder_set_lossy_quality(encoder, mMapCompressQuality));
523
0
  HEIF_ERR_CHECK(heif_context_encode_gain_map_image(ctx, baseHandle, encoder, secondaryImage,
524
0
                                                    options, iso_data.data(), iso_data.size(),
525
0
                                                    hdrNclx, &secondaryHandle));
526
527
0
  heif_context_write(ctx, &w, &writer);
528
0
  memcpy(dest->data, writer.data(), writer.size());
529
0
  dest->data_sz = dest->capacity = writer.size();
530
531
0
CleanUp:
532
0
  if (baseImage) heif_image_release(baseImage);
533
0
  baseImage = nullptr;
534
0
  if (baseHandle) heif_image_handle_release(baseHandle);
535
0
  baseHandle = nullptr;
536
0
  if (secondaryImage) heif_image_release(secondaryImage);
537
0
  secondaryImage = nullptr;
538
0
  if (secondaryHandle) heif_image_handle_release(secondaryHandle);
539
0
  secondaryHandle = nullptr;
540
0
  if (options) heif_encoding_options_free(options);
541
0
  options = nullptr;
542
0
  if (hdrNclx) heif_nclx_color_profile_free(hdrNclx);
543
0
  hdrNclx = nullptr;
544
0
  if (gainmapNclx) heif_nclx_color_profile_free(gainmapNclx);
545
0
  gainmapNclx = nullptr;
546
0
  if (sdrNclx) heif_nclx_color_profile_free(sdrNclx);
547
0
  sdrNclx = nullptr;
548
0
  if (encoder) heif_encoder_release(encoder);
549
0
  encoder = nullptr;
550
0
  if (ctx) heif_context_free(ctx);
551
0
  ctx = nullptr;
552
553
0
  return status;
554
0
}
555
556
/* Decode API */
557
uhdr_error_info_t AvifUltraHdr::decodeAvifUltraHdr(uhdr_compressed_image_t* uhdr_compressed_img,
558
                                     uhdr_raw_image_t* dest, float max_display_boost,
559
                                     uhdr_color_transfer_t output_ct, uhdr_img_fmt_t output_format,
560
                                     uhdr_raw_image_t* gainmap_img,
561
0
                                     uhdr_gainmap_metadata_t* gainmap_metadata) {
562
0
  uhdr_error_info_t status = g_no_error;
563
0
  struct heif_image_handle* base_handle = nullptr;
564
0
  struct heif_image* sdr_image = nullptr;
565
0
  struct heif_image_handle* gainmap_handle = nullptr;
566
0
  struct heif_image* gainmap_image = nullptr;
567
0
  struct heif_decoding_options* decoding_options = nullptr;
568
0
  heif_color_profile_nclx* nclx = nullptr;
569
0
  uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
570
0
  enum heif_colorspace out_colorspace;
571
0
  enum heif_chroma out_chroma;
572
0
  uhdr_img_fmt_t fmt;
573
0
  int sdr_width = 0, sdr_height = 0;
574
0
  int gainmap_width = 0, gainmap_height = 0;
575
0
  std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent, gainmap;
576
0
  heif_context* ctx = nullptr;
577
0
  heif_error nclx_err;
578
0
  uhdr_color_gamut_t sdr_cg = UHDR_CG_BT_709;
579
0
  uhdr_color_transfer_t sdr_ct = UHDR_CT_SRGB;
580
0
  uhdr_color_range_t sdr_range = UHDR_CR_FULL_RANGE;
581
0
  uhdr_color_gamut_t gm_cg = UHDR_CG_BT_709;
582
0
  uhdr_color_transfer_t gm_ct = UHDR_CT_SRGB;
583
0
  uhdr_color_range_t gm_range = UHDR_CR_FULL_RANGE;
584
0
  heif_error err;
585
586
0
  ctx = heif_context_alloc();
587
0
  if (!ctx) {
588
0
    status.error_code = UHDR_CODEC_MEM_ERROR;
589
0
    status.has_detail = 1;
590
0
    snprintf(status.detail, sizeof status.detail, "failed to allocate heif context");
591
0
    return status;
592
0
  }
593
0
  HEIF_ERR_CHECK(heif_context_read_from_memory_without_copy(
594
0
      ctx, static_cast<const uint8_t*>(uhdr_compressed_img->data), uhdr_compressed_img->data_sz,
595
0
      nullptr))
596
0
  HEIF_ERR_CHECK(heif_context_get_primary_image_handle(ctx, &base_handle))
597
0
  decoding_options = heif_decoding_options_alloc();
598
0
  out_colorspace = heif_colorspace_RGB;
599
0
  out_chroma = heif_chroma_interleaved_RGBA;
600
0
  HEIF_ERR_CHECK(
601
0
      heif_decode_image(base_handle, &sdr_image, out_colorspace, out_chroma, decoding_options))
602
0
  nclx_err = heif_image_handle_get_nclx_color_profile(base_handle, &nclx);
603
0
  if (nclx_err.code != heif_error_Ok) {
604
0
    nclx_err = heif_image_get_nclx_color_profile(sdr_image, &nclx);
605
0
  }
606
0
  HEIF_ERR_CHECK(map_heif_chroma_vars_to_fmt(out_colorspace, out_chroma, fmt))
607
0
  sdr_width = heif_image_handle_get_width(base_handle);
608
0
  sdr_height = heif_image_handle_get_height(base_handle);
609
0
  sdr_cg = (nclx_err.code == heif_error_Ok && nclx != nullptr)
610
0
               ? map_primaries_inverse(nclx->color_primaries)
611
0
               : UHDR_CG_BT_709;
612
0
  sdr_ct = (nclx_err.code == heif_error_Ok && nclx != nullptr)
613
0
               ? map_transfer_inverse(nclx->transfer_characteristics)
614
0
               : UHDR_CT_SRGB;
615
0
  sdr_range = (nclx_err.code == heif_error_Ok && nclx != nullptr)
616
0
                  ? (nclx->full_range_flag ? UHDR_CR_FULL_RANGE : UHDR_CR_LIMITED_RANGE)
617
0
                  : UHDR_CR_FULL_RANGE;
618
0
  sdr_intent = std::make_unique<uhdr_raw_image_ext_t>(
619
0
      fmt, sdr_cg, sdr_ct, sdr_range, sdr_width, sdr_height, 64);
620
0
  if (nclx) {
621
0
    heif_nclx_color_profile_free(nclx);
622
0
    nclx = nullptr;
623
0
  }
624
0
  HEIF_ERR_CHECK(copy_img_plane(sdr_image, heif_channel_interleaved,
625
0
                                sdr_intent->planes[UHDR_PLANE_PACKED], sdr_width, sdr_height,
626
0
                                sdr_intent->stride[UHDR_PLANE_PACKED] * 4, 4))
627
0
  if (gainmap_metadata != nullptr || output_ct != UHDR_CT_SRGB) {
628
0
    HEIF_ERR_CHECK(heif_image_handle_get_gain_map_image_handle(base_handle, &gainmap_handle))
629
0
    status = heif_get_gainmap_metadata(base_handle, &metadata);
630
0
    if (status.error_code != UHDR_CODEC_OK) return status;
631
0
    if (gainmap_metadata != nullptr) {
632
0
      std::copy(metadata.min_content_boost, metadata.min_content_boost + 3,
633
0
                gainmap_metadata->min_content_boost);
634
0
      std::copy(metadata.max_content_boost, metadata.max_content_boost + 3,
635
0
                gainmap_metadata->max_content_boost);
636
0
      std::copy(metadata.gamma, metadata.gamma + 3, gainmap_metadata->gamma);
637
0
      std::copy(metadata.offset_sdr, metadata.offset_sdr + 3, gainmap_metadata->offset_sdr);
638
0
      std::copy(metadata.offset_hdr, metadata.offset_hdr + 3, gainmap_metadata->offset_hdr);
639
0
      gainmap_metadata->hdr_capacity_min = metadata.hdr_capacity_min;
640
0
      gainmap_metadata->hdr_capacity_max = metadata.hdr_capacity_max;
641
0
      gainmap_metadata->use_base_cg = metadata.use_base_cg;
642
0
    }
643
0
    HEIF_ERR_CHECK(heif_image_handle_get_preferred_decoding_colorspace(
644
0
        gainmap_handle, &out_colorspace, &out_chroma))
645
0
    if (out_colorspace != heif_colorspace_monochrome) {
646
0
      out_colorspace = heif_colorspace_RGB;
647
0
      out_chroma = heif_chroma_interleaved_RGBA;
648
0
    }
649
0
    heif_error decode_err = heif_decode_image(gainmap_handle, &gainmap_image, out_colorspace,
650
0
                                              out_chroma, decoding_options);
651
0
    if (decode_err.code != heif_error_Ok && out_colorspace == heif_colorspace_monochrome) {
652
0
      out_colorspace = heif_colorspace_RGB;
653
0
      out_chroma = heif_chroma_interleaved_RGBA;
654
0
      decode_err = heif_decode_image(gainmap_handle, &gainmap_image, out_colorspace, out_chroma,
655
0
                                     decoding_options);
656
0
    }
657
0
    if (decode_err.code != heif_error_Ok) {
658
0
      status.error_code = UHDR_CODEC_ERROR;
659
0
      status.has_detail = 1;
660
0
      snprintf(status.detail, sizeof status.detail, "%s", decode_err.message);
661
0
      goto CleanUp;
662
0
    }
663
0
    nclx_err = heif_image_handle_get_nclx_color_profile(gainmap_handle, &nclx);
664
0
    if (nclx_err.code != heif_error_Ok) {
665
0
      nclx_err = heif_image_get_nclx_color_profile(gainmap_image, &nclx);
666
0
    }
667
0
    HEIF_ERR_CHECK(map_heif_chroma_vars_to_fmt(out_colorspace, out_chroma, fmt))
668
0
    gainmap_width = heif_image_handle_get_width(gainmap_handle);
669
0
    gainmap_height = heif_image_handle_get_height(gainmap_handle);
670
0
    gm_cg = (nclx_err.code == heif_error_Ok && nclx != nullptr)
671
0
                ? map_matrix_inverse(nclx->matrix_coefficients)
672
0
                : UHDR_CG_BT_709;
673
0
    gm_ct = (nclx_err.code == heif_error_Ok && nclx != nullptr)
674
0
                ? map_transfer_inverse(nclx->transfer_characteristics)
675
0
                : UHDR_CT_SRGB;
676
0
    gm_range = (nclx_err.code == heif_error_Ok && nclx != nullptr)
677
0
                   ? (nclx->full_range_flag ? UHDR_CR_FULL_RANGE : UHDR_CR_LIMITED_RANGE)
678
0
                   : UHDR_CR_FULL_RANGE;
679
0
    gainmap = std::make_unique<uhdr_raw_image_ext_t>(
680
0
        fmt, gm_cg, gm_ct, gm_range, gainmap_width, gainmap_height, 64);
681
0
    if (nclx) {
682
0
      heif_nclx_color_profile_free(nclx);
683
0
      nclx = nullptr;
684
0
    }
685
0
    if (out_colorspace == heif_colorspace_monochrome) {
686
0
      HEIF_ERR_CHECK(copy_img_plane(gainmap_image, heif_channel_Y, gainmap->planes[UHDR_PLANE_Y],
687
0
                                    gainmap_width, gainmap_height, gainmap->stride[UHDR_PLANE_Y],
688
0
                                    1))
689
0
    } else {
690
0
      HEIF_ERR_CHECK(copy_img_plane(gainmap_image, heif_channel_interleaved,
691
0
                                    gainmap->planes[UHDR_PLANE_PACKED], gainmap_width,
692
0
                                    gainmap_height, gainmap->stride[UHDR_PLANE_PACKED] * 4, 4))
693
0
    }
694
0
    if (gainmap_img != nullptr) {
695
0
      status = copy_raw_image(gainmap.get(), gainmap_img);
696
0
      if (status.error_code != UHDR_CODEC_OK) goto CleanUp;
697
0
    }
698
0
    err = heif_image_handle_get_derived_image_nclx_color_profile(base_handle, &nclx);
699
0
    if (err.code == heif_error_Ok) {
700
0
      gainmap->cg = map_primaries_inverse(nclx->color_primaries);
701
0
      gainmap->ct = map_transfer_inverse(nclx->transfer_characteristics);
702
0
      gainmap->range = nclx->full_range_flag ? UHDR_CR_FULL_RANGE : UHDR_CR_LIMITED_RANGE;
703
0
      heif_nclx_color_profile_free(nclx);
704
0
      nclx = nullptr;
705
0
    } else {
706
0
      gainmap->cg = sdr_intent->cg;
707
0
      gainmap->ct = sdr_intent->ct;
708
0
      gainmap->range = sdr_intent->range;
709
0
    }
710
0
  }
711
712
0
  if (output_ct != UHDR_CT_SRGB) {
713
0
    status = applyGainMap(sdr_intent.get(), gainmap.get(), &metadata, output_ct, output_format,
714
0
                          max_display_boost, dest);
715
0
  } else {
716
0
    status = copy_raw_image(sdr_intent.get(), dest);
717
0
  }
718
719
0
CleanUp:
720
0
  if (decoding_options) heif_decoding_options_free(decoding_options);
721
0
  decoding_options = nullptr;
722
0
  if (nclx) heif_nclx_color_profile_free(nclx);
723
0
  nclx = nullptr;
724
0
  if (sdr_image) heif_image_release(sdr_image);
725
0
  sdr_image = nullptr;
726
0
  if (gainmap_image) heif_image_release(gainmap_image);
727
0
  gainmap_image = nullptr;
728
0
  if (gainmap_handle) heif_image_handle_release(gainmap_handle);
729
0
  gainmap_handle = nullptr;
730
0
  if (base_handle) heif_image_handle_release(base_handle);
731
0
  base_handle = nullptr;
732
0
  if (ctx) heif_context_free(ctx);
733
0
  ctx = nullptr;
734
735
0
  return status;
736
0
}
737
738
}  // namespace ultrahdr
739
740
#endif