Coverage Report

Created: 2026-09-03 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libultrahdr/lib/src/ultrahdr_api.cpp
Line
Count
Source
1
/*
2
 * Copyright 2024 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7
 * option. This file may not be copied, modified, or distributed
8
 * except according to those terms.
9
 */
10
11
#include <cstdio>
12
#include <cstring>
13
14
#include "ultrahdr_api.h"
15
#include "ultrahdr/ultrahdrcommon.h"
16
#include "ultrahdr/gainmapmath.h"
17
#include "ultrahdr/editorhelper.h"
18
#include "ultrahdr/jpegr.h"
19
20
#ifdef UHDR_ENABLE_HEIF
21
#include "ultrahdr/heifultrahdr.h"
22
#include "ultrahdr/avifultrahdr.h"
23
#include "ultrahdr/gainmapmetadata.h"
24
#endif
25
26
27
#ifdef UHDR_ENABLE_HEIF
28
#include "ultrahdr/heifultrahdr.h"
29
#include "ultrahdr/avifultrahdr.h"
30
#include "ultrahdr/gainmapmetadata.h"
31
#endif
32
33
#include "ultrahdr/jpegrutils.h"
34
35
#include "image_io/base/data_segment_data_source.h"
36
#include "image_io/jpeg/jpeg_info.h"
37
#include "image_io/jpeg/jpeg_info_builder.h"
38
#include "image_io/jpeg/jpeg_marker.h"
39
#include "image_io/jpeg/jpeg_scanner.h"
40
41
using namespace photos_editing_formats::image_io;
42
43
namespace ultrahdr {
44
45
36.6k
uhdr_memory_block::uhdr_memory_block(size_t capacity) {
46
36.6k
  m_buffer = std::make_unique<uint8_t[]>(capacity);
47
36.6k
  m_capacity = capacity;
48
36.6k
}
49
50
uhdr_raw_image_ext::uhdr_raw_image_ext(uhdr_img_fmt_t fmt_, uhdr_color_gamut_t cg_,
51
                                       uhdr_color_transfer_t ct_, uhdr_color_range_t range_,
52
7.88k
                                       unsigned w_, unsigned h_, unsigned align_stride_to) {
53
7.88k
  this->fmt = fmt_;
54
7.88k
  this->cg = cg_;
55
7.88k
  this->ct = ct_;
56
7.88k
  this->range = range_;
57
58
7.88k
  this->w = w_;
59
7.88k
  this->h = h_;
60
61
7.88k
  int aligned_width = ALIGNM(w_, align_stride_to);
62
63
7.88k
  size_t bpp = 1;
64
7.88k
  if (fmt_ == UHDR_IMG_FMT_24bppYCbCrP010 || fmt_ == UHDR_IMG_FMT_30bppYCbCr444) {
65
0
    bpp = 2;
66
7.88k
  } else if (fmt_ == UHDR_IMG_FMT_24bppRGB888) {
67
0
    bpp = 3;
68
7.88k
  } else if (fmt_ == UHDR_IMG_FMT_32bppRGBA8888 || fmt_ == UHDR_IMG_FMT_32bppRGBA1010102) {
69
4.68k
    bpp = 4;
70
4.68k
  } else if (fmt_ == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
71
1.64k
    bpp = 8;
72
1.64k
  }
73
74
7.88k
  size_t plane_1_sz = bpp * aligned_width * h_;
75
7.88k
  size_t plane_2_sz;
76
7.88k
  size_t plane_3_sz;
77
7.88k
  if (fmt_ == UHDR_IMG_FMT_24bppYCbCrP010) {
78
0
    plane_2_sz = (2 /* planes */ * bpp * (aligned_width / 2) * (h_ / 2));
79
0
    plane_3_sz = 0;
80
7.88k
  } else if (fmt_ == UHDR_IMG_FMT_30bppYCbCr444 || fmt_ == UHDR_IMG_FMT_24bppYCbCr444) {
81
0
    plane_2_sz = bpp * aligned_width * h_;
82
0
    plane_3_sz = bpp * aligned_width * h_;
83
7.88k
  } else if (fmt_ == UHDR_IMG_FMT_12bppYCbCr420) {
84
0
    plane_2_sz = (bpp * (aligned_width / 2) * (h_ / 2));
85
0
    plane_3_sz = (bpp * (aligned_width / 2) * (h_ / 2));
86
7.88k
  } else {
87
7.88k
    plane_2_sz = 0;
88
7.88k
    plane_3_sz = 0;
89
7.88k
  }
90
7.88k
  size_t total_size = plane_1_sz + plane_2_sz + plane_3_sz;
91
7.88k
  this->m_block = std::make_unique<uhdr_memory_block_t>(total_size);
92
93
7.88k
  uint8_t* data = this->m_block->m_buffer.get();
94
7.88k
  this->planes[UHDR_PLANE_Y] = data;
95
7.88k
  this->stride[UHDR_PLANE_Y] = aligned_width;
96
7.88k
  if (fmt_ == UHDR_IMG_FMT_24bppYCbCrP010) {
97
0
    this->planes[UHDR_PLANE_UV] = data + plane_1_sz;
98
0
    this->stride[UHDR_PLANE_UV] = aligned_width;
99
0
    this->planes[UHDR_PLANE_V] = nullptr;
100
0
    this->stride[UHDR_PLANE_V] = 0;
101
7.88k
  } else if (fmt_ == UHDR_IMG_FMT_30bppYCbCr444 || fmt_ == UHDR_IMG_FMT_24bppYCbCr444) {
102
0
    this->planes[UHDR_PLANE_U] = data + plane_1_sz;
103
0
    this->stride[UHDR_PLANE_U] = aligned_width;
104
0
    this->planes[UHDR_PLANE_V] = data + plane_1_sz + plane_2_sz;
105
0
    this->stride[UHDR_PLANE_V] = aligned_width;
106
7.88k
  } else if (fmt_ == UHDR_IMG_FMT_12bppYCbCr420) {
107
0
    this->planes[UHDR_PLANE_U] = data + plane_1_sz;
108
0
    this->stride[UHDR_PLANE_U] = aligned_width / 2;
109
0
    this->planes[UHDR_PLANE_V] = data + plane_1_sz + plane_2_sz;
110
0
    this->stride[UHDR_PLANE_V] = aligned_width / 2;
111
7.88k
  } else {
112
7.88k
    this->planes[UHDR_PLANE_U] = nullptr;
113
7.88k
    this->stride[UHDR_PLANE_U] = 0;
114
7.88k
    this->planes[UHDR_PLANE_V] = nullptr;
115
7.88k
    this->stride[UHDR_PLANE_V] = 0;
116
7.88k
  }
117
7.88k
}
118
119
uhdr_compressed_image_ext::uhdr_compressed_image_ext(uhdr_color_gamut_t cg_,
120
                                                     uhdr_color_transfer_t ct_,
121
28.7k
                                                     uhdr_color_range_t range_, size_t size) {
122
28.7k
  this->m_block = std::make_unique<uhdr_memory_block_t>(size);
123
28.7k
  this->data = this->m_block->m_buffer.get();
124
28.7k
  this->capacity = size;
125
28.7k
  this->data_sz = 0;
126
28.7k
  this->cg = cg_;
127
28.7k
  this->ct = ct_;
128
28.7k
  this->range = range_;
129
28.7k
}
130
131
0
uhdr_error_info_t apply_effects(uhdr_encoder_private* enc) {
132
0
  for (auto& it : enc->m_effects) {
133
0
    std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> hdr_img = nullptr;
134
0
    std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> sdr_img = nullptr;
135
136
0
    if (nullptr != dynamic_cast<uhdr_rotate_effect_t*>(it)) {
137
0
      auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second;
138
0
      hdr_img = apply_rotate(dynamic_cast<uhdr_rotate_effect_t*>(it), hdr_raw_entry.get());
139
0
      if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) {
140
0
        auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second;
141
0
        sdr_img = apply_rotate(dynamic_cast<uhdr_rotate_effect_t*>(it), sdr_raw_entry.get());
142
0
      }
143
0
    } else if (nullptr != dynamic_cast<uhdr_mirror_effect_t*>(it)) {
144
0
      auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second;
145
0
      hdr_img = apply_mirror(dynamic_cast<uhdr_mirror_effect_t*>(it), hdr_raw_entry.get());
146
0
      if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) {
147
0
        auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second;
148
0
        sdr_img = apply_mirror(dynamic_cast<uhdr_mirror_effect_t*>(it), sdr_raw_entry.get());
149
0
      }
150
0
    } else if (nullptr != dynamic_cast<uhdr_crop_effect_t*>(it)) {
151
0
      auto crop_effect = dynamic_cast<uhdr_crop_effect_t*>(it);
152
0
      auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second;
153
0
      int left = (std::max)(0, crop_effect->m_left);
154
0
      int right = (std::min)((int)hdr_raw_entry->w, crop_effect->m_right);
155
0
      int crop_width = right - left;
156
0
      if (crop_width <= 0) {
157
0
        uhdr_error_info_t status;
158
0
        status.error_code = UHDR_CODEC_INVALID_PARAM;
159
0
        status.has_detail = 1;
160
0
        snprintf(status.detail, sizeof status.detail,
161
0
                 "unexpected crop dimensions. crop width is expected to be > 0, crop width is %d",
162
0
                 crop_width);
163
0
        return status;
164
0
      }
165
0
      if (crop_width % 2 != 0 && hdr_raw_entry->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
166
0
        uhdr_error_info_t status;
167
0
        status.error_code = UHDR_CODEC_INVALID_PARAM;
168
0
        status.has_detail = 1;
169
0
        snprintf(status.detail, sizeof status.detail,
170
0
                 "unexpected crop dimensions. crop width is expected to even for format "
171
0
                 "{UHDR_IMG_FMT_24bppYCbCrP010}, crop width is %d",
172
0
                 crop_width);
173
0
        return status;
174
0
      }
175
176
0
      int top = (std::max)(0, crop_effect->m_top);
177
0
      int bottom = (std::min)((int)hdr_raw_entry->h, crop_effect->m_bottom);
178
0
      int crop_height = bottom - top;
179
0
      if (crop_height <= 0) {
180
0
        uhdr_error_info_t status;
181
0
        status.error_code = UHDR_CODEC_INVALID_PARAM;
182
0
        status.has_detail = 1;
183
0
        snprintf(status.detail, sizeof status.detail,
184
0
                 "unexpected crop dimensions. crop height is expected to be > 0, crop height is %d",
185
0
                 crop_height);
186
0
        return status;
187
0
      }
188
0
      if (crop_height % 2 != 0 && hdr_raw_entry->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
189
0
        uhdr_error_info_t status;
190
0
        status.error_code = UHDR_CODEC_INVALID_PARAM;
191
0
        status.has_detail = 1;
192
0
        snprintf(status.detail, sizeof status.detail,
193
0
                 "unexpected crop dimensions. crop height is expected to even for format "
194
0
                 "{UHDR_IMG_FMT_24bppYCbCrP010}. crop height is %d",
195
0
                 crop_height);
196
0
        return status;
197
0
      }
198
0
      hdr_img = apply_crop(dynamic_cast<ultrahdr::uhdr_crop_effect_t*>(it), hdr_raw_entry.get(),
199
0
                           left, top, crop_width, crop_height);
200
0
      if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) {
201
0
        auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second;
202
0
        if (crop_width % 2 != 0 && sdr_raw_entry->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
203
0
          uhdr_error_info_t status;
204
0
          status.error_code = UHDR_CODEC_INVALID_PARAM;
205
0
          status.has_detail = 1;
206
0
          snprintf(status.detail, sizeof status.detail,
207
0
                   "unexpected crop dimensions. crop width is expected to even for format "
208
0
                   "{UHDR_IMG_FMT_12bppYCbCr420}, crop width is %d",
209
0
                   crop_width);
210
0
          return status;
211
0
        }
212
0
        if (crop_height % 2 != 0 && sdr_raw_entry->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
213
0
          uhdr_error_info_t status;
214
0
          status.error_code = UHDR_CODEC_INVALID_PARAM;
215
0
          status.has_detail = 1;
216
0
          snprintf(status.detail, sizeof status.detail,
217
0
                   "unexpected crop dimensions. crop height is expected to even for format "
218
0
                   "{UHDR_IMG_FMT_12bppYCbCr420}. crop height is %d",
219
0
                   crop_height);
220
0
          return status;
221
0
        }
222
0
        sdr_img = apply_crop(dynamic_cast<ultrahdr::uhdr_crop_effect_t*>(it), sdr_raw_entry.get(),
223
0
                             left, top, crop_width, crop_height);
224
0
      }
225
0
    } else if (nullptr != dynamic_cast<uhdr_resize_effect_t*>(it)) {
226
0
      auto resize_effect = dynamic_cast<uhdr_resize_effect_t*>(it);
227
0
      int dst_w = resize_effect->m_width;
228
0
      int dst_h = resize_effect->m_height;
229
0
      auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second;
230
0
      if (dst_w <= 0 || dst_h <= 0 || dst_w > ultrahdr::kMaxWidth || dst_h > ultrahdr::kMaxHeight) {
231
0
        uhdr_error_info_t status;
232
0
        status.error_code = UHDR_CODEC_INVALID_PARAM;
233
0
        snprintf(status.detail, sizeof status.detail,
234
0
                 "destination dimensions must be in range (0, %d] x (0, %d]. dest image width "
235
0
                 "is %d, dest image height is %d",
236
0
                 ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, dst_w, dst_h);
237
0
        return status;
238
0
      }
239
0
      if ((dst_w % 2 != 0 || dst_h % 2 != 0) && hdr_raw_entry->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
240
0
        uhdr_error_info_t status;
241
0
        status.error_code = UHDR_CODEC_INVALID_PARAM;
242
0
        snprintf(status.detail, sizeof status.detail,
243
0
                 "destination dimensions cannot be odd for format {UHDR_IMG_FMT_24bppYCbCrP010}. "
244
0
                 "dest image width is %d, dest image height is %d",
245
0
                 dst_w, dst_h);
246
0
        return status;
247
0
      }
248
0
      hdr_img =
249
0
          apply_resize(dynamic_cast<uhdr_resize_effect_t*>(it), hdr_raw_entry.get(), dst_w, dst_h);
250
0
      if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) {
251
0
        auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second;
252
0
        if ((dst_w % 2 != 0 || dst_h % 2 != 0) &&
253
0
            sdr_raw_entry->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
254
0
          uhdr_error_info_t status;
255
0
          status.error_code = UHDR_CODEC_INVALID_PARAM;
256
0
          snprintf(status.detail, sizeof status.detail,
257
0
                   "destination dimensions cannot be odd for format {UHDR_IMG_FMT_12bppYCbCr420}. "
258
0
                   "dest image width is %d, dest image height is %d",
259
0
                   dst_w, dst_h);
260
0
          return status;
261
0
        }
262
0
        sdr_img = apply_resize(dynamic_cast<uhdr_resize_effect_t*>(it), sdr_raw_entry.get(), dst_w,
263
0
                               dst_h);
264
0
      }
265
0
    }
266
267
0
    if (hdr_img == nullptr ||
268
0
        (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end() && sdr_img == nullptr)) {
269
0
      uhdr_error_info_t status;
270
0
      status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
271
0
      status.has_detail = 1;
272
0
      snprintf(status.detail, sizeof status.detail,
273
0
               "encountered unknown error while applying effect %s", it->to_string().c_str());
274
0
      return status;
275
0
    }
276
0
    enc->m_raw_images.insert_or_assign(UHDR_HDR_IMG, std::move(hdr_img));
277
0
    if (sdr_img != nullptr) {
278
0
      enc->m_raw_images.insert_or_assign(UHDR_SDR_IMG, std::move(sdr_img));
279
0
    }
280
0
  }
281
282
0
  return g_no_error;
283
0
}
284
285
0
bool is_resize_effect(const ultrahdr::uhdr_effect_desc_t* effect) {
286
0
  return dynamic_cast<const ultrahdr::uhdr_resize_effect_t*>(effect) != nullptr;
287
0
}
288
289
1.14k
uhdr_error_info_t apply_effects(uhdr_decoder_private* dec) {
290
1.14k
  void *gl_ctxt = nullptr, *disp_texture_ptr = nullptr, *gm_texture_ptr = nullptr;
291
#ifdef UHDR_ENABLE_GLES
292
  if (dec->m_enable_gles) {
293
    gl_ctxt = &dec->m_uhdr_gl_ctxt;
294
    bool texture_created =
295
        dec->m_uhdr_gl_ctxt.mDecodedImgTexture != 0 && dec->m_uhdr_gl_ctxt.mGainmapImgTexture != 0;
296
    bool resize_effect_present = std::find_if(dec->m_effects.begin(), dec->m_effects.end(),
297
                                              is_resize_effect) != dec->m_effects.end();
298
    if (!texture_created && resize_effect_present &&
299
        isBufferDataContiguous(dec->m_decoded_img_buffer.get()) &&
300
        isBufferDataContiguous(dec->m_gainmap_img_buffer.get())) {
301
      dec->m_uhdr_gl_ctxt.mDecodedImgTexture = dec->m_uhdr_gl_ctxt.create_texture(
302
          dec->m_decoded_img_buffer->fmt, dec->m_decoded_img_buffer->w,
303
          dec->m_decoded_img_buffer->h, dec->m_decoded_img_buffer->planes[0]);
304
      dec->m_uhdr_gl_ctxt.mGainmapImgTexture = dec->m_uhdr_gl_ctxt.create_texture(
305
          dec->m_gainmap_img_buffer->fmt, dec->m_gainmap_img_buffer->w,
306
          dec->m_gainmap_img_buffer->h, dec->m_gainmap_img_buffer->planes[0]);
307
    }
308
    disp_texture_ptr = &dec->m_uhdr_gl_ctxt.mDecodedImgTexture;
309
    gm_texture_ptr = &dec->m_uhdr_gl_ctxt.mGainmapImgTexture;
310
  }
311
#endif
312
2.35k
  for (auto& it : dec->m_effects) {
313
2.35k
    std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> disp_img = nullptr;
314
2.35k
    std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> gm_img = nullptr;
315
316
2.35k
    if (nullptr != dynamic_cast<uhdr_rotate_effect_t*>(it)) {
317
602
      disp_img = apply_rotate(dynamic_cast<uhdr_rotate_effect_t*>(it),
318
602
                              dec->m_decoded_img_buffer.get(), gl_ctxt, disp_texture_ptr);
319
602
      gm_img = apply_rotate(dynamic_cast<uhdr_rotate_effect_t*>(it),
320
602
                            dec->m_gainmap_img_buffer.get(), gl_ctxt, gm_texture_ptr);
321
1.74k
    } else if (nullptr != dynamic_cast<uhdr_mirror_effect_t*>(it)) {
322
701
      disp_img = apply_mirror(dynamic_cast<uhdr_mirror_effect_t*>(it),
323
701
                              dec->m_decoded_img_buffer.get(), gl_ctxt, disp_texture_ptr);
324
701
      gm_img = apply_mirror(dynamic_cast<uhdr_mirror_effect_t*>(it),
325
701
                            dec->m_gainmap_img_buffer.get(), gl_ctxt, gm_texture_ptr);
326
1.04k
    } else if (nullptr != dynamic_cast<uhdr_crop_effect_t*>(it)) {
327
420
      auto crop_effect = dynamic_cast<uhdr_crop_effect_t*>(it);
328
420
      uhdr_raw_image_t* disp = dec->m_decoded_img_buffer.get();
329
420
      uhdr_raw_image_t* gm = dec->m_gainmap_img_buffer.get();
330
420
      int left = (std::max)(0, crop_effect->m_left);
331
420
      int right = (std::min)((int)disp->w, crop_effect->m_right);
332
420
      if (right <= left) {
333
262
        uhdr_error_info_t status;
334
262
        status.error_code = UHDR_CODEC_INVALID_PARAM;
335
262
        status.has_detail = 1;
336
262
        snprintf(
337
262
            status.detail, sizeof status.detail,
338
262
            "unexpected crop dimensions. crop right is <= crop left, after crop image width is %d",
339
262
            right - left);
340
262
        return status;
341
262
      }
342
343
158
      int top = (std::max)(0, crop_effect->m_top);
344
158
      int bottom = (std::min)((int)disp->h, crop_effect->m_bottom);
345
158
      if (bottom <= top) {
346
66
        uhdr_error_info_t status;
347
66
        status.error_code = UHDR_CODEC_INVALID_PARAM;
348
66
        status.has_detail = 1;
349
66
        snprintf(
350
66
            status.detail, sizeof status.detail,
351
66
            "unexpected crop dimensions. crop bottom is <= crop top, after crop image height is %d",
352
66
            bottom - top);
353
66
        return status;
354
66
      }
355
356
92
      float wd_ratio = ((float)disp->w) / gm->w;
357
92
      float ht_ratio = ((float)disp->h) / gm->h;
358
92
      int gm_left = (int)(left / wd_ratio);
359
92
      int gm_right = (int)(right / wd_ratio);
360
92
      if (gm_right <= gm_left) {
361
2
        uhdr_error_info_t status;
362
2
        status.error_code = UHDR_CODEC_INVALID_PARAM;
363
2
        status.has_detail = 1;
364
2
        snprintf(status.detail, sizeof status.detail,
365
2
                 "unexpected crop dimensions. crop right is <= crop left for gainmap image, after "
366
2
                 "crop gainmap image width is %d",
367
2
                 gm_right - gm_left);
368
2
        return status;
369
2
      }
370
371
90
      int gm_top = (int)(top / ht_ratio);
372
90
      int gm_bottom = (int)(bottom / ht_ratio);
373
90
      if (gm_bottom <= gm_top) {
374
2
        uhdr_error_info_t status;
375
2
        status.error_code = UHDR_CODEC_INVALID_PARAM;
376
2
        status.has_detail = 1;
377
2
        snprintf(status.detail, sizeof status.detail,
378
2
                 "unexpected crop dimensions. crop bottom is <= crop top for gainmap image, after "
379
2
                 "crop gainmap image height is %d",
380
2
                 gm_bottom - gm_top);
381
2
        return status;
382
2
      }
383
384
88
      disp_img = apply_crop(dynamic_cast<ultrahdr::uhdr_crop_effect_t*>(it), disp, left, top,
385
88
                            right - left, bottom - top, gl_ctxt, disp_texture_ptr);
386
88
      gm_img = apply_crop(dynamic_cast<ultrahdr::uhdr_crop_effect_t*>(it), gm, gm_left, gm_top,
387
88
                          (gm_right - gm_left), (gm_bottom - gm_top), gl_ctxt, gm_texture_ptr);
388
627
    } else if (nullptr != dynamic_cast<uhdr_resize_effect_t*>(it)) {
389
627
      auto resize_effect = dynamic_cast<uhdr_resize_effect_t*>(it);
390
627
      int dst_w = resize_effect->m_width;
391
627
      int dst_h = resize_effect->m_height;
392
627
      float wd_ratio =
393
627
          ((float)dec->m_decoded_img_buffer.get()->w) / dec->m_gainmap_img_buffer.get()->w;
394
627
      float ht_ratio =
395
627
          ((float)dec->m_decoded_img_buffer.get()->h) / dec->m_gainmap_img_buffer.get()->h;
396
627
      int dst_gm_w = (int)(dst_w / wd_ratio);
397
627
      int dst_gm_h = (int)(dst_h / ht_ratio);
398
627
      if (dst_w <= 0 || dst_h <= 0 || dst_gm_w <= 0 || dst_gm_h <= 0 ||
399
599
          dst_w > ultrahdr::kMaxWidth || dst_h > ultrahdr::kMaxHeight ||
400
569
          dst_gm_w > ultrahdr::kMaxWidth || dst_gm_h > ultrahdr::kMaxHeight) {
401
95
        uhdr_error_info_t status;
402
95
        status.error_code = UHDR_CODEC_INVALID_PARAM;
403
95
        snprintf(status.detail, sizeof status.detail,
404
95
                 "destination dimension must be in range (0, %d] x (0, %d]. dest image width is "
405
95
                 "%d, dest image height is %d, dest gainmap width is %d, dest gainmap height is %d",
406
95
                 ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, dst_w, dst_h, dst_gm_w, dst_gm_h);
407
95
        return status;
408
95
      }
409
532
      disp_img =
410
532
          apply_resize(dynamic_cast<uhdr_resize_effect_t*>(it), dec->m_decoded_img_buffer.get(),
411
532
                       dst_w, dst_h, gl_ctxt, disp_texture_ptr);
412
532
      gm_img =
413
532
          apply_resize(dynamic_cast<uhdr_resize_effect_t*>(it), dec->m_gainmap_img_buffer.get(),
414
532
                       dst_gm_w, dst_gm_h, gl_ctxt, gm_texture_ptr);
415
532
    }
416
417
1.92k
    if (disp_img == nullptr || gm_img == nullptr) {
418
0
      uhdr_error_info_t status;
419
0
      status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
420
0
      status.has_detail = 1;
421
0
      snprintf(status.detail, sizeof status.detail,
422
0
               "encountered unknown error while applying effect %s", it->to_string().c_str());
423
0
      return status;
424
0
    }
425
1.92k
    dec->m_decoded_img_buffer = std::move(disp_img);
426
1.92k
    dec->m_gainmap_img_buffer = std::move(gm_img);
427
1.92k
  }
428
714
  return g_no_error;
429
1.14k
}
430
431
16.5k
uhdr_error_info_t uhdr_validate_gainmap_metadata_descriptor(uhdr_gainmap_metadata_t* metadata) {
432
16.5k
  uhdr_error_info_t status = g_no_error;
433
434
16.5k
  if (metadata == nullptr) {
435
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
436
0
    status.has_detail = 1;
437
0
    snprintf(status.detail, sizeof status.detail,
438
0
             "received nullptr for gainmap metadata descriptor");
439
16.5k
  } else {
440
65.8k
    for (int i = 0; i < 3; i++) {
441
49.4k
      if (!std::isfinite(metadata->min_content_boost[i]) ||
442
48.4k
          !std::isfinite(metadata->max_content_boost[i]) ||
443
47.8k
          !std::isfinite(metadata->offset_sdr[i]) || !std::isfinite(metadata->offset_hdr[i]) ||
444
47.8k
          !std::isfinite(metadata->hdr_capacity_min) ||
445
47.0k
          !std::isfinite(metadata->hdr_capacity_max) || !std::isfinite(metadata->gamma[i])) {
446
2.91k
        status.error_code = UHDR_CODEC_INVALID_PARAM;
447
2.91k
        status.has_detail = 1;
448
2.91k
        snprintf(
449
2.91k
            status.detail, sizeof status.detail,
450
2.91k
            "Field(s) of gainmap metadata descriptor are either NaN or infinite. min content "
451
2.91k
            "boost %f, max content boost %f, offset sdr %f, offset hdr %f, hdr capacity min %f, "
452
2.91k
            "hdr capacity max %f, gamma %f",
453
2.91k
            metadata->min_content_boost[i], metadata->max_content_boost[i], metadata->offset_sdr[i],
454
2.91k
            metadata->offset_hdr[i], metadata->hdr_capacity_min, metadata->hdr_capacity_max,
455
2.91k
            metadata->gamma[i]);
456
46.5k
      } else if (metadata->max_content_boost[i] < metadata->min_content_boost[i]) {
457
14.9k
        status.error_code = UHDR_CODEC_INVALID_PARAM;
458
14.9k
        status.has_detail = 1;
459
14.9k
        snprintf(
460
14.9k
            status.detail, sizeof status.detail,
461
14.9k
            "received bad value for content boost max %f, expects to be >= content boost min %f",
462
14.9k
            metadata->max_content_boost[i], metadata->min_content_boost[i]);
463
31.5k
      } else if (metadata->min_content_boost[i] <= 0.0f) {
464
48
        status.error_code = UHDR_CODEC_INVALID_PARAM;
465
48
        status.has_detail = 1;
466
48
        snprintf(status.detail, sizeof status.detail,
467
48
                 "received bad value for min boost %f, expects > 0.0f",
468
48
                 metadata->min_content_boost[i]);
469
48
        return status;
470
31.4k
      } else if (metadata->gamma[i] <= 0.0f) {
471
1.87k
        status.error_code = UHDR_CODEC_INVALID_PARAM;
472
1.87k
        status.has_detail = 1;
473
1.87k
        snprintf(status.detail, sizeof status.detail,
474
1.87k
                 "received bad value for gamma %f, expects > 0.0f", metadata->gamma[i]);
475
29.6k
      } else if (metadata->offset_sdr[i] < 0.0f) {
476
4.00k
        status.error_code = UHDR_CODEC_INVALID_PARAM;
477
4.00k
        status.has_detail = 1;
478
4.00k
        snprintf(status.detail, sizeof status.detail,
479
4.00k
                 "received bad value for offset sdr %f, expects to be >= 0.0f",
480
4.00k
                 metadata->offset_sdr[i]);
481
25.6k
      } else if (metadata->offset_hdr[i] < 0.0f) {
482
1.68k
        status.error_code = UHDR_CODEC_INVALID_PARAM;
483
1.68k
        status.has_detail = 1;
484
1.68k
        snprintf(status.detail, sizeof status.detail,
485
1.68k
                 "received bad value for offset hdr %f, expects to be >= 0.0f",
486
1.68k
                 metadata->offset_hdr[i]);
487
23.9k
      } else if (metadata->hdr_capacity_max <= metadata->hdr_capacity_min) {
488
5.46k
        status.error_code = UHDR_CODEC_INVALID_PARAM;
489
5.46k
        status.has_detail = 1;
490
5.46k
        snprintf(status.detail, sizeof status.detail,
491
5.46k
                 "received bad value for hdr capacity max %f, expects to be > hdr capacity min %f",
492
5.46k
                 metadata->hdr_capacity_max, metadata->hdr_capacity_min);
493
18.4k
      } else if (metadata->hdr_capacity_min < 1.0f) {
494
0
        status.error_code = UHDR_CODEC_INVALID_PARAM;
495
0
        status.has_detail = 1;
496
0
        snprintf(status.detail, sizeof status.detail,
497
0
                 "received bad value for hdr capacity min %f, expects to be >= 1.0f",
498
0
                 metadata->hdr_capacity_min);
499
0
      }
500
49.4k
    }
501
16.5k
  }
502
16.4k
  return status;
503
16.5k
}
504
505
}  // namespace ultrahdr
506
507
28.8k
uhdr_codec_private::~uhdr_codec_private() {
508
28.8k
  for (auto it : m_effects) delete it;
509
28.8k
  m_effects.clear();
510
28.8k
}
511
512
uhdr_error_info_t uhdr_enc_validate_and_set_compressed_img(uhdr_codec_private_t* enc,
513
                                                           uhdr_compressed_image_t* img,
514
0
                                                           uhdr_img_label_t intent) {
515
0
  uhdr_error_info_t status = g_no_error;
516
517
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
518
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
519
0
    status.has_detail = 1;
520
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
521
0
  } else if (img == nullptr) {
522
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
523
0
    status.has_detail = 1;
524
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle");
525
0
  } else if (img->data == nullptr) {
526
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
527
0
    status.has_detail = 1;
528
0
    snprintf(status.detail, sizeof status.detail,
529
0
             "received nullptr for compressed img->data field");
530
0
  } else if (img->capacity < img->data_sz) {
531
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
532
0
    status.has_detail = 1;
533
0
    snprintf(status.detail, sizeof status.detail, "img->capacity %zd is less than img->data_sz %zd",
534
0
             img->capacity, img->data_sz);
535
0
  }
536
0
  if (status.error_code != UHDR_CODEC_OK) return status;
537
538
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
539
0
  if (handle->m_sailed) {
540
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
541
0
    status.has_detail = 1;
542
0
    snprintf(status.detail, sizeof status.detail,
543
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
544
0
             "end state. The context is no longer configurable. To reuse, call reset()");
545
0
    return status;
546
0
  }
547
548
0
  std::shared_ptr<DataSegment> seg =
549
0
      DataSegment::Create(DataRange(0, img->data_sz), static_cast<const uint8_t*>(img->data),
550
0
                          DataSegment::BufferDispositionPolicy::kDontDelete);
551
0
  DataSegmentDataSource data_source(seg);
552
0
  JpegInfoBuilder jpeg_info_builder;
553
0
  JpegScanner jpeg_scanner(nullptr);
554
0
  jpeg_scanner.Run(&data_source, &jpeg_info_builder);
555
0
  data_source.Reset();
556
0
  if (jpeg_scanner.HasError()) {
557
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
558
0
    snprintf(status.detail, sizeof status.detail,
559
0
             "received bad/corrupted jpeg image as part of input configuration");
560
0
    return status;
561
0
  }
562
563
0
  const auto& image_ranges = jpeg_info_builder.GetInfo().GetImageRanges();
564
0
  if (image_ranges.empty()) {
565
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
566
0
    status.has_detail = 1;
567
0
    snprintf(status.detail, sizeof status.detail,
568
0
             "compressed image received as part of input config contains no valid jpeg images");
569
0
    return status;
570
0
  }
571
572
0
  if (image_ranges.size() > 1) {
573
0
    ALOGW(
574
0
        "compressed image received as part of input config contains multiple jpeg images, "
575
0
        "selecting first image for intent %d, rest are ignored",
576
0
        intent);
577
0
  }
578
579
0
  auto entry = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(img->cg, img->ct, img->range,
580
0
                                                                       image_ranges[0].GetLength());
581
0
  memcpy(entry->data, static_cast<uint8_t*>(img->data) + image_ranges[0].GetBegin(),
582
0
         image_ranges[0].GetLength());
583
0
  entry->data_sz = image_ranges[0].GetLength();
584
0
  handle->m_compressed_images.insert_or_assign(intent, std::move(entry));
585
586
0
  return status;
587
0
}
588
589
0
uhdr_codec_private_t* uhdr_create_encoder(void) {
590
0
  uhdr_encoder_private* handle = new uhdr_encoder_private();
591
592
0
  if (handle != nullptr) {
593
0
    uhdr_reset_encoder(handle);
594
0
  }
595
0
  return handle;
596
0
}
597
598
0
void uhdr_release_encoder(uhdr_codec_private_t* enc) {
599
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) != nullptr) {
600
0
    uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
601
0
    delete handle;
602
0
  }
603
0
}
604
605
UHDR_EXTERN uhdr_error_info_t
606
0
uhdr_enc_set_using_multi_channel_gainmap(uhdr_codec_private_t* enc, int use_multi_channel_gainmap) {
607
0
  uhdr_error_info_t status = g_no_error;
608
609
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
610
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
611
0
    status.has_detail = 1;
612
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
613
0
    return status;
614
0
  }
615
616
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
617
618
0
  if (handle->m_sailed) {
619
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
620
0
    status.has_detail = 1;
621
0
    snprintf(status.detail, sizeof status.detail,
622
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
623
0
             "end state. The context is no longer configurable. To reuse, call reset()");
624
0
    return status;
625
0
  }
626
627
0
  handle->m_use_multi_channel_gainmap = use_multi_channel_gainmap;
628
629
0
  return status;
630
0
}
631
632
UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_gainmap_scale_factor(uhdr_codec_private_t* enc,
633
0
                                                                int gainmap_scale_factor) {
634
0
  uhdr_error_info_t status = g_no_error;
635
636
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
637
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
638
0
    status.has_detail = 1;
639
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
640
0
    return status;
641
0
  }
642
643
0
  if (gainmap_scale_factor <= 0 || gainmap_scale_factor > 128) {
644
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
645
0
    status.has_detail = 1;
646
0
    snprintf(status.detail, sizeof status.detail,
647
0
             "gainmap scale factor is expected to be in range (0, 128], received %d",
648
0
             gainmap_scale_factor);
649
0
    return status;
650
0
  }
651
652
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
653
654
0
  if (handle->m_sailed) {
655
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
656
0
    status.has_detail = 1;
657
0
    snprintf(status.detail, sizeof status.detail,
658
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
659
0
             "end state. The context is no longer configurable. To reuse, call reset()");
660
0
    return status;
661
0
  }
662
663
0
  handle->m_gainmap_scale_factor = gainmap_scale_factor;
664
665
0
  return status;
666
0
}
667
668
0
UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_gainmap_gamma(uhdr_codec_private_t* enc, float gamma) {
669
0
  uhdr_error_info_t status = g_no_error;
670
671
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
672
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
673
0
    status.has_detail = 1;
674
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
675
0
    return status;
676
0
  }
677
678
0
  if (!std::isfinite(gamma) || gamma <= 0.0f) {
679
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
680
0
    status.has_detail = 1;
681
0
    snprintf(status.detail, sizeof status.detail, "unsupported gainmap gamma %f, expects to be > 0",
682
0
             gamma);
683
0
    return status;
684
0
  }
685
686
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
687
688
0
  if (handle->m_sailed) {
689
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
690
0
    status.has_detail = 1;
691
0
    snprintf(status.detail, sizeof status.detail,
692
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
693
0
             "end state. The context is no longer configurable. To reuse, call reset()");
694
0
    return status;
695
0
  }
696
697
0
  handle->m_gamma = gamma;
698
699
0
  return status;
700
0
}
701
702
0
uhdr_error_info_t uhdr_enc_set_preset(uhdr_codec_private_t* enc, uhdr_enc_preset_t preset) {
703
0
  uhdr_error_info_t status = g_no_error;
704
705
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
706
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
707
0
    status.has_detail = 1;
708
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
709
0
    return status;
710
0
  }
711
712
0
  if (preset != UHDR_USAGE_REALTIME && preset != UHDR_USAGE_BEST_QUALITY) {
713
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
714
0
    status.has_detail = 1;
715
0
    snprintf(status.detail, sizeof status.detail,
716
0
             "invalid preset %d, expects one of {UHDR_USAGE_REALTIME, UHDR_USAGE_BEST_QUALITY}",
717
0
             preset);
718
0
    return status;
719
0
  }
720
721
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
722
723
0
  if (handle->m_sailed) {
724
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
725
0
    status.has_detail = 1;
726
0
    snprintf(status.detail, sizeof status.detail,
727
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
728
0
             "end state. The context is no longer configurable. To reuse, call reset()");
729
0
    return status;
730
0
  }
731
732
0
  handle->m_enc_preset = preset;
733
734
0
  return status;
735
0
}
736
737
uhdr_error_info_t uhdr_enc_set_min_max_content_boost(uhdr_codec_private_t* enc, float min_boost,
738
0
                                                     float max_boost) {
739
0
  uhdr_error_info_t status = g_no_error;
740
741
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
742
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
743
0
    status.has_detail = 1;
744
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
745
0
    return status;
746
0
  }
747
748
0
  if (!std::isfinite(min_boost) || !std::isfinite(max_boost)) {
749
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
750
0
    status.has_detail = 1;
751
0
    snprintf(status.detail, sizeof status.detail,
752
0
             "received an argument with value either NaN or infinite. Configured min boost %f, "
753
0
             "max boost %f",
754
0
             max_boost, min_boost);
755
0
    return status;
756
0
  }
757
758
0
  if (max_boost < min_boost) {
759
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
760
0
    status.has_detail = 1;
761
0
    snprintf(status.detail, sizeof status.detail,
762
0
             "Invalid min boost / max boost configuration. configured max boost %f is less than "
763
0
             "min boost %f",
764
0
             max_boost, min_boost);
765
0
    return status;
766
0
  }
767
768
0
  if (min_boost <= 0.0f) {
769
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
770
0
    status.has_detail = 1;
771
0
    snprintf(status.detail, sizeof status.detail,
772
0
             "Invalid min boost configuration %f, expects > 0.0f", min_boost);
773
0
    return status;
774
0
  }
775
776
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
777
778
0
  if (handle->m_sailed) {
779
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
780
0
    status.has_detail = 1;
781
0
    snprintf(status.detail, sizeof status.detail,
782
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
783
0
             "end state. The context is no longer configurable. To reuse, call reset()");
784
0
    return status;
785
0
  }
786
787
0
  handle->m_min_content_boost = min_boost;
788
0
  handle->m_max_content_boost = max_boost;
789
790
0
  return status;
791
0
}
792
793
uhdr_error_info_t uhdr_enc_set_target_display_peak_brightness(uhdr_codec_private_t* enc,
794
0
                                                              float nits) {
795
0
  uhdr_error_info_t status = g_no_error;
796
797
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
798
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
799
0
    status.has_detail = 1;
800
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
801
0
    return status;
802
0
  }
803
804
0
  if (!std::isfinite(nits) || nits < ultrahdr::kSdrWhiteNits || nits > ultrahdr::kPqMaxNits) {
805
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
806
0
    status.has_detail = 1;
807
0
    snprintf(
808
0
        status.detail, sizeof status.detail,
809
0
        "unexpected target display peak brightness nits %f, expects to be with in range [%f, %f]",
810
0
        nits, ultrahdr::kSdrWhiteNits, ultrahdr::kPqMaxNits);
811
0
  }
812
813
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
814
815
0
  if (handle->m_sailed) {
816
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
817
0
    status.has_detail = 1;
818
0
    snprintf(status.detail, sizeof status.detail,
819
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
820
0
             "end state. The context is no longer configurable. To reuse, call reset()");
821
0
    return status;
822
0
  }
823
824
0
  handle->m_target_disp_max_brightness = nits;
825
826
0
  return status;
827
0
}
828
829
uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc, uhdr_raw_image_t* img,
830
0
                                         uhdr_img_label_t intent) {
831
0
  uhdr_error_info_t status = g_no_error;
832
833
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
834
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
835
0
    status.has_detail = 1;
836
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
837
0
  } else if (img == nullptr) {
838
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
839
0
    status.has_detail = 1;
840
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for raw image handle");
841
0
  } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG) {
842
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
843
0
    status.has_detail = 1;
844
0
    snprintf(status.detail, sizeof status.detail,
845
0
             "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG}", intent);
846
0
  } else if (intent == UHDR_HDR_IMG && (img->fmt != UHDR_IMG_FMT_24bppYCbCrP010 &&
847
0
                                        img->fmt != UHDR_IMG_FMT_32bppRGBA1010102 &&
848
0
                                        img->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat)) {
849
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
850
0
    status.has_detail = 1;
851
0
    snprintf(status.detail, sizeof status.detail,
852
0
             "unsupported input pixel format for hdr intent %d, expects one of "
853
0
             "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_32bppRGBA1010102, "
854
0
             "UHDR_IMG_FMT_64bppRGBAHalfFloat}",
855
0
             img->fmt);
856
0
  } else if (intent == UHDR_SDR_IMG &&
857
0
             (img->fmt != UHDR_IMG_FMT_12bppYCbCr420 && img->fmt != UHDR_IMG_FMT_32bppRGBA8888)) {
858
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
859
0
    status.has_detail = 1;
860
0
    snprintf(status.detail, sizeof status.detail,
861
0
             "unsupported input pixel format for sdr intent %d, expects one of "
862
0
             "{UHDR_IMG_FMT_12bppYCbCr420, UHDR_IMG_FMT_32bppRGBA8888}",
863
0
             img->fmt);
864
0
  } else if (img->cg != UHDR_CG_BT_2100 && img->cg != UHDR_CG_DISPLAY_P3 &&
865
0
             img->cg != UHDR_CG_BT_709) {
866
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
867
0
    status.has_detail = 1;
868
0
    snprintf(status.detail, sizeof status.detail,
869
0
             "invalid input color gamut %d, expects one of {UHDR_CG_BT_2100, UHDR_CG_DISPLAY_P3, "
870
0
             "UHDR_CG_BT_709}",
871
0
             img->cg);
872
0
  } else if (intent == UHDR_SDR_IMG && img->ct != UHDR_CT_SRGB) {
873
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
874
0
    status.has_detail = 1;
875
0
    snprintf(status.detail, sizeof status.detail,
876
0
             "invalid input color transfer for sdr intent image %d, expects UHDR_CT_SRGB", img->ct);
877
0
  } else if (intent == UHDR_HDR_IMG && img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat &&
878
0
             img->ct != UHDR_CT_LINEAR) {
879
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
880
0
    status.has_detail = 1;
881
0
    snprintf(status.detail, sizeof status.detail,
882
0
             "invalid input color transfer for hdr intent image %d with format "
883
0
             "UHDR_IMG_FMT_64bppRGBAHalfFloat, expects one of {UHDR_CT_LINEAR}",
884
0
             img->ct);
885
0
  } else if (intent == UHDR_HDR_IMG && img->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat &&
886
0
             (img->ct != UHDR_CT_HLG && img->ct != UHDR_CT_PQ)) {
887
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
888
0
    status.has_detail = 1;
889
0
    snprintf(status.detail, sizeof status.detail,
890
0
             "invalid input color transfer for hdr intent image %d with format %d, expects one of "
891
0
             "{UHDR_CT_HLG, UHDR_CT_PQ}",
892
0
             img->fmt, img->ct);
893
0
  } else if ((img->w % 2 != 0 || img->h % 2 != 0) &&
894
0
             (img->fmt == UHDR_IMG_FMT_12bppYCbCr420 || img->fmt == UHDR_IMG_FMT_24bppYCbCrP010)) {
895
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
896
0
    status.has_detail = 1;
897
0
    snprintf(status.detail, sizeof status.detail,
898
0
             "image dimensions cannot be odd for formats {UHDR_IMG_FMT_12bppYCbCr420, "
899
0
             "UHDR_IMG_FMT_24bppYCbCrP010}, received image dimensions %dx%d",
900
0
             img->w, img->h);
901
0
  } else if ((int)img->w < ultrahdr::kMinWidth || (int)img->h < ultrahdr::kMinHeight) {
902
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
903
0
    status.has_detail = 1;
904
0
    snprintf(status.detail, sizeof status.detail,
905
0
             "image dimensions cannot be less than %dx%d, received image dimensions %dx%d",
906
0
             ultrahdr::kMinWidth, ultrahdr::kMinHeight, img->w, img->h);
907
0
  } else if ((int)img->w > ultrahdr::kMaxWidth || (int)img->h > ultrahdr::kMaxHeight) {
908
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
909
0
    status.has_detail = 1;
910
0
    snprintf(status.detail, sizeof status.detail,
911
0
             "image dimensions cannot be larger than %dx%d, received image dimensions %dx%d",
912
0
             ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, img->w, img->h);
913
0
  } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
914
0
    if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_UV] == nullptr) {
915
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
916
0
      status.has_detail = 1;
917
0
      snprintf(status.detail, sizeof status.detail,
918
0
               "received nullptr for data field(s), luma ptr %p, chroma_uv ptr %p",
919
0
               img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_UV]);
920
0
    } else if (img->stride[UHDR_PLANE_Y] < img->w) {
921
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
922
0
      status.has_detail = 1;
923
0
      snprintf(status.detail, sizeof status.detail,
924
0
               "luma stride must not be smaller than width, stride=%d, width=%d",
925
0
               img->stride[UHDR_PLANE_Y], img->w);
926
0
    } else if (img->stride[UHDR_PLANE_UV] < img->w) {
927
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
928
0
      status.has_detail = 1;
929
0
      snprintf(status.detail, sizeof status.detail,
930
0
               "chroma_uv stride must not be smaller than width, stride=%d, width=%d",
931
0
               img->stride[UHDR_PLANE_UV], img->w);
932
0
    } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010 &&
933
0
               (img->range != UHDR_CR_FULL_RANGE && img->range != UHDR_CR_LIMITED_RANGE)) {
934
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
935
0
      status.has_detail = 1;
936
0
      snprintf(status.detail, sizeof status.detail,
937
0
               "invalid range, expects one of {UHDR_CR_FULL_RANGE, UHDR_CR_LIMITED_RANGE}");
938
0
    } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA1010102 && img->range != UHDR_CR_FULL_RANGE) {
939
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
940
0
      status.has_detail = 1;
941
0
      snprintf(status.detail, sizeof status.detail,
942
0
               "invalid range, expects one of {UHDR_CR_FULL_RANGE}");
943
0
    }
944
0
  } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
945
0
    if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_U] == nullptr ||
946
0
        img->planes[UHDR_PLANE_V] == nullptr) {
947
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
948
0
      status.has_detail = 1;
949
0
      snprintf(status.detail, sizeof status.detail,
950
0
               "received nullptr for data field(s) luma ptr %p, chroma_u ptr %p, chroma_v ptr %p",
951
0
               img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_U], img->planes[UHDR_PLANE_V]);
952
0
    } else if (img->stride[UHDR_PLANE_Y] < img->w) {
953
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
954
0
      status.has_detail = 1;
955
0
      snprintf(status.detail, sizeof status.detail,
956
0
               "luma stride must not be smaller than width, stride=%d, width=%d",
957
0
               img->stride[UHDR_PLANE_Y], img->w);
958
0
    } else if (img->stride[UHDR_PLANE_U] < img->w / 2) {
959
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
960
0
      status.has_detail = 1;
961
0
      snprintf(status.detail, sizeof status.detail,
962
0
               "chroma_u stride must not be smaller than width / 2, stride=%d, width=%d",
963
0
               img->stride[UHDR_PLANE_U], img->w);
964
0
    } else if (img->stride[UHDR_PLANE_V] < img->w / 2) {
965
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
966
0
      status.has_detail = 1;
967
0
      snprintf(status.detail, sizeof status.detail,
968
0
               "chroma_v stride must not be smaller than width / 2, stride=%d, width=%d",
969
0
               img->stride[UHDR_PLANE_V], img->w);
970
0
    } else if (img->range != UHDR_CR_FULL_RANGE) {
971
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
972
0
      status.has_detail = 1;
973
0
      snprintf(status.detail, sizeof status.detail,
974
0
               "invalid range, expects one of {UHDR_CR_FULL_RANGE}");
975
0
    }
976
0
  } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || img->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
977
0
             img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
978
0
    if (img->planes[UHDR_PLANE_PACKED] == nullptr) {
979
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
980
0
      status.has_detail = 1;
981
0
      snprintf(status.detail, sizeof status.detail,
982
0
               "received nullptr for data field(s) rgb plane packed ptr %p",
983
0
               img->planes[UHDR_PLANE_PACKED]);
984
0
    } else if (img->stride[UHDR_PLANE_PACKED] < img->w) {
985
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
986
0
      status.has_detail = 1;
987
0
      snprintf(status.detail, sizeof status.detail,
988
0
               "rgb planar stride must not be smaller than width, stride=%d, width=%d",
989
0
               img->stride[UHDR_PLANE_PACKED], img->w);
990
0
    } else if (img->range != UHDR_CR_FULL_RANGE) {
991
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
992
0
      status.has_detail = 1;
993
0
      snprintf(status.detail, sizeof status.detail,
994
0
               "invalid range, expects one of {UHDR_CR_FULL_RANGE}");
995
0
    }
996
0
  }
997
0
  if (status.error_code != UHDR_CODEC_OK) return status;
998
999
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1000
0
  if (intent == UHDR_HDR_IMG &&
1001
0
      handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) {
1002
0
    auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second;
1003
0
    if (img->w != sdr_raw_entry->w || img->h != sdr_raw_entry->h) {
1004
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
1005
0
      status.has_detail = 1;
1006
0
      snprintf(status.detail, sizeof status.detail,
1007
0
               "image resolutions mismatch: hdr intent: %dx%d, sdr intent: %dx%d", img->w, img->h,
1008
0
               sdr_raw_entry->w, sdr_raw_entry->h);
1009
0
      return status;
1010
0
    }
1011
0
  }
1012
0
  if (intent == UHDR_SDR_IMG &&
1013
0
      handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) {
1014
0
    auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second;
1015
0
    if (img->w != hdr_raw_entry->w || img->h != hdr_raw_entry->h) {
1016
0
      status.error_code = UHDR_CODEC_INVALID_PARAM;
1017
0
      status.has_detail = 1;
1018
0
      snprintf(status.detail, sizeof status.detail,
1019
0
               "image resolutions mismatch: sdr intent: %dx%d, hdr intent: %dx%d", img->w, img->h,
1020
0
               hdr_raw_entry->w, hdr_raw_entry->h);
1021
0
      return status;
1022
0
    }
1023
0
  }
1024
0
  if (handle->m_sailed) {
1025
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1026
0
    status.has_detail = 1;
1027
0
    snprintf(status.detail, sizeof status.detail,
1028
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
1029
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1030
0
    return status;
1031
0
  }
1032
1033
0
  std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> entry = ultrahdr::copy_raw_image(img);
1034
0
  if (entry == nullptr) {
1035
0
    status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
1036
0
    status.has_detail = 1;
1037
0
    snprintf(status.detail, sizeof status.detail,
1038
0
             "encountered unknown error during color space conversion");
1039
0
    return status;
1040
0
  }
1041
1042
0
  handle->m_raw_images.insert_or_assign(intent, std::move(entry));
1043
1044
0
  return status;
1045
0
}
1046
1047
uhdr_error_info_t uhdr_enc_set_compressed_image(uhdr_codec_private_t* enc,
1048
                                                uhdr_compressed_image_t* img,
1049
0
                                                uhdr_img_label_t intent) {
1050
0
  uhdr_error_info_t status = g_no_error;
1051
1052
0
  if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG) {
1053
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1054
0
    status.has_detail = 1;
1055
0
    snprintf(status.detail, sizeof status.detail,
1056
0
             "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG}",
1057
0
             intent);
1058
0
  }
1059
1060
0
  return uhdr_enc_validate_and_set_compressed_img(enc, img, intent);
1061
0
}
1062
1063
uhdr_error_info_t uhdr_enc_set_gainmap_image(uhdr_codec_private_t* enc,
1064
                                             uhdr_compressed_image_t* img,
1065
0
                                             uhdr_gainmap_metadata_t* metadata) {
1066
0
  uhdr_error_info_t status = ultrahdr::uhdr_validate_gainmap_metadata_descriptor(metadata);
1067
0
  if (status.error_code != UHDR_CODEC_OK) return status;
1068
1069
0
  status = uhdr_enc_validate_and_set_compressed_img(enc, img, UHDR_GAIN_MAP_IMG);
1070
0
  if (status.error_code != UHDR_CODEC_OK) return status;
1071
1072
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1073
0
  memcpy(&handle->m_metadata, metadata, sizeof *metadata);
1074
1075
0
  return status;
1076
0
}
1077
1078
uhdr_error_info_t uhdr_enc_set_quality(uhdr_codec_private_t* enc, int quality,
1079
0
                                       uhdr_img_label_t intent) {
1080
0
  uhdr_error_info_t status = g_no_error;
1081
1082
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
1083
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1084
0
    status.has_detail = 1;
1085
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1086
0
  } else if (quality < 0 || quality > 100) {
1087
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1088
0
    status.has_detail = 1;
1089
0
    snprintf(status.detail, sizeof status.detail,
1090
0
             "invalid quality factor %d, expects in range [0-100]", quality);
1091
0
  } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG &&
1092
0
             intent != UHDR_GAIN_MAP_IMG) {
1093
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1094
0
    status.has_detail = 1;
1095
0
    snprintf(status.detail, sizeof status.detail,
1096
0
             "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG, "
1097
0
             "UHDR_GAIN_MAP_IMG}",
1098
0
             intent);
1099
0
  }
1100
0
  if (status.error_code != UHDR_CODEC_OK) return status;
1101
1102
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1103
0
  if (handle->m_sailed) {
1104
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1105
0
    status.has_detail = 1;
1106
0
    snprintf(status.detail, sizeof status.detail,
1107
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
1108
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1109
0
    return status;
1110
0
  }
1111
1112
0
  handle->m_quality.insert_or_assign(intent, quality);
1113
1114
0
  return status;
1115
0
}
1116
1117
0
uhdr_error_info_t uhdr_enc_set_exif_data(uhdr_codec_private_t* enc, uhdr_mem_block_t* exif) {
1118
0
  uhdr_error_info_t status = g_no_error;
1119
1120
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
1121
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1122
0
    status.has_detail = 1;
1123
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1124
0
  } else if (exif == nullptr) {
1125
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1126
0
    status.has_detail = 1;
1127
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for exif image handle");
1128
0
  } else if (exif->data == nullptr) {
1129
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1130
0
    status.has_detail = 1;
1131
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for exif->data field");
1132
0
  } else if (exif->capacity < exif->data_sz) {
1133
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1134
0
    status.has_detail = 1;
1135
0
    snprintf(status.detail, sizeof status.detail,
1136
0
             "exif->capacity %zd is less than exif->data_sz %zd", exif->capacity, exif->data_sz);
1137
0
  }
1138
0
  if (status.error_code != UHDR_CODEC_OK) return status;
1139
1140
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1141
0
  if (handle->m_sailed) {
1142
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1143
0
    status.has_detail = 1;
1144
0
    snprintf(status.detail, sizeof status.detail,
1145
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
1146
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1147
0
    return status;
1148
0
  }
1149
1150
0
  uint8_t* data = static_cast<uint8_t*>(exif->data);
1151
0
  std::vector<uint8_t> entry(data, data + exif->data_sz);
1152
0
  handle->m_exif = std::move(entry);
1153
1154
0
  return status;
1155
0
}
1156
1157
0
uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* enc, uhdr_codec_t media_type) {
1158
0
  uhdr_error_info_t status = g_no_error;
1159
1160
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
1161
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1162
0
    status.has_detail = 1;
1163
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1164
0
  }
1165
0
#ifdef UHDR_ENABLE_HEIF
1166
0
  else if (media_type != UHDR_CODEC_JPG && media_type != UHDR_CODEC_AVIF &&
1167
0
           media_type != UHDR_CODEC_HEIF) {
1168
0
    status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1169
0
    status.has_detail = 1;
1170
0
    snprintf(status.detail, sizeof status.detail,
1171
0
             "invalid output format %d, expects one of {UHDR_CODEC_JPG, UHDR_CODEC_AVIF, "
1172
0
             "UHDR_CODEC_HEIF}",
1173
0
             media_type);
1174
0
  }
1175
#else
1176
  else if (media_type != UHDR_CODEC_JPG) {
1177
    status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1178
    status.has_detail = 1;
1179
    snprintf(status.detail, sizeof status.detail,
1180
             "invalid output format %d, expects {UHDR_CODEC_JPG}", media_type);
1181
  }
1182
#endif
1183
0
  if (status.error_code != UHDR_CODEC_OK) return status;
1184
1185
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1186
0
  if (handle->m_sailed) {
1187
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1188
0
    status.has_detail = 1;
1189
0
    snprintf(status.detail, sizeof status.detail,
1190
0
             "An earlier call to uhdr_encode() has switched the context from configurable state to "
1191
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1192
0
    return status;
1193
0
  }
1194
1195
0
  handle->m_output_format = media_type;
1196
1197
0
  return status;
1198
0
}
1199
1200
0
uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) {
1201
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
1202
0
    uhdr_error_info_t status;
1203
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1204
0
    status.has_detail = 1;
1205
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1206
0
    return status;
1207
0
  }
1208
1209
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1210
1211
0
  if (handle->m_sailed) {
1212
0
    return handle->m_encode_call_status;
1213
0
  }
1214
1215
0
  handle->m_sailed = true;
1216
1217
0
  uhdr_error_info_t& status = handle->m_encode_call_status;
1218
1219
0
  if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() &&
1220
0
      handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) {
1221
0
    if (handle->m_effects.size() != 0) {
1222
0
      status.error_code = UHDR_CODEC_INVALID_OPERATION;
1223
0
      status.has_detail = 1;
1224
0
      snprintf(status.detail, sizeof status.detail,
1225
0
               "image effects are not enabled for inputs with compressed intent");
1226
0
      return status;
1227
0
    }
1228
0
  } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) {
1229
0
    if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() &&
1230
0
        handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1231
      // api - 0
1232
0
      if (handle->m_effects.size() != 0) {
1233
0
        status = ultrahdr::apply_effects(handle);
1234
0
        if (status.error_code != UHDR_CODEC_OK) return status;
1235
0
      }
1236
0
    } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) !=
1237
0
                   handle->m_compressed_images.end() &&
1238
0
               handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1239
0
      if (handle->m_effects.size() != 0) {
1240
0
        status.error_code = UHDR_CODEC_INVALID_OPERATION;
1241
0
        status.has_detail = 1;
1242
0
        snprintf(status.detail, sizeof status.detail,
1243
0
                 "image effects are not enabled for inputs with compressed intent");
1244
0
        return status;
1245
0
      }
1246
0
    } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) {
1247
0
      if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) {
1248
0
        if (handle->m_effects.size() != 0) {
1249
0
          status = ultrahdr::apply_effects(handle);
1250
0
          if (status.error_code != UHDR_CODEC_OK) return status;
1251
0
        }
1252
0
      } else {
1253
0
        if (handle->m_effects.size() != 0) {
1254
0
          status.error_code = UHDR_CODEC_INVALID_OPERATION;
1255
0
          status.has_detail = 1;
1256
0
          snprintf(status.detail, sizeof status.detail,
1257
0
                   "image effects are not enabled for inputs with compressed intent");
1258
0
          return status;
1259
0
        }
1260
0
      }
1261
0
    }
1262
0
  }
1263
1264
0
  uhdr_mem_block_t exif{};
1265
0
  if (handle->m_exif.size() > 0) {
1266
0
    exif.data = handle->m_exif.data();
1267
0
    exif.capacity = exif.data_sz = handle->m_exif.size();
1268
0
  }
1269
1270
0
  if (handle->m_output_format == UHDR_CODEC_JPG) {
1271
0
    ultrahdr::JpegR jpegr(nullptr, handle->m_gainmap_scale_factor,
1272
0
                          handle->m_quality.find(UHDR_GAIN_MAP_IMG)->second,
1273
0
                          handle->m_use_multi_channel_gainmap, handle->m_gamma,
1274
0
                          handle->m_enc_preset, handle->m_min_content_boost,
1275
0
                          handle->m_max_content_boost, handle->m_target_disp_max_brightness);
1276
0
    if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() &&
1277
0
        handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) {
1278
0
      auto& base_entry = handle->m_compressed_images.find(UHDR_BASE_IMG)->second;
1279
0
      auto& gainmap_entry = handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG)->second;
1280
1281
0
      size_t size =
1282
0
          (std::max)(((size_t)64 * 1024), 2 * (base_entry->data_sz + gainmap_entry->data_sz));
1283
0
      handle->m_compressed_output_buffer = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
1284
0
          UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size);
1285
1286
0
      ultrahdr::uhdr_gainmap_metadata_ext_t metadata(handle->m_metadata, ultrahdr::kJpegrVersion);
1287
1288
      // api - 4
1289
0
      status = jpegr.encodeJPEGR(base_entry.get(), gainmap_entry.get(), &metadata,
1290
0
                                 handle->m_compressed_output_buffer.get());
1291
0
    } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) {
1292
0
      auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second;
1293
1294
0
      size_t size = (std::max)((64u * 1024), hdr_raw_entry->w * hdr_raw_entry->h * 3 * 2);
1295
0
      handle->m_compressed_output_buffer = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
1296
0
          UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size);
1297
1298
0
      if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() &&
1299
0
          handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1300
        // api - 0
1301
0
        status = jpegr.encodeJPEGR(hdr_raw_entry.get(), handle->m_compressed_output_buffer.get(),
1302
0
                                   handle->m_quality.find(UHDR_BASE_IMG)->second,
1303
0
                                   handle->m_exif.size() > 0 ? &exif : nullptr);
1304
0
      } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) !=
1305
0
                     handle->m_compressed_images.end() &&
1306
0
                 handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1307
0
        auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second;
1308
        // api - 3
1309
0
        status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_compressed_entry.get(),
1310
0
                                   handle->m_compressed_output_buffer.get());
1311
0
      } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) {
1312
0
        auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second;
1313
1314
0
        if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) {
1315
          // api - 1
1316
0
          status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_raw_entry.get(),
1317
0
                                     handle->m_compressed_output_buffer.get(),
1318
0
                                     handle->m_quality.find(UHDR_BASE_IMG)->second,
1319
0
                                     handle->m_exif.size() > 0 ? &exif : nullptr);
1320
0
        } else {
1321
0
          auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second;
1322
          // api - 2
1323
0
          status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_raw_entry.get(),
1324
0
                                     sdr_compressed_entry.get(),
1325
0
                                     handle->m_compressed_output_buffer.get());
1326
0
        }
1327
0
      }
1328
0
    } else {
1329
0
      status.error_code = UHDR_CODEC_INVALID_OPERATION;
1330
0
      status.has_detail = 1;
1331
0
      snprintf(status.detail, sizeof status.detail,
1332
0
               "resources required for uhdr_encode() operation are not present");
1333
0
    }
1334
0
  }
1335
0
#ifdef UHDR_ENABLE_HEIF
1336
0
  else if (handle->m_output_format == UHDR_CODEC_AVIF) {
1337
0
    ultrahdr::AvifUltraHdr avifultrahdr(
1338
0
        nullptr, handle->m_gainmap_scale_factor, handle->m_quality.find(UHDR_GAIN_MAP_IMG)->second,
1339
0
        handle->m_use_multi_channel_gainmap, handle->m_gamma, handle->m_enc_preset,
1340
0
        handle->m_min_content_boost, handle->m_max_content_boost,
1341
0
        handle->m_target_disp_max_brightness);
1342
0
    if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() &&
1343
0
        handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) {
1344
0
      status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1345
0
      status.has_detail = 1;
1346
0
      snprintf(status.detail, sizeof status.detail,
1347
0
               "heif/avif encoding is supported only with raw intents");
1348
0
    } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) {
1349
0
      auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second;
1350
1351
0
      size_t size = (std::max)((64u * 1024), hdr_raw_entry->w * hdr_raw_entry->h * 3 * 2);
1352
0
      handle->m_compressed_output_buffer = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
1353
0
          UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size);
1354
1355
0
      if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() &&
1356
0
          handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1357
        // api - 0
1358
0
        status = avifultrahdr.encodeAvifUltraHdr(hdr_raw_entry.get(), handle->m_compressed_output_buffer.get(),
1359
0
                                   handle->m_quality.find(UHDR_BASE_IMG)->second,
1360
0
                                   handle->m_exif.size() > 0 ? &exif : nullptr);
1361
0
      } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) !=
1362
0
                     handle->m_compressed_images.end() &&
1363
0
                 handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1364
0
        status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1365
0
        status.has_detail = 1;
1366
0
        snprintf(status.detail, sizeof status.detail,
1367
0
                 "heif/avif encoding is supported only with raw intents");
1368
0
      } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) {
1369
0
        auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second;
1370
0
        if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) {
1371
          // api - 1
1372
0
          status = avifultrahdr.encodeAvifUltraHdr(hdr_raw_entry.get(), sdr_raw_entry.get(),
1373
0
                                     handle->m_compressed_output_buffer.get(),
1374
0
                                     handle->m_quality.find(UHDR_BASE_IMG)->second,
1375
0
                                     handle->m_exif.size() > 0 ? &exif : nullptr);
1376
0
        } else {
1377
0
          status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1378
0
          status.has_detail = 1;
1379
0
          snprintf(status.detail, sizeof status.detail,
1380
0
                   "heif/avif encoding is supported only with raw intents");
1381
0
        }
1382
0
      }
1383
0
    }
1384
0
  }
1385
0
  else if (handle->m_output_format == UHDR_CODEC_HEIF) {
1386
0
    ultrahdr::HeifUltraHdr heifultrahdr(
1387
0
        nullptr, handle->m_gainmap_scale_factor, handle->m_quality.find(UHDR_GAIN_MAP_IMG)->second,
1388
0
        handle->m_use_multi_channel_gainmap, handle->m_gamma, handle->m_enc_preset,
1389
0
        handle->m_min_content_boost, handle->m_max_content_boost,
1390
0
        handle->m_target_disp_max_brightness);
1391
0
    if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() &&
1392
0
        handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) {
1393
0
      status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1394
0
      status.has_detail = 1;
1395
0
      snprintf(status.detail, sizeof status.detail,
1396
0
               "heif/avif encoding is supported only with raw intents");
1397
0
    } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) {
1398
0
      auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second;
1399
1400
0
      size_t size = (std::max)((64u * 1024), hdr_raw_entry->w * hdr_raw_entry->h * 3 * 2);
1401
0
      handle->m_compressed_output_buffer = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
1402
0
          UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size);
1403
1404
0
      if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() &&
1405
0
          handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1406
        // api - 0
1407
0
        status = heifultrahdr.encodeHeicUltraHdr(hdr_raw_entry.get(), handle->m_compressed_output_buffer.get(),
1408
0
                                   handle->m_quality.find(UHDR_BASE_IMG)->second,
1409
0
                                   handle->m_exif.size() > 0 ? &exif : nullptr);
1410
0
      } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) !=
1411
0
                     handle->m_compressed_images.end() &&
1412
0
                 handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) {
1413
0
        status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1414
0
        status.has_detail = 1;
1415
0
        snprintf(status.detail, sizeof status.detail,
1416
0
                 "heif/avif encoding is supported only with raw intents");
1417
0
      } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) {
1418
0
        auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second;
1419
0
        if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) {
1420
          // api - 1
1421
0
          status = heifultrahdr.encodeHeicUltraHdr(hdr_raw_entry.get(), sdr_raw_entry.get(),
1422
0
                                     handle->m_compressed_output_buffer.get(),
1423
0
                                     handle->m_quality.find(UHDR_BASE_IMG)->second,
1424
0
                                     handle->m_exif.size() > 0 ? &exif : nullptr);
1425
0
        } else {
1426
0
          status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1427
0
          status.has_detail = 1;
1428
0
          snprintf(status.detail, sizeof status.detail,
1429
0
                   "heif/avif encoding is supported only with raw intents");
1430
0
        }
1431
0
      }
1432
0
    }
1433
0
  }
1434
0
#endif
1435
1436
0
  return status;
1437
0
}
1438
1439
0
uhdr_compressed_image_t* uhdr_get_encoded_stream(uhdr_codec_private_t* enc) {
1440
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
1441
0
    return nullptr;
1442
0
  }
1443
1444
0
  uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1445
0
  if (!handle->m_sailed || handle->m_encode_call_status.error_code != UHDR_CODEC_OK) {
1446
0
    return nullptr;
1447
0
  }
1448
1449
0
  return handle->m_compressed_output_buffer.get();
1450
0
}
1451
1452
0
void uhdr_reset_encoder(uhdr_codec_private_t* enc) {
1453
0
  if (dynamic_cast<uhdr_encoder_private*>(enc) != nullptr) {
1454
0
    uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
1455
1456
    // clear entries and restore defaults
1457
0
    for (auto it : handle->m_effects) delete it;
1458
0
    handle->m_effects.clear();
1459
#ifdef UHDR_ENABLE_GLES
1460
    handle->m_uhdr_gl_ctxt.reset_opengl_ctxt();
1461
    handle->m_enable_gles = false;
1462
#endif
1463
0
    handle->m_sailed = false;
1464
0
    handle->m_raw_images.clear();
1465
0
    handle->m_compressed_images.clear();
1466
0
    handle->m_quality.clear();
1467
0
    handle->m_quality.emplace(UHDR_HDR_IMG, ultrahdr::kBaseCompressQualityDefault);
1468
0
    handle->m_quality.emplace(UHDR_SDR_IMG, ultrahdr::kBaseCompressQualityDefault);
1469
0
    handle->m_quality.emplace(UHDR_BASE_IMG, ultrahdr::kBaseCompressQualityDefault);
1470
0
    handle->m_quality.emplace(UHDR_GAIN_MAP_IMG, ultrahdr::kMapCompressQualityDefault);
1471
0
    handle->m_exif.clear();
1472
0
    handle->m_output_format = UHDR_CODEC_JPG;
1473
0
    handle->m_gainmap_scale_factor = ultrahdr::kMapDimensionScaleFactorDefault;
1474
0
    handle->m_use_multi_channel_gainmap = ultrahdr::kUseMultiChannelGainMapDefault;
1475
0
    handle->m_gamma = ultrahdr::kGainMapGammaDefault;
1476
0
    handle->m_enc_preset = ultrahdr::kEncSpeedPresetDefault;
1477
0
    handle->m_min_content_boost = FLT_MIN;
1478
0
    handle->m_max_content_boost = FLT_MAX;
1479
0
    handle->m_target_disp_max_brightness = -1.0f;
1480
1481
0
    handle->m_compressed_output_buffer.reset();
1482
0
    handle->m_encode_call_status = g_no_error;
1483
0
  }
1484
0
}
1485
1486
14.4k
int is_uhdr_image(void* data, int size) {
1487
14.4k
  if (data == nullptr || size < 4) return 0;
1488
1489
14.3k
#define RET_IF_ERR(x)                         \
1490
28.7k
  {                                           \
1491
28.7k
    uhdr_error_info_t status = (x);           \
1492
28.7k
    if (status.error_code != UHDR_CODEC_OK) { \
1493
12.5k
      uhdr_release_decoder(obj);              \
1494
12.5k
      return 0;                               \
1495
12.5k
    }                                         \
1496
28.7k
  }
1497
1498
14.3k
  uhdr_codec_private_t* obj = uhdr_create_decoder();
1499
14.3k
  uhdr_compressed_image_t uhdr_image;
1500
14.3k
  uhdr_image.data = data;
1501
14.3k
  uhdr_image.data_sz = size;
1502
14.3k
  uhdr_image.capacity = size;
1503
14.3k
  uhdr_image.cg = UHDR_CG_UNSPECIFIED;
1504
14.3k
  uhdr_image.ct = UHDR_CT_UNSPECIFIED;
1505
14.3k
  uhdr_image.range = UHDR_CR_UNSPECIFIED;
1506
1507
14.3k
  RET_IF_ERR(uhdr_dec_set_image(obj, &uhdr_image));
1508
14.3k
  RET_IF_ERR(uhdr_dec_probe(obj));
1509
1.84k
#undef RET_IF_ERR
1510
1511
1.84k
  uhdr_release_decoder(obj);
1512
1513
1.84k
  return 1;
1514
14.3k
}
1515
1516
28.8k
uhdr_codec_private_t* uhdr_create_decoder(void) {
1517
28.8k
  uhdr_decoder_private* handle = new uhdr_decoder_private();
1518
1519
28.8k
  if (handle != nullptr) {
1520
28.8k
    uhdr_reset_decoder(handle);
1521
28.8k
  }
1522
28.8k
  return handle;
1523
28.8k
}
1524
1525
28.8k
void uhdr_release_decoder(uhdr_codec_private_t* dec) {
1526
28.8k
  if (dynamic_cast<uhdr_decoder_private*>(dec) != nullptr) {
1527
28.8k
    uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1528
28.8k
    delete handle;
1529
28.8k
  }
1530
28.8k
}
1531
1532
28.8k
uhdr_error_info_t uhdr_dec_set_image(uhdr_codec_private_t* dec, uhdr_compressed_image_t* img) {
1533
28.8k
  uhdr_error_info_t status = g_no_error;
1534
1535
28.8k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1536
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1537
0
    status.has_detail = 1;
1538
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1539
28.8k
  } else if (img == nullptr) {
1540
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1541
0
    status.has_detail = 1;
1542
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle");
1543
28.8k
  } else if (img->data == nullptr) {
1544
42
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1545
42
    status.has_detail = 1;
1546
42
    snprintf(status.detail, sizeof status.detail,
1547
42
             "received nullptr for compressed img->data field");
1548
28.7k
  } else if (img->capacity < img->data_sz) {
1549
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1550
0
    status.has_detail = 1;
1551
0
    snprintf(status.detail, sizeof status.detail, "img->capacity %zd is less than img->data_sz %zd",
1552
0
             img->capacity, img->data_sz);
1553
0
  }
1554
28.8k
  if (status.error_code != UHDR_CODEC_OK) return status;
1555
1556
28.7k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1557
28.7k
  if (handle->m_probed) {
1558
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1559
0
    status.has_detail = 1;
1560
0
    snprintf(status.detail, sizeof status.detail,
1561
0
             "An earlier call to uhdr_decode() has switched the context from configurable state to "
1562
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1563
0
    return status;
1564
0
  }
1565
1566
28.7k
  handle->m_uhdr_compressed_img = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
1567
28.7k
      img->cg, img->ct, img->range, img->data_sz);
1568
28.7k
  memcpy(handle->m_uhdr_compressed_img->data, img->data, img->data_sz);
1569
28.7k
  handle->m_uhdr_compressed_img->data_sz = img->data_sz;
1570
1571
28.7k
  return status;
1572
28.7k
}
1573
1574
14.4k
uhdr_error_info_t uhdr_dec_set_out_img_format(uhdr_codec_private_t* dec, uhdr_img_fmt_t fmt) {
1575
14.4k
  uhdr_error_info_t status = g_no_error;
1576
1577
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1578
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1579
0
    status.has_detail = 1;
1580
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1581
14.4k
  } else if (fmt != UHDR_IMG_FMT_32bppRGBA8888 && fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat &&
1582
7.47k
             fmt != UHDR_IMG_FMT_32bppRGBA1010102) {
1583
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1584
0
    status.has_detail = 1;
1585
0
    snprintf(status.detail, sizeof status.detail,
1586
0
             "invalid output format %d, expects one of {UHDR_IMG_FMT_32bppRGBA8888,  "
1587
0
             "UHDR_IMG_FMT_64bppRGBAHalfFloat, UHDR_IMG_FMT_32bppRGBA1010102}",
1588
0
             fmt);
1589
0
  }
1590
14.4k
  if (status.error_code != UHDR_CODEC_OK) return status;
1591
1592
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1593
14.4k
  if (handle->m_probed) {
1594
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1595
0
    status.has_detail = 1;
1596
0
    snprintf(status.detail, sizeof status.detail,
1597
0
             "An earlier call to uhdr_decode() has switched the context from configurable state to "
1598
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1599
0
    return status;
1600
0
  }
1601
1602
14.4k
  handle->m_output_fmt = fmt;
1603
1604
14.4k
  return status;
1605
14.4k
}
1606
1607
uhdr_error_info_t uhdr_dec_set_out_color_transfer(uhdr_codec_private_t* dec,
1608
14.4k
                                                  uhdr_color_transfer_t ct) {
1609
14.4k
  uhdr_error_info_t status = g_no_error;
1610
1611
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1612
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1613
0
    status.has_detail = 1;
1614
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1615
14.4k
  } else if (ct != UHDR_CT_HLG && ct != UHDR_CT_PQ && ct != UHDR_CT_LINEAR && ct != UHDR_CT_SRGB) {
1616
2.70k
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1617
2.70k
    status.has_detail = 1;
1618
2.70k
    snprintf(status.detail, sizeof status.detail,
1619
2.70k
             "invalid output color transfer %d, expects one of {UHDR_CT_HLG, UHDR_CT_PQ, "
1620
2.70k
             "UHDR_CT_LINEAR, UHDR_CT_SRGB}",
1621
2.70k
             ct);
1622
2.70k
  }
1623
14.4k
  if (status.error_code != UHDR_CODEC_OK) return status;
1624
1625
11.7k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1626
11.7k
  if (handle->m_probed) {
1627
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1628
0
    status.has_detail = 1;
1629
0
    snprintf(status.detail, sizeof status.detail,
1630
0
             "An earlier call to uhdr_decode() has switched the context from configurable state to "
1631
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1632
0
    return status;
1633
0
  }
1634
1635
11.7k
  handle->m_output_ct = ct;
1636
1637
11.7k
  return status;
1638
11.7k
}
1639
1640
uhdr_error_info_t uhdr_dec_set_out_max_display_boost(uhdr_codec_private_t* dec,
1641
14.4k
                                                     float display_boost) {
1642
14.4k
  uhdr_error_info_t status = g_no_error;
1643
1644
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1645
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1646
0
    status.has_detail = 1;
1647
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1648
14.4k
  } else if (!std::isfinite(display_boost) || display_boost < 1.0f) {
1649
4.02k
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1650
4.02k
    status.has_detail = 1;
1651
4.02k
    snprintf(status.detail, sizeof status.detail,
1652
4.02k
             "invalid display boost %f, expects to be >= 1.0f}", display_boost);
1653
4.02k
  }
1654
14.4k
  if (status.error_code != UHDR_CODEC_OK) return status;
1655
1656
10.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1657
10.4k
  if (handle->m_probed) {
1658
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
1659
0
    status.has_detail = 1;
1660
0
    snprintf(status.detail, sizeof status.detail,
1661
0
             "An earlier call to uhdr_decode() has switched the context from configurable state to "
1662
0
             "end state. The context is no longer configurable. To reuse, call reset()");
1663
0
    return status;
1664
0
  }
1665
1666
10.4k
  handle->m_output_max_disp_boost = display_boost;
1667
1668
10.4k
  return status;
1669
10.4k
}
1670
1671
43.3k
uhdr_error_info_t uhdr_dec_probe(uhdr_codec_private_t* dec) {
1672
43.3k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1673
0
    uhdr_error_info_t status;
1674
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1675
0
    status.has_detail = 1;
1676
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1677
0
    return status;
1678
0
  }
1679
1680
43.3k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1681
43.3k
  uhdr_error_info_t& status = handle->m_probe_call_status;
1682
1683
43.3k
  if (!handle->m_probed) {
1684
28.8k
    handle->m_probed = true;
1685
1686
28.8k
    if (handle->m_uhdr_compressed_img.get() == nullptr) {
1687
42
      status.error_code = UHDR_CODEC_INVALID_OPERATION;
1688
42
      status.has_detail = 1;
1689
42
      snprintf(status.detail, sizeof status.detail, "did not receive any image for decoding");
1690
42
      return status;
1691
42
    }
1692
1693
28.7k
#ifdef UHDR_ENABLE_HEIF
1694
28.7k
    enum heif_filetype_result filetype = heif_check_filetype(
1695
28.7k
        static_cast<const uint8_t*>(handle->m_uhdr_compressed_img->data),
1696
28.7k
        handle->m_uhdr_compressed_img->data_sz);
1697
28.7k
    if (filetype == heif_filetype_yes_supported || filetype == heif_filetype_maybe) {
1698
9.06k
      struct heif_context* ctx = heif_context_alloc();
1699
9.06k
      if (ctx) {
1700
9.06k
        heif_error err = heif_context_read_from_memory_without_copy(
1701
9.06k
            ctx, static_cast<const uint8_t*>(handle->m_uhdr_compressed_img->data),
1702
9.06k
            handle->m_uhdr_compressed_img->data_sz, nullptr);
1703
9.06k
        if (err.code == heif_error_Ok) {
1704
0
          struct heif_image_handle* base_handle = nullptr;
1705
0
          err = heif_context_get_primary_image_handle(ctx, &base_handle);
1706
0
          if (err.code == heif_error_Ok && base_handle != nullptr) {
1707
0
            handle->m_img_wd = heif_image_handle_get_width(base_handle);
1708
0
            handle->m_img_ht = heif_image_handle_get_height(base_handle);
1709
1710
0
            struct heif_image_handle* gainmap_handle = nullptr;
1711
0
            err = heif_image_handle_get_gain_map_image_handle(base_handle, &gainmap_handle);
1712
0
            if (err.code == heif_error_Ok && gainmap_handle != nullptr) {
1713
0
              handle->m_gainmap_wd = heif_image_handle_get_width(gainmap_handle);
1714
0
              handle->m_gainmap_ht = heif_image_handle_get_height(gainmap_handle);
1715
0
              handle->m_gainmap_num_comp = 1;
1716
1717
0
              int meta_len = heif_image_handle_get_gain_map_metadata_size(base_handle);
1718
0
              if (meta_len > 0) {
1719
0
                std::vector<uint8_t> meta(meta_len);
1720
0
                heif_image_handle_get_gain_map_metadata(base_handle, meta.data());
1721
0
                ultrahdr::uhdr_gainmap_metadata_frac frac;
1722
0
                if (ultrahdr::uhdr_gainmap_metadata_frac::decodeGainmapMetadata(meta, &frac).error_code == UHDR_CODEC_OK) {
1723
0
                  ultrahdr::uhdr_gainmap_metadata_ext_t metadata;
1724
0
                  if (ultrahdr::uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(&frac, &metadata).error_code == UHDR_CODEC_OK) {
1725
0
                    std::copy(metadata.max_content_boost, metadata.max_content_boost + 3,
1726
0
                              handle->m_metadata.max_content_boost);
1727
0
                    std::copy(metadata.min_content_boost, metadata.min_content_boost + 3,
1728
0
                              handle->m_metadata.min_content_boost);
1729
0
                    std::copy(metadata.gamma, metadata.gamma + 3, handle->m_metadata.gamma);
1730
0
                    std::copy(metadata.offset_sdr, metadata.offset_sdr + 3, handle->m_metadata.offset_sdr);
1731
0
                    std::copy(metadata.offset_hdr, metadata.offset_hdr + 3, handle->m_metadata.offset_hdr);
1732
0
                    handle->m_metadata.hdr_capacity_min = metadata.hdr_capacity_min;
1733
0
                    handle->m_metadata.hdr_capacity_max = metadata.hdr_capacity_max;
1734
0
                    handle->m_metadata.use_base_cg = metadata.use_base_cg;
1735
0
                  }
1736
0
                }
1737
0
              }
1738
0
              heif_image_handle_release(gainmap_handle);
1739
0
            }
1740
0
            heif_image_handle_release(base_handle);
1741
0
            heif_context_free(ctx);
1742
0
            return status;
1743
0
          }
1744
0
        }
1745
9.06k
        heif_context_free(ctx);
1746
9.06k
      }
1747
9.06k
    }
1748
28.7k
#endif
1749
1750
28.7k
    ultrahdr::jpeg_info_struct primary_image;
1751
28.7k
    ultrahdr::jpeg_info_struct gainmap_image;
1752
28.7k
    ultrahdr::jpegr_info_struct jpegr_info;
1753
28.7k
    jpegr_info.primaryImgInfo = &primary_image;
1754
28.7k
    jpegr_info.gainmapImgInfo = &gainmap_image;
1755
1756
28.7k
    ultrahdr::JpegR jpegr;
1757
28.7k
    status = jpegr.getJPEGRInfo(handle->m_uhdr_compressed_img.get(), &jpegr_info);
1758
28.7k
    if (status.error_code != UHDR_CODEC_OK) return status;
1759
1760
17.3k
    ultrahdr::uhdr_gainmap_metadata_ext_t metadata;
1761
17.3k
    status = jpegr.parseGainMapMetadata(gainmap_image.isoData.data(), gainmap_image.isoData.size(),
1762
17.3k
                                        gainmap_image.xmpData.data(), gainmap_image.xmpData.size(),
1763
17.3k
                                        primary_image.exifData.data(),
1764
17.3k
                                        primary_image.exifData.size(), &metadata);
1765
17.3k
    if (status.error_code != UHDR_CODEC_OK) return status;
1766
3.69k
    std::copy(metadata.max_content_boost, metadata.max_content_boost + 3,
1767
3.69k
              handle->m_metadata.max_content_boost);
1768
3.69k
    std::copy(metadata.min_content_boost, metadata.min_content_boost + 3,
1769
3.69k
              handle->m_metadata.min_content_boost);
1770
3.69k
    std::copy(metadata.gamma, metadata.gamma + 3, handle->m_metadata.gamma);
1771
3.69k
    std::copy(metadata.offset_sdr, metadata.offset_sdr + 3, handle->m_metadata.offset_sdr);
1772
3.69k
    std::copy(metadata.offset_hdr, metadata.offset_hdr + 3, handle->m_metadata.offset_hdr);
1773
3.69k
    handle->m_metadata.hdr_capacity_min = metadata.hdr_capacity_min;
1774
3.69k
    handle->m_metadata.hdr_capacity_max = metadata.hdr_capacity_max;
1775
3.69k
    handle->m_metadata.use_base_cg = metadata.use_base_cg;
1776
1777
3.69k
    handle->m_img_wd = primary_image.width;
1778
3.69k
    handle->m_img_ht = primary_image.height;
1779
3.69k
    handle->m_gainmap_wd = gainmap_image.width;
1780
3.69k
    handle->m_gainmap_ht = gainmap_image.height;
1781
3.69k
    handle->m_gainmap_num_comp = gainmap_image.numComponents;
1782
3.69k
    handle->m_exif = std::move(primary_image.exifData);
1783
3.69k
    handle->m_exif_block.data = handle->m_exif.data();
1784
3.69k
    handle->m_exif_block.data_sz = handle->m_exif_block.capacity = handle->m_exif.size();
1785
3.69k
    handle->m_icc = std::move(primary_image.iccData);
1786
3.69k
    handle->m_icc_block.data = handle->m_icc.data();
1787
3.69k
    handle->m_icc_block.data_sz = handle->m_icc_block.capacity = handle->m_icc.size();
1788
3.69k
    handle->m_base_img = std::move(primary_image.imgData);
1789
3.69k
    handle->m_base_img_block.data = handle->m_base_img.data();
1790
3.69k
    handle->m_base_img_block.data_sz = handle->m_base_img_block.capacity =
1791
3.69k
        handle->m_base_img.size();
1792
3.69k
    handle->m_gainmap_img = std::move(gainmap_image.imgData);
1793
3.69k
    handle->m_gainmap_img_block.data = handle->m_gainmap_img.data();
1794
3.69k
    handle->m_gainmap_img_block.data_sz = handle->m_gainmap_img_block.capacity =
1795
3.69k
        handle->m_gainmap_img.size();
1796
3.69k
  }
1797
1798
18.1k
  return status;
1799
43.3k
}
1800
1801
14.4k
int uhdr_dec_get_image_width(uhdr_codec_private_t* dec) {
1802
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1803
0
    return -1;
1804
0
  }
1805
1806
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1807
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1808
12.6k
    return -1;
1809
12.6k
  }
1810
1811
1.84k
  return handle->m_img_wd;
1812
14.4k
}
1813
1814
14.4k
int uhdr_dec_get_image_height(uhdr_codec_private_t* dec) {
1815
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1816
0
    return -1;
1817
0
  }
1818
1819
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1820
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1821
12.6k
    return -1;
1822
12.6k
  }
1823
1824
1.84k
  return handle->m_img_ht;
1825
14.4k
}
1826
1827
14.4k
int uhdr_dec_get_gainmap_width(uhdr_codec_private_t* dec) {
1828
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1829
0
    return -1;
1830
0
  }
1831
1832
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1833
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1834
12.6k
    return -1;
1835
12.6k
  }
1836
1837
1.84k
  return handle->m_gainmap_wd;
1838
14.4k
}
1839
1840
14.4k
int uhdr_dec_get_gainmap_height(uhdr_codec_private_t* dec) {
1841
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1842
0
    return -1;
1843
0
  }
1844
1845
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1846
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1847
12.6k
    return -1;
1848
12.6k
  }
1849
1850
1.84k
  return handle->m_gainmap_ht;
1851
14.4k
}
1852
1853
14.4k
uhdr_mem_block_t* uhdr_dec_get_exif(uhdr_codec_private_t* dec) {
1854
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1855
0
    return nullptr;
1856
0
  }
1857
1858
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1859
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1860
12.6k
    return nullptr;
1861
12.6k
  }
1862
1863
1.84k
  return &handle->m_exif_block;
1864
14.4k
}
1865
1866
14.4k
uhdr_mem_block_t* uhdr_dec_get_icc(uhdr_codec_private_t* dec) {
1867
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1868
0
    return nullptr;
1869
0
  }
1870
1871
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1872
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1873
12.6k
    return nullptr;
1874
12.6k
  }
1875
1876
1.84k
  return &handle->m_icc_block;
1877
14.4k
}
1878
1879
14.4k
uhdr_mem_block_t* uhdr_dec_get_base_image(uhdr_codec_private_t* dec) {
1880
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1881
0
    return nullptr;
1882
0
  }
1883
1884
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1885
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1886
12.6k
    return nullptr;
1887
12.6k
  }
1888
1889
1.84k
  return &handle->m_base_img_block;
1890
14.4k
}
1891
1892
14.4k
uhdr_mem_block_t* uhdr_dec_get_gainmap_image(uhdr_codec_private_t* dec) {
1893
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1894
0
    return nullptr;
1895
0
  }
1896
1897
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1898
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1899
12.6k
    return nullptr;
1900
12.6k
  }
1901
1902
1.84k
  return &handle->m_gainmap_img_block;
1903
14.4k
}
1904
1905
14.4k
uhdr_gainmap_metadata_t* uhdr_dec_get_gainmap_metadata(uhdr_codec_private_t* dec) {
1906
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1907
0
    return nullptr;
1908
0
  }
1909
1910
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1911
14.4k
  if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
1912
12.6k
    return nullptr;
1913
12.6k
  }
1914
1915
1.84k
  return &handle->m_metadata;
1916
14.4k
}
1917
1918
14.4k
uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec) {
1919
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
1920
0
    uhdr_error_info_t status;
1921
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1922
0
    status.has_detail = 1;
1923
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
1924
0
    return status;
1925
0
  }
1926
1927
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
1928
1929
14.4k
  if (handle->m_sailed) {
1930
0
    return handle->m_decode_call_status;
1931
0
  }
1932
1933
14.4k
  uhdr_error_info_t& status = handle->m_decode_call_status;
1934
14.4k
  status = uhdr_dec_probe(dec);
1935
14.4k
  if (status.error_code != UHDR_CODEC_OK) return status;
1936
1937
1.84k
  handle->m_sailed = true;
1938
1939
1.84k
  if ((handle->m_output_fmt == UHDR_IMG_FMT_32bppRGBA1010102 &&
1940
860
       (handle->m_output_ct != UHDR_CT_HLG && handle->m_output_ct != UHDR_CT_PQ)) ||
1941
1.84k
      (handle->m_output_fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat &&
1942
645
       handle->m_output_ct != UHDR_CT_LINEAR) ||
1943
1.84k
      (handle->m_output_fmt == UHDR_IMG_FMT_32bppRGBA8888 && handle->m_output_ct != UHDR_CT_SRGB)) {
1944
2
    status.error_code = UHDR_CODEC_INVALID_PARAM;
1945
2
    status.has_detail = 1;
1946
2
    snprintf(status.detail, sizeof status.detail,
1947
2
             "unsupported output pixel format and output color transfer pair");
1948
2
    return status;
1949
2
  }
1950
1951
1.84k
  handle->m_decoded_img_buffer = std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(
1952
1.84k
      handle->m_output_fmt, UHDR_CG_UNSPECIFIED, handle->m_output_ct, UHDR_CR_UNSPECIFIED,
1953
1.84k
      handle->m_img_wd, handle->m_img_ht, 1);
1954
1955
1.84k
  handle->m_gainmap_img_buffer = std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(
1956
1.84k
      handle->m_gainmap_num_comp == 1 ? UHDR_IMG_FMT_8bppYCbCr400 : UHDR_IMG_FMT_32bppRGBA8888,
1957
1.84k
      UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, handle->m_gainmap_wd,
1958
1.84k
      handle->m_gainmap_ht, 1);
1959
1960
1.84k
#ifdef UHDR_ENABLE_HEIF
1961
1.84k
  enum heif_filetype_result filetype = heif_check_filetype(
1962
1.84k
      static_cast<const uint8_t*>(handle->m_uhdr_compressed_img->data),
1963
1.84k
      handle->m_uhdr_compressed_img->data_sz);
1964
1.84k
  if (filetype == heif_filetype_yes_supported || filetype == heif_filetype_maybe) {
1965
84
    ultrahdr::HeifUltraHdr heifApp;
1966
84
    status = heifApp.decodeHeicUltraHdr(
1967
84
        handle->m_uhdr_compressed_img.get(), handle->m_decoded_img_buffer.get(),
1968
84
        handle->m_output_max_disp_boost, handle->m_output_ct, handle->m_output_fmt,
1969
84
        handle->m_gainmap_img_buffer.get(), nullptr);
1970
84
    if (status.error_code == UHDR_CODEC_OK && dec->m_effects.size() != 0) {
1971
0
      status = ultrahdr::apply_effects(handle);
1972
0
    }
1973
84
    return status;
1974
84
  }
1975
1.76k
#endif
1976
1977
#ifdef UHDR_ENABLE_GLES
1978
  ultrahdr::uhdr_opengl_ctxt_t* uhdrGLESCtxt = nullptr;
1979
  if (handle->m_enable_gles &&
1980
      (handle->m_output_ct != UHDR_CT_SRGB || handle->m_effects.size() > 0)) {
1981
    handle->m_uhdr_gl_ctxt.init_opengl_ctxt();
1982
    status = handle->m_uhdr_gl_ctxt.mErrorStatus;
1983
    if (status.error_code != UHDR_CODEC_OK) return status;
1984
    uhdrGLESCtxt = &handle->m_uhdr_gl_ctxt;
1985
  }
1986
  ultrahdr::JpegR jpegr(uhdrGLESCtxt);
1987
#else
1988
1.76k
  ultrahdr::JpegR jpegr;
1989
1.76k
#endif
1990
1991
1.76k
  status =
1992
1.76k
      jpegr.decodeJPEGR(handle->m_uhdr_compressed_img.get(), handle->m_decoded_img_buffer.get(),
1993
1.76k
                        handle->m_output_max_disp_boost, handle->m_output_ct, handle->m_output_fmt,
1994
1.76k
                        handle->m_gainmap_img_buffer.get(), nullptr);
1995
1996
1.76k
  if (status.error_code == UHDR_CODEC_OK && dec->m_effects.size() != 0) {
1997
1.14k
    status = ultrahdr::apply_effects(handle);
1998
1.14k
  }
1999
2000
#ifdef UHDR_ENABLE_GLES
2001
  if (handle->m_enable_gles) {
2002
    if (handle->m_uhdr_gl_ctxt.mDecodedImgTexture != 0) {
2003
      handle->m_uhdr_gl_ctxt.read_texture(
2004
          &handle->m_uhdr_gl_ctxt.mDecodedImgTexture, handle->m_decoded_img_buffer->fmt,
2005
          handle->m_decoded_img_buffer->w, handle->m_decoded_img_buffer->h,
2006
          handle->m_decoded_img_buffer->planes[0]);
2007
    }
2008
    if (handle->m_uhdr_gl_ctxt.mGainmapImgTexture != 0 && dec->m_effects.size() != 0) {
2009
      handle->m_uhdr_gl_ctxt.read_texture(
2010
          &handle->m_uhdr_gl_ctxt.mGainmapImgTexture, handle->m_gainmap_img_buffer->fmt,
2011
          handle->m_gainmap_img_buffer->w, handle->m_gainmap_img_buffer->h,
2012
          handle->m_gainmap_img_buffer->planes[0]);
2013
    }
2014
  }
2015
#endif
2016
1.76k
  return status;
2017
1.84k
}
2018
2019
14.4k
uhdr_raw_image_t* uhdr_get_decoded_image(uhdr_codec_private_t* dec) {
2020
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
2021
0
    return nullptr;
2022
0
  }
2023
2024
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
2025
14.4k
  if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) {
2026
13.7k
    return nullptr;
2027
13.7k
  }
2028
2029
752
  return handle->m_decoded_img_buffer.get();
2030
14.4k
}
2031
2032
14.4k
uhdr_raw_image_t* uhdr_get_decoded_gainmap_image(uhdr_codec_private_t* dec) {
2033
14.4k
  if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
2034
0
    return nullptr;
2035
0
  }
2036
2037
14.4k
  uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
2038
14.4k
  if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) {
2039
13.7k
    return nullptr;
2040
13.7k
  }
2041
2042
752
  return handle->m_gainmap_img_buffer.get();
2043
14.4k
}
2044
2045
43.3k
void uhdr_reset_decoder(uhdr_codec_private_t* dec) {
2046
43.3k
  if (dynamic_cast<uhdr_decoder_private*>(dec) != nullptr) {
2047
43.3k
    uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
2048
2049
    // clear entries and restore defaults
2050
43.3k
    for (auto it : handle->m_effects) delete it;
2051
43.3k
    handle->m_effects.clear();
2052
#ifdef UHDR_ENABLE_GLES
2053
    handle->m_uhdr_gl_ctxt.reset_opengl_ctxt();
2054
    handle->m_enable_gles = false;
2055
#endif
2056
43.3k
    handle->m_sailed = false;
2057
43.3k
    handle->m_uhdr_compressed_img.reset();
2058
43.3k
    handle->m_output_fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat;
2059
43.3k
    handle->m_output_ct = UHDR_CT_LINEAR;
2060
43.3k
    handle->m_output_max_disp_boost = FLT_MAX;
2061
2062
    // ready to be configured
2063
43.3k
    handle->m_probed = false;
2064
43.3k
    handle->m_decoded_img_buffer.reset();
2065
43.3k
    handle->m_gainmap_img_buffer.reset();
2066
43.3k
    handle->m_img_wd = 0;
2067
43.3k
    handle->m_img_ht = 0;
2068
43.3k
    handle->m_gainmap_wd = 0;
2069
43.3k
    handle->m_gainmap_ht = 0;
2070
43.3k
    handle->m_gainmap_num_comp = 0;
2071
43.3k
    handle->m_exif.clear();
2072
43.3k
    memset(&handle->m_exif_block, 0, sizeof handle->m_exif_block);
2073
43.3k
    handle->m_icc.clear();
2074
43.3k
    memset(&handle->m_icc_block, 0, sizeof handle->m_icc_block);
2075
43.3k
    handle->m_base_img.clear();
2076
43.3k
    memset(&handle->m_base_img_block, 0, sizeof handle->m_base_img_block);
2077
43.3k
    handle->m_gainmap_img.clear();
2078
43.3k
    memset(&handle->m_gainmap_img_block, 0, sizeof handle->m_gainmap_img_block);
2079
43.3k
    memset(&handle->m_metadata, 0, sizeof handle->m_metadata);
2080
43.3k
    handle->m_probe_call_status = g_no_error;
2081
43.3k
    handle->m_decode_call_status = g_no_error;
2082
43.3k
  }
2083
43.3k
}
2084
2085
uhdr_error_info_t uhdr_enable_gpu_acceleration(uhdr_codec_private_t* codec,
2086
14.4k
                                               [[maybe_unused]] int enable) {
2087
14.4k
  uhdr_error_info_t status = g_no_error;
2088
2089
14.4k
  if (codec == nullptr) {
2090
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
2091
0
    status.has_detail = 1;
2092
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
2093
0
    return status;
2094
0
  }
2095
2096
14.4k
  if (codec->m_sailed) {
2097
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
2098
0
    status.has_detail = 1;
2099
0
    snprintf(
2100
0
        status.detail, sizeof status.detail,
2101
0
        "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable "
2102
0
        "state to end state. The context is no longer configurable. To reuse, call reset()");
2103
0
    return status;
2104
0
  }
2105
2106
#ifdef UHDR_ENABLE_GLES
2107
  codec->m_enable_gles = enable;
2108
#endif
2109
2110
14.4k
  return status;
2111
14.4k
}
2112
2113
uhdr_error_info_t uhdr_add_effect_mirror(uhdr_codec_private_t* codec,
2114
7.11k
                                         uhdr_mirror_direction_t direction) {
2115
7.11k
  uhdr_error_info_t status = g_no_error;
2116
2117
7.11k
  if (codec == nullptr) {
2118
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
2119
0
    status.has_detail = 1;
2120
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
2121
0
    return status;
2122
0
  }
2123
2124
7.11k
  if (direction != UHDR_MIRROR_HORIZONTAL && direction != UHDR_MIRROR_VERTICAL) {
2125
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
2126
0
    status.has_detail = 1;
2127
0
    snprintf(
2128
0
        status.detail, sizeof status.detail,
2129
0
        "unsupported direction, expects one of {UHDR_MIRROR_HORIZONTAL, UHDR_MIRROR_VERTICAL}");
2130
0
    return status;
2131
0
  }
2132
2133
7.11k
  if (codec->m_sailed) {
2134
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
2135
0
    status.has_detail = 1;
2136
0
    snprintf(
2137
0
        status.detail, sizeof status.detail,
2138
0
        "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable "
2139
0
        "state to end state. The context is no longer configurable. To reuse, call reset()");
2140
0
    return status;
2141
0
  }
2142
2143
7.11k
  codec->m_effects.push_back(new ultrahdr::uhdr_mirror_effect_t(direction));
2144
2145
7.11k
  return status;
2146
7.11k
}
2147
2148
6.75k
uhdr_error_info_t uhdr_add_effect_rotate(uhdr_codec_private_t* codec, int degrees) {
2149
6.75k
  uhdr_error_info_t status = g_no_error;
2150
2151
6.75k
  if (codec == nullptr) {
2152
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
2153
0
    status.has_detail = 1;
2154
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
2155
0
    return status;
2156
0
  }
2157
2158
6.75k
  if (degrees != 90 && degrees != 180 && degrees != 270) {
2159
3.09k
    status.error_code = UHDR_CODEC_INVALID_PARAM;
2160
3.09k
    status.has_detail = 1;
2161
3.09k
    snprintf(status.detail, sizeof status.detail,
2162
3.09k
             "unsupported degrees, expects one of {90, 180, 270}");
2163
3.09k
    return status;
2164
3.09k
  }
2165
2166
3.66k
  if (codec->m_sailed) {
2167
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
2168
0
    status.has_detail = 1;
2169
0
    snprintf(
2170
0
        status.detail, sizeof status.detail,
2171
0
        "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable "
2172
0
        "state to end state. The context is no longer configurable. To reuse, call reset()");
2173
0
    return status;
2174
0
  }
2175
2176
3.66k
  codec->m_effects.push_back(new ultrahdr::uhdr_rotate_effect_t(degrees));
2177
2178
3.66k
  return status;
2179
3.66k
}
2180
2181
uhdr_error_info_t uhdr_add_effect_crop(uhdr_codec_private_t* codec, int left, int right, int top,
2182
6.35k
                                       int bottom) {
2183
6.35k
  uhdr_error_info_t status = g_no_error;
2184
2185
6.35k
  if (codec == nullptr) {
2186
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
2187
0
    status.has_detail = 1;
2188
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
2189
0
    return status;
2190
0
  }
2191
2192
6.35k
  if (codec->m_sailed) {
2193
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
2194
0
    status.has_detail = 1;
2195
0
    snprintf(
2196
0
        status.detail, sizeof status.detail,
2197
0
        "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable "
2198
0
        "state to end state. The context is no longer configurable. To reuse, call reset()");
2199
0
    return status;
2200
0
  }
2201
2202
6.35k
  codec->m_effects.push_back(new ultrahdr::uhdr_crop_effect_t(left, right, top, bottom));
2203
2204
6.35k
  return status;
2205
6.35k
}
2206
2207
6.65k
uhdr_error_info_t uhdr_add_effect_resize(uhdr_codec_private_t* codec, int width, int height) {
2208
6.65k
  uhdr_error_info_t status = g_no_error;
2209
2210
6.65k
  if (codec == nullptr) {
2211
0
    status.error_code = UHDR_CODEC_INVALID_PARAM;
2212
0
    status.has_detail = 1;
2213
0
    snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
2214
0
    return status;
2215
0
  }
2216
2217
6.65k
  if (codec->m_sailed) {
2218
0
    status.error_code = UHDR_CODEC_INVALID_OPERATION;
2219
0
    status.has_detail = 1;
2220
0
    snprintf(
2221
0
        status.detail, sizeof status.detail,
2222
0
        "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable "
2223
0
        "state to end state. The context is no longer configurable. To reuse, call reset()");
2224
0
    return status;
2225
0
  }
2226
2227
6.65k
  codec->m_effects.push_back(new ultrahdr::uhdr_resize_effect_t(width, height));
2228
2229
6.65k
  return status;
2230
6.65k
}