Coverage Report

Created: 2026-05-16 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image-items/image_item.cc
Line
Count
Source
1
/*
2
 * HEIF image base codec.
3
 * Copyright (c) 2024 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "image_item.h"
22
#include "mask_image.h"
23
#include "context.h"
24
#include "file.h"
25
#include "jpeg.h"
26
#include "jpeg2000.h"
27
#include "avif.h"
28
#include "avc.h"
29
#include "hevc.h"
30
#include "grid.h"
31
#include "overlay.h"
32
#include "iden.h"
33
#include "tiled.h"
34
#include "codecs/decoder.h"
35
#include "color-conversion/colorconversion.h"
36
#include "api_structs.h"
37
#include "plugin_registry.h"
38
#include "security_limits.h"
39
40
#include <limits>
41
#include <cassert>
42
#include <cstring>
43
#include <sstream>
44
//#include <ranges>
45
46
#if WITH_UNCOMPRESSED_CODEC
47
#include "image-items/unc_image.h"
48
#endif
49
50
51
ImageItem::ImageItem(HeifContext* context)
52
23.4k
    : m_heif_context(context)
53
23.4k
{
54
23.4k
  memset(&m_depth_representation_info, 0, sizeof(m_depth_representation_info));
55
23.4k
}
56
57
58
ImageItem::ImageItem(HeifContext* context, heif_item_id id)
59
23.4k
    : ImageItem(context)
60
23.4k
{
61
23.4k
  m_id = id;
62
23.4k
}
63
64
65
bool ImageItem::is_property_essential(const std::shared_ptr<Box>& property) const
66
0
{
67
0
  if (property->get_short_type() == fourcc("ispe")) {
68
0
    return is_ispe_essential();
69
0
  }
70
0
  else {
71
0
    return property->is_essential();
72
0
  }
73
0
}
74
75
76
std::shared_ptr<HeifFile> ImageItem::get_file() const
77
12.8k
{
78
12.8k
  return m_heif_context->get_heif_file();
79
12.8k
}
80
81
82
heif_property_id ImageItem::add_property(std::shared_ptr<Box> property, bool essential)
83
2.32k
{
84
2.32k
  if (!property) {
85
25
    return 0;
86
25
  }
87
88
  // TODO: is this correct? What happens when add_property does deduplicate the property?
89
2.30k
  m_properties.push_back(property);
90
2.30k
  return get_file()->add_property(get_id(), property, essential);
91
2.32k
}
92
93
94
heif_property_id ImageItem::add_property_without_deduplication(std::shared_ptr<Box> property, bool essential)
95
0
{
96
0
  if (!property) {
97
0
    return 0;
98
0
  }
99
100
0
  m_properties.push_back(property);
101
0
  return get_file()->add_property_without_deduplication(get_id(), property, essential);
102
0
}
103
104
105
heif_compression_format ImageItem::compression_format_from_fourcc_infe_type(uint32_t type)
106
0
{
107
0
  switch (type) {
108
0
    case fourcc("jpeg"):
109
0
      return heif_compression_JPEG;
110
0
    case fourcc("hvc1"):
111
0
      return heif_compression_HEVC;
112
0
    case fourcc("av01"):
113
0
      return heif_compression_AV1;
114
0
    case fourcc("vvc1"):
115
0
      return heif_compression_VVC;
116
0
    case fourcc("j2k1"):
117
0
      return heif_compression_JPEG2000;
118
0
    case fourcc("unci"):
119
0
      return heif_compression_uncompressed;
120
0
    case fourcc("mski"):
121
0
      return heif_compression_mask;
122
0
    default:
123
0
      return heif_compression_undefined;
124
0
  }
125
0
}
126
127
uint32_t ImageItem::compression_format_to_fourcc_infe_type(heif_compression_format format)
128
0
{
129
0
  switch (format) {
130
0
    case heif_compression_JPEG:
131
0
      return fourcc("jpeg");
132
0
    case heif_compression_HEVC:
133
0
      return fourcc("hvc1");
134
0
    case heif_compression_AV1:
135
0
      return fourcc("av01");
136
0
    case heif_compression_VVC:
137
0
      return fourcc("vvc1");
138
0
    case heif_compression_JPEG2000:
139
0
      return fourcc("j2k1");
140
0
    case heif_compression_uncompressed:
141
0
      return fourcc("unci");
142
0
    case heif_compression_mask:
143
0
      return fourcc("mski");
144
0
    default:
145
0
      return 0;
146
0
  }
147
0
}
148
149
150
std::shared_ptr<ImageItem> ImageItem::alloc_for_infe_box(HeifContext* ctx, const std::shared_ptr<Box_infe>& infe)
151
23.8k
{
152
23.8k
  uint32_t item_type = infe->get_item_type_4cc();
153
23.8k
  heif_item_id id = infe->get_item_ID();
154
155
23.8k
  if (item_type == fourcc("jpeg") ||
156
23.6k
      (item_type == fourcc("mime") && infe->get_content_type() == "image/jpeg")) {
157
201
    return std::make_shared<ImageItem_JPEG>(ctx, id);
158
201
  }
159
23.6k
  else if (item_type == fourcc("hvc1")) {
160
14.1k
    return std::make_shared<ImageItem_HEVC>(ctx, id);
161
14.1k
  }
162
9.55k
  else if (item_type == fourcc("av01")) {
163
2.21k
    return std::make_shared<ImageItem_AVIF>(ctx, id);
164
2.21k
  }
165
7.33k
  else if (item_type == fourcc("vvc1")) {
166
19
    return std::make_shared<ImageItem_VVC>(ctx, id);
167
19
  }
168
7.31k
  else if (item_type == fourcc("avc1")) {
169
45
    return std::make_shared<ImageItem_AVC>(ctx, id);
170
45
  }
171
7.27k
  else if (item_type == fourcc("unci")) {
172
#if WITH_UNCOMPRESSED_CODEC
173
    return std::make_shared<ImageItem_uncompressed>(ctx, id);
174
#else
175
    // It is an image item type that we do not support. Thus, generate an ImageItem_Error.
176
177
11
    std::stringstream sstr;
178
11
    sstr << "Image item of type '" << fourcc_to_string(item_type) << "' is not supported.";
179
11
    Error err{ heif_error_Unsupported_feature, heif_suberror_Unsupported_image_type, sstr.str() };
180
11
    return std::make_shared<ImageItem_Error>(item_type, id, err);
181
11
#endif
182
11
  }
183
7.26k
  else if (item_type == fourcc("j2k1")) {
184
26
    return std::make_shared<ImageItem_JPEG2000>(ctx, id);
185
26
  }
186
7.23k
  else if (item_type == fourcc("lhv1")) {
187
4
    return std::make_shared<ImageItem_Error>(item_type, id,
188
4
                                             Error{heif_error_Unsupported_feature,
189
4
                                                   heif_suberror_Unsupported_image_type,
190
4
                                                   "Layered HEVC images (lhv1) are not supported yet"});
191
4
  }
192
7.23k
  else if (item_type == fourcc("mski")) {
193
215
    return std::make_shared<ImageItem_mask>(ctx, id);
194
215
  }
195
7.01k
  else if (item_type == fourcc("grid")) {
196
1.23k
    return std::make_shared<ImageItem_Grid>(ctx, id);
197
1.23k
  }
198
5.78k
  else if (item_type == fourcc("iovl")) {
199
514
    return std::make_shared<ImageItem_Overlay>(ctx, id);
200
514
  }
201
5.26k
  else if (item_type == fourcc("iden")) {
202
20
    return std::make_shared<ImageItem_iden>(ctx, id);
203
20
  }
204
5.24k
  else if (item_type == fourcc("tili")) {
205
8
    return std::make_shared<ImageItem_Tiled>(ctx, id);
206
8
  }
207
5.23k
  else {
208
    // This item has an unknown type. It could be an image or anything else.
209
    // Do not process the item.
210
211
5.23k
    return nullptr;
212
5.23k
  }
213
23.8k
}
214
215
216
std::shared_ptr<ImageItem> ImageItem::alloc_for_compression_format(HeifContext* ctx, heif_compression_format format)
217
0
{
218
0
  switch (format) {
219
0
    case heif_compression_JPEG:
220
0
      return std::make_shared<ImageItem_JPEG>(ctx);
221
0
    case heif_compression_HEVC:
222
0
      return std::make_shared<ImageItem_HEVC>(ctx);
223
0
    case heif_compression_AV1:
224
0
      return std::make_shared<ImageItem_AVIF>(ctx);
225
0
    case heif_compression_VVC:
226
0
      return std::make_shared<ImageItem_VVC>(ctx);
227
0
    case heif_compression_AVC:
228
0
      return std::make_shared<ImageItem_AVC>(ctx);
229
#if WITH_UNCOMPRESSED_CODEC
230
    case heif_compression_uncompressed:
231
      return std::make_shared<ImageItem_uncompressed>(ctx);
232
#endif
233
0
    case heif_compression_JPEG2000:
234
0
    case heif_compression_HTJ2K:
235
0
      return std::make_shared<ImageItem_JPEG2000>(ctx);
236
0
    case heif_compression_mask:
237
0
      return std::make_shared<ImageItem_mask>(ctx);
238
0
    default:
239
0
      assert(false);
240
0
      return nullptr;
241
0
  }
242
0
}
243
244
245
Result<Encoder::CodedImageData> ImageItem::encode_to_bitstream_and_boxes(const std::shared_ptr<HeifPixelImage>& image,
246
                                                                           heif_encoder* encoder,
247
                                                                           const heif_encoding_options& options,
248
                                                                           heif_image_input_class input_class)
249
0
{
250
  // === generate compressed image bitstream
251
252
0
  Result<Encoder::CodedImageData> encodeResult = encode(image, encoder, options, input_class);
253
0
  if (!encodeResult) {
254
0
    return encodeResult;
255
0
  }
256
257
0
  Encoder::CodedImageData& codedImage = *encodeResult;
258
259
  // === generate properties
260
261
  // --- choose which color profile to put into 'colr' box
262
263
0
  auto colr_boxes = add_color_profile(image, options, input_class, options.output_nclx_profile);
264
0
  codedImage.properties.insert(codedImage.properties.end(),
265
0
                               colr_boxes.begin(),
266
0
                               colr_boxes.end());
267
268
269
  // --- ispe
270
  // Note: 'ispe' must come before the transformation properties
271
272
0
  uint32_t input_width, input_height;
273
0
  input_width = image->get_width();
274
0
  input_height = image->get_height();
275
276
  // --- get the real size of the encoded image
277
278
  // highest priority: codedImageData
279
0
  uint32_t encoded_width = codedImage.encoded_image_width;
280
0
  uint32_t encoded_height = codedImage.encoded_image_height;
281
282
  // second priority: query plugin API
283
0
  if (encoded_width == 0 &&
284
0
      encoder->plugin->plugin_api_version >= 3 &&
285
0
      encoder->plugin->query_encoded_size != nullptr) {
286
287
0
    encoder->plugin->query_encoded_size(encoder->encoder,
288
0
                                        input_width, input_height,
289
0
                                        &encoded_width,
290
0
                                        &encoded_height);
291
0
  }
292
0
  else if (encoded_width == 0) {
293
    // fallback priority: use input size
294
0
    encoded_width = input_width;
295
0
    encoded_height = input_height;
296
0
  }
297
298
0
  auto ispe = std::make_shared<Box_ispe>();
299
0
  ispe->set_size(encoded_width, encoded_height);
300
0
  ispe->set_is_essential(is_ispe_essential());
301
0
  codedImage.properties.push_back(ispe);
302
303
304
  // --- clap (if needed)
305
306
0
  if (input_width != encoded_width ||
307
0
      input_height != encoded_height) {
308
309
0
    auto clap = std::make_shared<Box_clap>();
310
0
    clap->set(input_width, input_height, encoded_width, encoded_height);
311
0
    codedImage.properties.push_back(clap);
312
0
  }
313
314
315
316
  // --- add common metadata properties (pixi, ...)
317
318
0
  auto colorspace = image->get_colorspace();
319
0
  auto chroma = image->get_chroma_format();
320
321
322
  // --- write PIXI property
323
324
0
  std::shared_ptr<Box_pixi> pixi = std::make_shared<Box_pixi>();
325
0
  bool valid_pixi = false;
326
327
0
  if (colorspace == heif_colorspace_filter_array) {
328
    // Skip pixi for filter array images — bit depth info is in uncC
329
0
  }
330
0
  else if (colorspace == heif_colorspace_monochrome) {
331
0
    valid_pixi = pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y));
332
0
  }
333
0
  else if (colorspace == heif_colorspace_YCbCr) {
334
0
    valid_pixi = (pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y)) ||
335
0
                  pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cb)) ||
336
0
                  pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cr)));
337
0
  }
338
0
  else if (colorspace == heif_colorspace_RGB) {
339
0
    if (chroma == heif_chroma_444) {
340
0
      valid_pixi = (pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_R)) ||
341
0
                    pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_G)) ||
342
0
                    pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_B)));
343
0
    }
344
0
    else if (chroma == heif_chroma_interleaved_RGB ||
345
0
             chroma == heif_chroma_interleaved_RGBA ||
346
0
             chroma == heif_chroma_interleaved_RRGGBB_LE ||
347
0
             chroma == heif_chroma_interleaved_RRGGBB_BE ||
348
0
             chroma == heif_chroma_interleaved_RRGGBBAA_LE ||
349
0
             chroma == heif_chroma_interleaved_RRGGBBAA_BE) {
350
0
      uint16_t bpp = image->get_bits_per_pixel(heif_channel_interleaved);
351
0
      valid_pixi = (pixi->add_channel_bits(bpp) ||
352
0
                    pixi->add_channel_bits(bpp) ||
353
0
                    pixi->add_channel_bits(bpp));
354
0
    }
355
0
  }
356
357
0
  if (valid_pixi) {
358
0
    codedImage.properties.push_back(pixi);
359
0
  }
360
361
  // --- generate properties for image extra data
362
363
  // copy over ImageDescription into image item
364
0
  *static_cast<ImageDescription*>(this) = static_cast<ImageDescription>(*image);
365
366
0
  auto extra_data_properties = image->generate_property_boxes(false);
367
0
  codedImage.properties.insert(codedImage.properties.end(),
368
0
                               extra_data_properties.begin(),
369
0
                               extra_data_properties.end());
370
371
0
  return encodeResult;
372
0
}
373
374
375
Error ImageItem::encode_to_item(HeifContext* ctx,
376
                                const std::shared_ptr<HeifPixelImage>& image,
377
                                heif_encoder* encoder,
378
                                const heif_encoding_options& options,
379
                                heif_image_input_class input_class)
380
0
{
381
0
  uint32_t input_width = image->get_width();
382
0
  uint32_t input_height = image->get_height();
383
384
0
  set_size(input_width, input_height);
385
386
387
  // compress image and assign data to item
388
389
0
  Result<Encoder::CodedImageData> codingResult = encode_to_bitstream_and_boxes(image, encoder, options, input_class);
390
0
  if (!codingResult) {
391
0
    return codingResult.error();
392
0
  }
393
394
0
  Encoder::CodedImageData& codedImage = *codingResult;
395
396
0
  auto infe_result = ctx->get_heif_file()->add_new_infe_box(get_infe_type());
397
0
  if (!infe_result) {
398
0
    return infe_result.error();
399
0
  }
400
0
  auto infe_box = *infe_result;
401
0
  heif_item_id image_id = infe_box->get_item_ID();
402
0
  set_id(image_id);
403
404
0
  ctx->get_heif_file()->append_iloc_data(image_id, codedImage.bitstream, 0);
405
406
407
  // set item properties
408
409
0
  for (auto& propertyBox : codingResult->properties) {
410
0
    bool essential = is_property_essential(propertyBox);
411
412
    // TODO: can we simply use add_property() ?
413
0
    int index = ctx->get_heif_file()->get_ipco_box()->find_or_append_child_box(propertyBox);
414
0
    ctx->get_heif_file()->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{essential,
415
0
                                                                                                           uint16_t(index + 1)});
416
0
  }
417
418
419
  // MIAF 7.3.6.7
420
  // This is according to MIAF without Amd2. With Amd2, the restriction has been lifted and the image is MIAF compatible.
421
  // However, since AVIF is based on MIAF, the whole image would be invalid in that case.
422
423
  // We might remove this code at a later point in time when MIAF Amd2 is in wide use.
424
425
0
  if (encoder->plugin->compression_format != heif_compression_AV1 &&
426
0
      image->get_colorspace() == heif_colorspace_YCbCr) {
427
0
    if (!is_integer_multiple_of_chroma_size(image->get_width(),
428
0
                                            image->get_height(),
429
0
                                            image->get_chroma_format())) {
430
0
      mark_not_miaf_compatible();
431
0
    }
432
0
  }
433
434
  // TODO: move this into encode_to_bistream_and_boxes()
435
0
  ctx->get_heif_file()->add_orientation_properties(image_id, options.image_orientation);
436
437
0
  return Error::Ok;
438
0
}
439
440
bool ImageItem::has_ispe_resolution() const
441
0
{
442
0
  return get_property<Box_ispe>() != nullptr;
443
0
}
444
445
uint32_t ImageItem::get_ispe_width() const
446
14.5k
{
447
14.5k
  auto ispe = get_property<Box_ispe>();
448
14.5k
  if (!ispe) {
449
1.38k
    return 0;
450
1.38k
  }
451
13.1k
  else {
452
13.1k
    return ispe->get_width();
453
13.1k
  }
454
14.5k
}
455
456
457
uint32_t ImageItem::get_ispe_height() const
458
14.5k
{
459
14.5k
  auto ispe = get_property<Box_ispe>();
460
14.5k
  if (!ispe) {
461
1.38k
    return 0;
462
1.38k
  }
463
13.1k
  else {
464
13.1k
    return ispe->get_height();
465
13.1k
  }
466
14.5k
}
467
468
469
void ImageItem::get_tile_size(uint32_t& w, uint32_t& h) const
470
0
{
471
0
  w = get_width();
472
0
  h = get_height();
473
0
}
474
475
476
Error ImageItem::postprocess_coded_image_colorspace(heif_colorspace* inout_colorspace, heif_chroma* inout_chroma) const
477
20.7k
{
478
#if 0
479
  auto pixi = m_heif_context->get_heif_file()->get_property<Box_pixi>(id);
480
  if (pixi && pixi->get_num_channels() == 1) {
481
    *out_colorspace = heif_colorspace_monochrome;
482
    *out_chroma = heif_chroma_monochrome;
483
  }
484
#endif
485
486
20.7k
  if (*inout_colorspace == heif_colorspace_YCbCr) {
487
9.80k
    auto nclx = get_color_profile_nclx();
488
9.80k
    if (nclx.get_matrix_coefficients() == 0) {
489
8
      *inout_colorspace = heif_colorspace_RGB;
490
8
      *inout_chroma = heif_chroma_444; // TODO: this or keep the original chroma?
491
8
    }
492
9.80k
  }
493
494
20.7k
  return Error::Ok;
495
20.7k
}
496
497
498
Error ImageItem::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
499
21.2k
{
500
21.2k
  auto decoderResult = get_decoder();
501
21.2k
  if (!decoderResult) {
502
294
    return decoderResult.error();
503
294
  }
504
505
20.9k
  auto decoder = *decoderResult;
506
507
20.9k
  Error err = decoder->get_coded_image_colorspace(out_colorspace, out_chroma);
508
20.9k
  if (err) {
509
265
    return err;
510
265
  }
511
512
20.7k
  postprocess_coded_image_colorspace(out_colorspace, out_chroma);
513
514
20.7k
  return Error::Ok;
515
20.9k
}
516
517
518
int ImageItem::get_luma_bits_per_pixel() const
519
20.8k
{
520
20.8k
  auto decoderResult = get_decoder();
521
20.8k
  if (!decoderResult) {
522
0
    return decoderResult.error();
523
0
  }
524
525
20.8k
  auto decoder = *decoderResult;
526
527
20.8k
  return decoder->get_luma_bits_per_pixel();
528
20.8k
}
529
530
531
int ImageItem::get_chroma_bits_per_pixel() const
532
12.9k
{
533
12.9k
  auto decoderResult = get_decoder();
534
12.9k
  if (!decoderResult) {
535
0
    return decoderResult.error();
536
0
  }
537
538
12.9k
  auto decoder = *decoderResult;
539
540
12.9k
  return decoder->get_chroma_bits_per_pixel();
541
12.9k
}
542
543
544
Result<Encoder::CodedImageData> ImageItem::encode(const std::shared_ptr<HeifPixelImage>& image,
545
                                                  heif_encoder* h_encoder,
546
                                                  const heif_encoding_options& options,
547
                                                  heif_image_input_class input_class)
548
0
{
549
0
  auto encoder = get_encoder();
550
0
  return encoder->encode(image, h_encoder, options, input_class);
551
0
}
552
553
554
void ImageItem::set_alpha_channel(std::shared_ptr<ImageItem> img)
555
24
{
556
24
  m_alpha_channel = std::move(img);
557
24
  if (!m_alpha_channel) {
558
0
    return;
559
0
  }
560
561
  // Avoid emitting a duplicate Alpha description if set_alpha_channel was
562
  // called more than once.
563
30
  for (const auto& d : get_component_descriptions()) {
564
30
    if (d.channel == heif_channel_Alpha) {
565
1
      return;
566
1
    }
567
30
  }
568
569
  // Bit depth of the alpha plane comes from the alpha aux item's coded
570
  // image (typically a monochrome HEVC/AVIF channel). Fall back to 8 bpp
571
  // if the decoder cannot tell us.
572
23
  int alpha_bpp = m_alpha_channel->get_luma_bits_per_pixel();
573
23
  if (alpha_bpp <= 0) {
574
19
    alpha_bpp = 8;
575
19
  }
576
577
23
  ComponentDescription desc;
578
23
  desc.component_id = mint_component_id();
579
23
  desc.channel = heif_channel_Alpha;
580
23
  desc.component_type = heif_unci_component_type_alpha;
581
23
  desc.datatype = heif_component_datatype_unsigned_integer;
582
23
  desc.bit_depth = static_cast<uint16_t>(alpha_bpp);
583
23
  desc.width = get_ispe_width();
584
23
  desc.height = get_ispe_height();
585
23
  desc.has_data_plane = true;
586
23
  add_component_description(std::move(desc));
587
23
}
588
589
590
void ImageItem::populate_component_descriptions()
591
35.6k
{
592
  // Idempotent: a subclass override (e.g. unci) may already have populated.
593
35.6k
  if (!get_component_descriptions().empty()) {
594
0
    return;
595
0
  }
596
597
  // Visual codecs (HEVC/AVC/AVIF/JPEG/JPEG2000/VVC). Requires the decoder to
598
  // be initialized so we can read colorspace / chroma / bit depths from the
599
  // codec config. If the decoder isn't ready (e.g. on the encoder-output
600
  // path that doesn't call initialize_decoder, or for items whose codec is
601
  // not supported), bail out and leave m_components empty.
602
35.6k
  auto decoderResult = get_decoder();
603
35.6k
  if (!decoderResult || !*decoderResult) {
604
22.5k
    return;
605
22.5k
  }
606
13.0k
  heif_colorspace colorspace = heif_colorspace_undefined;
607
13.0k
  heif_chroma chroma = heif_chroma_undefined;
608
13.0k
  if (Error err = get_coded_image_colorspace(&colorspace, &chroma); err) {
609
161
    return;
610
161
  }
611
612
12.9k
  uint32_t img_w = get_ispe_width();
613
12.9k
  uint32_t img_h = get_ispe_height();
614
12.9k
  int luma_bpp = get_luma_bits_per_pixel();
615
12.9k
  int chroma_bpp = get_chroma_bits_per_pixel();
616
12.9k
  if (luma_bpp <= 0) luma_bpp = 8;
617
12.9k
  if (chroma_bpp <= 0) chroma_bpp = luma_bpp;
618
619
12.9k
  auto emit = [this](heif_channel ch, uint16_t type, int bpp,
620
27.5k
                     uint32_t w, uint32_t h) {
621
27.5k
    ComponentDescription desc;
622
27.5k
    desc.component_id = mint_component_id();
623
27.5k
    desc.channel = ch;
624
27.5k
    desc.component_type = type;
625
27.5k
    desc.datatype = heif_component_datatype_unsigned_integer;
626
27.5k
    desc.bit_depth = static_cast<uint16_t>(bpp);
627
27.5k
    desc.width = w;
628
27.5k
    desc.height = h;
629
27.5k
    desc.has_data_plane = true;
630
27.5k
    add_component_description(std::move(desc));
631
27.5k
  };
632
633
12.9k
  switch (colorspace) {
634
5.59k
    case heif_colorspace_monochrome:
635
5.59k
      emit(heif_channel_Y, heif_unci_component_type_monochrome, luma_bpp, img_w, img_h);
636
5.59k
      break;
637
638
7.30k
    case heif_colorspace_YCbCr: {
639
7.30k
      uint32_t cw = channel_width(img_w, chroma, heif_channel_Cb);
640
7.30k
      uint32_t ch_ = channel_height(img_h, chroma, heif_channel_Cb);
641
7.30k
      emit(heif_channel_Y,  heif_unci_component_type_Y,  luma_bpp,   img_w, img_h);
642
7.30k
      emit(heif_channel_Cb, heif_unci_component_type_Cb, chroma_bpp, cw,    ch_);
643
7.30k
      emit(heif_channel_Cr, heif_unci_component_type_Cr, chroma_bpp, cw,    ch_);
644
7.30k
      break;
645
0
    }
646
647
0
    case heif_colorspace_RGB:
648
0
      emit(heif_channel_R, heif_unci_component_type_red,   luma_bpp, img_w, img_h);
649
0
      emit(heif_channel_G, heif_unci_component_type_green, luma_bpp, img_w, img_h);
650
0
      emit(heif_channel_B, heif_unci_component_type_blue,  luma_bpp, img_w, img_h);
651
0
      break;
652
653
0
    default:
654
      // Other colorspaces (filter_array, nonvisual) are unci-only and are
655
      // populated by the unci override.
656
0
      break;
657
12.9k
  }
658
12.9k
}
659
660
661
bool ImageItem::populate_descriptions_from_child(const ImageItem& child,
662
                                                  uint32_t child_w, uint32_t child_h)
663
17
{
664
17
  const auto& child_descs = child.get_component_descriptions();
665
17
  if (child_descs.empty()) {
666
17
    return false;
667
17
  }
668
669
0
  uint32_t img_w = get_ispe_width();
670
0
  uint32_t img_h = get_ispe_height();
671
0
  if (img_w == 0 || img_h == 0 || child_w == 0 || child_h == 0) {
672
0
    return false;
673
0
  }
674
675
0
  for (const auto& src : child_descs) {
676
0
    ComponentDescription d = src;
677
0
    d.component_id = mint_component_id();
678
0
    if (src.has_data_plane) {
679
      // Preserve subsampling ratio: a half-size chroma plane in the child
680
      // becomes half of img_w/h in the wrapper.
681
0
      uint64_t w64 = static_cast<uint64_t>(img_w) * src.width / child_w;
682
0
      uint64_t h64 = static_cast<uint64_t>(img_h) * src.height / child_h;
683
0
      d.width = static_cast<uint32_t>(w64);
684
0
      d.height = static_cast<uint32_t>(h64);
685
0
    }
686
0
    add_component_description(std::move(d));
687
0
  }
688
0
  return true;
689
0
}
690
691
692
std::vector<std::shared_ptr<Box_colr> >
693
ImageItem::add_color_profile(const std::shared_ptr<HeifPixelImage>& image,
694
                             const heif_encoding_options& options,
695
                             heif_image_input_class input_class,
696
                             const heif_color_profile_nclx* target_heif_nclx)
697
0
{
698
0
  std::vector<std::shared_ptr<Box_colr> > colr_boxes;
699
700
0
  if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) {
701
    // No color profile for non-visual images (e.g. elevation data)
702
0
    if (image->get_colorspace() == heif_colorspace_custom) {
703
0
      return colr_boxes;
704
0
    }
705
706
0
    auto icc_profile = image->get_color_profile_icc();
707
0
    if (icc_profile) {
708
0
      auto colr = std::make_shared<Box_colr>();
709
0
      colr->set_color_profile(icc_profile);
710
0
      colr_boxes.push_back(colr);
711
0
    }
712
713
714
    // save nclx profile
715
716
0
    bool save_nclx_profile = (options.output_nclx_profile != nullptr);
717
718
    // if there is an ICC profile, only save NCLX when we chose to save both profiles
719
0
    if (icc_profile && !(options.version >= 3 &&
720
0
                         options.save_two_colr_boxes_when_ICC_and_nclx_available)) {
721
0
      save_nclx_profile = false;
722
0
    }
723
724
    // we might have turned off nclx completely because macOS/iOS cannot read it
725
0
    if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) {
726
0
      save_nclx_profile = false;
727
0
    }
728
729
0
    if (save_nclx_profile) {
730
0
      auto target_nclx_profile = std::make_shared<color_profile_nclx>();
731
0
      target_nclx_profile->set_from_heif_color_profile_nclx(target_heif_nclx);
732
733
0
      auto colr = std::make_shared<Box_colr>();
734
0
      colr->set_color_profile(target_nclx_profile);
735
0
      colr_boxes.push_back(colr);
736
0
    }
737
0
  }
738
739
0
  return colr_boxes;
740
0
}
741
742
743
Error ImageItem::transform_requested_tile_position_to_original_tile_position(uint32_t& tile_x, uint32_t& tile_y) const
744
0
{
745
0
  Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
746
0
  if (!propertiesResult) {
747
0
    return propertiesResult.error();
748
0
  }
749
750
0
  heif_image_tiling tiling = get_heif_image_tiling();
751
752
  //for (auto& prop : std::ranges::reverse_view(propertiesResult.value)) {
753
0
  for (auto propIter = propertiesResult->rbegin(); propIter != propertiesResult->rend(); propIter++) {
754
0
    if (auto irot = std::dynamic_pointer_cast<Box_irot>(*propIter)) {
755
0
      switch (irot->get_rotation_ccw()) {
756
0
        case 90: {
757
0
          uint32_t tx0 = tiling.num_columns - 1 - tile_y;
758
0
          uint32_t ty0 = tile_x;
759
0
          tile_y = ty0;
760
0
          tile_x = tx0;
761
0
          break;
762
0
        }
763
0
        case 270: {
764
0
          uint32_t tx0 = tile_y;
765
0
          uint32_t ty0 = tiling.num_rows - 1 - tile_x;
766
0
          tile_y = ty0;
767
0
          tile_x = tx0;
768
0
          break;
769
0
        }
770
0
        case 180: {
771
0
          tile_x = tiling.num_columns - 1 - tile_x;
772
0
          tile_y = tiling.num_rows - 1 - tile_y;
773
0
          break;
774
0
        }
775
0
        case 0:
776
0
          break;
777
0
        default:
778
0
          assert(false);
779
0
          break;
780
0
      }
781
0
    }
782
783
0
    if (auto imir = std::dynamic_pointer_cast<Box_imir>(*propIter)) {
784
0
      switch (imir->get_mirror_direction()) {
785
0
        case heif_transform_mirror_direction_horizontal:
786
0
          tile_x = tiling.num_columns - 1 - tile_x;
787
0
          break;
788
0
        case heif_transform_mirror_direction_vertical:
789
0
          tile_y = tiling.num_rows - 1 - tile_y;
790
0
          break;
791
0
        default:
792
0
          assert(false);
793
0
          break;
794
0
      }
795
0
    }
796
0
  }
797
798
0
  return Error::Ok;
799
0
}
800
801
802
void ImageItem::set_clli(const heif_content_light_level& clli)
803
0
{
804
0
  ImageDescription::set_clli(clli);
805
0
  add_property(create_clli_box(), false);
806
0
}
807
808
809
void ImageItem::set_mdcv(const heif_mastering_display_colour_volume& mdcv)
810
0
{
811
0
  ImageDescription::set_mdcv(mdcv);
812
0
  add_property(create_mdcv_box(), false);
813
0
}
814
815
816
void ImageItem::set_pixel_ratio(uint32_t h, uint32_t v)
817
0
{
818
0
  ImageDescription::set_pixel_ratio(h, v);
819
0
  add_property(create_pasp_box(), false);
820
0
}
821
822
823
void ImageItem::set_color_profile_nclx(const nclx_profile& profile)
824
304
{
825
304
  ImageDescription::set_color_profile_nclx(profile);
826
304
  add_property(create_colr_box_nclx(), false);
827
304
}
828
829
830
void ImageItem::set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile)
831
2.02k
{
832
2.02k
  ImageDescription::set_color_profile_icc(profile);
833
2.02k
  add_property(create_colr_box_icc(), false);
834
2.02k
}
835
836
#if HEIF_WITH_OMAF
837
void ImageItem::set_omaf_image_projection(heif_omaf_image_projection projection)
838
0
{
839
0
  ImageDescription::set_omaf_image_projection(projection);
840
0
  add_property(create_prfr_box(), true);
841
0
}
842
#endif
843
844
845
Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_image(const heif_decoding_options& options,
846
                                                                bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
847
                                                                std::set<heif_item_id> processed_ids) const
848
10.3k
{
849
  // Check for cycles before taking m_decode_mutex: a derived item that
850
  // (transitively) references itself would otherwise re-enter decode_image()
851
  // on the same ImageItem and self-deadlock on the non-recursive mutex.
852
  // The matching insert lives inside decode_compressed_image() of derived
853
  // items (grid/overlay/iden), so the current item is in processed_ids only
854
  // when called from one of its own descendants.
855
10.3k
  if (processed_ids.contains(m_id)) {
856
0
    return Error{heif_error_Invalid_input,
857
0
                 heif_suberror_Unspecified,
858
0
                 "'iref' has cyclic references"};
859
0
  }
860
861
10.3k
  std::lock_guard<std::mutex> lock(m_decode_mutex);
862
863
  // --- check whether image size (according to 'ispe') exceeds maximum
864
865
10.3k
  if (!decode_tile_only) {
866
10.3k
    auto ispe = get_property<Box_ispe>();
867
10.3k
    if (ispe) {
868
10.2k
      Error err = check_for_valid_image_size(get_context()->get_security_limits(), ispe->get_width(), ispe->get_height());
869
10.2k
      if (err) {
870
7
        return err;
871
7
      }
872
10.2k
    }
873
10.3k
  }
874
875
876
  // --- transform tile position
877
878
10.3k
  if (decode_tile_only && options.ignore_transformations == false) {
879
0
    if (Error error = transform_requested_tile_position_to_original_tile_position(tile_x0, tile_y0)) {
880
0
      return error;
881
0
    }
882
0
  }
883
884
  // --- decode image
885
886
10.3k
  Result<std::shared_ptr<HeifPixelImage>> decodingResult = decode_compressed_image(options, decode_tile_only, tile_x0, tile_y0, processed_ids);
887
10.3k
  if (!decodingResult) {
888
8.84k
    return decodingResult.error();
889
8.84k
  }
890
891
1.54k
  auto img = *decodingResult;
892
1.54k
  if (!img) {
893
    // Can happen if missing tiled image is decoded in non-strict mode.
894
606
    return Error(heif_error_Decoder_plugin_error, heif_suberror_Unspecified);
895
606
  }
896
897
  // --- validate the decoded image against the signaled size (pre-transform)
898
899
936
  if (Error err = check_decoded_image_size(*img, decode_tile_only, tile_x0, tile_y0)) {
900
262
    return err;
901
262
  }
902
903
674
  std::shared_ptr<HeifFile> file = m_heif_context->get_heif_file();
904
905
906
  // --- apply image transformations
907
908
674
  if (options.ignore_transformations == false) {
909
673
    Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
910
673
    if (!propertiesResult) {
911
0
      return propertiesResult.error();
912
0
    }
913
914
673
    const std::vector<std::shared_ptr<Box>>& properties = *propertiesResult;
915
916
2.85k
    for (const auto& property : properties) {
917
2.85k
      if (auto rot = std::dynamic_pointer_cast<Box_irot>(property)) {
918
438
        auto rotateResult = img->rotate_ccw(rot->get_rotation_ccw(), m_heif_context->get_security_limits());
919
438
        if (!rotateResult) {
920
0
          return rotateResult.error();
921
0
        }
922
923
438
        img = *rotateResult;
924
438
      }
925
926
927
2.85k
      if (auto mirror = std::dynamic_pointer_cast<Box_imir>(property)) {
928
227
        auto mirrorResult = img->mirror_inplace(mirror->get_mirror_direction(),
929
227
                                                get_context()->get_security_limits());
930
227
        if (!mirrorResult) {
931
0
          return mirrorResult.error();
932
0
        }
933
227
        img = *mirrorResult;
934
227
      }
935
936
937
2.85k
      if (!decode_tile_only) {
938
        // For tiles decoding, we do not process the 'clap' because this is handled by a shift of the tiling grid.
939
940
2.85k
        if (auto clap = std::dynamic_pointer_cast<Box_clap>(property)) {
941
421
          std::shared_ptr<HeifPixelImage> clap_img;
942
943
421
          uint32_t img_width = img->get_width();
944
421
          uint32_t img_height = img->get_height();
945
946
421
          int left = clap->left_rounded(img_width);
947
421
          int right = clap->right_rounded(img_width);
948
421
          int top = clap->top_rounded(img_height);
949
421
          int bottom = clap->bottom_rounded(img_height);
950
951
421
          if (left < 0) { left = 0; }
952
421
          if (top < 0) { top = 0; }
953
954
421
          if ((uint32_t) right >= img_width) { right = img_width - 1; }
955
421
          if ((uint32_t) bottom >= img_height) { bottom = img_height - 1; }
956
957
421
          if (left > right ||
958
392
              top > bottom) {
959
44
            return Error(heif_error_Invalid_input,
960
44
                         heif_suberror_Invalid_clean_aperture);
961
44
          }
962
963
377
          auto cropResult = img->crop(left, right, top, bottom, m_heif_context->get_security_limits());
964
377
          if (!cropResult) {
965
0
            return cropResult.error();
966
0
          }
967
968
377
          img = *cropResult;
969
377
        }
970
2.85k
      }
971
2.85k
    }
972
673
  }
973
974
975
  // --- add alpha channel, if available
976
977
  // TODO: this if statement is probably wrong. When we have a tiled image with alpha
978
  // channel, then the alpha images should be associated with their respective tiles.
979
  // However, the tile images are not part of the m_all_images list.
980
  // Fix this, when we have a test image available.
981
982
630
  std::shared_ptr<ImageItem> alpha_image = get_alpha_channel();
983
630
  if (alpha_image) {
984
0
    if (alpha_image->get_item_error()) {
985
0
      return alpha_image->get_item_error();
986
0
    }
987
988
0
    auto alphaDecodingResult = alpha_image->decode_image(options, decode_tile_only, tile_x0, tile_y0, processed_ids);
989
0
    if (!alphaDecodingResult) {
990
0
      return alphaDecodingResult.error();
991
0
    }
992
993
0
    std::shared_ptr<HeifPixelImage> alpha = *alphaDecodingResult;
994
995
    // TODO: check that sizes are the same and that we have an Y channel
996
    // BUT: is there any indication in the standard that the alpha channel should have the same size?
997
998
    // TODO: convert in case alpha is decoded as RGB interleaved
999
1000
0
    heif_channel channel;
1001
0
    switch (alpha->get_colorspace()) {
1002
0
      case heif_colorspace_YCbCr:
1003
0
      case heif_colorspace_monochrome:
1004
0
        channel = heif_channel_Y;
1005
0
        break;
1006
0
      case heif_colorspace_RGB:
1007
0
        channel = heif_channel_R;
1008
0
        break;
1009
0
      case heif_colorspace_undefined:
1010
0
      default:
1011
0
        return Error(heif_error_Invalid_input,
1012
0
                     heif_suberror_Unsupported_color_conversion);
1013
0
    }
1014
1015
1016
    // TODO: we should include a decoding option to control whether libheif should automatically scale the alpha channel, and if so, which scaling filter (enum: Off, NN, Bilinear, ...).
1017
    //       It might also be that a specific output format implies that alpha is scaled (RGBA32). That would favor an enum for the scaling filter option + a bool to switch auto-filtering on.
1018
    //       But we can only do this when libheif itself doesn't assume anymore that the alpha channel has the same resolution.
1019
1020
0
    if ((alpha_image->get_width() != img->get_width()) || (alpha_image->get_height() != img->get_height())) {
1021
0
      std::shared_ptr<HeifPixelImage> scaled_alpha;
1022
0
      Error err = alpha->scale_nearest_neighbor(scaled_alpha, img->get_width(), img->get_height(), m_heif_context->get_security_limits());
1023
0
      if (err) {
1024
0
        return err;
1025
0
      }
1026
0
      alpha = std::move(scaled_alpha);
1027
0
    }
1028
0
    img->transfer_channel_from_image_as(alpha, channel, heif_channel_Alpha);
1029
1030
0
    if (is_premultiplied_alpha()) {
1031
0
      img->set_premultiplied_alpha(true);
1032
0
    }
1033
0
  }
1034
1035
1036
  // --- set color profile
1037
1038
  // If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion.
1039
  // Otherwise, use the profile that is stored in the image stream itself and then set the
1040
  // (non-NCLX) profile later.
1041
630
  auto nclx = get_color_profile_nclx();
1042
630
  if (!nclx.is_undefined()) {
1043
    // If the decoder plugin populated an NCLX profile from the bitstream's
1044
    // color signalling (e.g. HEVC SPS VUI, AV1 sequence header), compare it
1045
    // against the colr box. Per ISO/IEC 14496-12 and ISO/IEC 23000-22 (MIAF)
1046
    // the colr box overrides the bitstream, but a mismatch is a strong
1047
    // indication of a muxer bug (e.g. some Sony cameras mis-tag full_range_flag
1048
    // in colr while the bitstream VUI is correct, see issue #1770) and is
1049
    // worth surfacing as a warning.
1050
0
    auto bitstream_nclx = img->get_color_profile_nclx();
1051
0
    if (!bitstream_nclx.is_undefined()) {
1052
0
      auto cicp_mismatch = [](uint16_t bs, uint16_t cr) {
1053
0
        return bs != 2 /*unspecified*/ && cr != 2 && bs != cr;
1054
0
      };
1055
0
      if (cicp_mismatch(bitstream_nclx.m_colour_primaries,        nclx.m_colour_primaries)        ||
1056
0
          cicp_mismatch(bitstream_nclx.m_transfer_characteristics, nclx.m_transfer_characteristics) ||
1057
0
          cicp_mismatch(bitstream_nclx.m_matrix_coefficients,     nclx.m_matrix_coefficients)     ||
1058
0
          bitstream_nclx.m_full_range_flag != nclx.m_full_range_flag) {
1059
0
        std::stringstream msg;
1060
0
        msg << "colr box NCLX ("
1061
0
            << nclx.m_colour_primaries << "/"
1062
0
            << nclx.m_transfer_characteristics << "/"
1063
0
            << nclx.m_matrix_coefficients << "/"
1064
0
            << (nclx.m_full_range_flag ? "full" : "limited")
1065
0
            << ") disagrees with bitstream signalling ("
1066
0
            << bitstream_nclx.m_colour_primaries << "/"
1067
0
            << bitstream_nclx.m_transfer_characteristics << "/"
1068
0
            << bitstream_nclx.m_matrix_coefficients << "/"
1069
0
            << (bitstream_nclx.m_full_range_flag ? "full" : "limited")
1070
0
            << "); colr takes precedence per ISO/IEC 14496-12 and ISO/IEC 23000-22 (MIAF)";
1071
0
        add_decoding_warning({heif_error_Invalid_input,
1072
0
                              heif_suberror_NCLX_colr_VUI_mismatch,
1073
0
                              msg.str()});
1074
0
      }
1075
0
    }
1076
0
    img->set_color_profile_nclx(nclx);
1077
0
  }
1078
1079
630
  auto icc = get_color_profile_icc();
1080
630
  if (icc) {
1081
0
    img->set_color_profile_icc(icc);
1082
0
  }
1083
1084
1085
  // --- attach metadata to image
1086
1087
630
  {
1088
630
    auto ipco_box = file->get_ipco_box();
1089
630
    auto ipma_box = file->get_ipma_box();
1090
1091
    // CLLI
1092
1093
630
    auto clli = get_property<Box_clli>();
1094
630
    if (clli) {
1095
0
      img->set_clli(clli->clli);
1096
0
    }
1097
1098
    // MDCV
1099
1100
630
    auto mdcv = get_property<Box_mdcv>();
1101
630
    if (mdcv) {
1102
0
      img->set_mdcv(mdcv->mdcv);
1103
0
    }
1104
1105
    // PASP
1106
1107
630
    auto pasp = get_property<Box_pasp>();
1108
630
    if (pasp) {
1109
0
      img->set_pixel_ratio(pasp->hSpacing, pasp->vSpacing);
1110
0
    }
1111
1112
    // TAI
1113
1114
630
    auto itai = get_property<Box_itai>();
1115
630
    if (itai) {
1116
0
      img->set_tai_timestamp(itai->get_tai_timestamp_packet());
1117
0
    }
1118
1119
    // GIMI content ID
1120
1121
630
    auto gimi_content_id = get_property<Box_gimi_content_id>();
1122
630
    if (gimi_content_id) {
1123
0
      img->set_gimi_sample_content_id(gimi_content_id->get_content_id());
1124
0
    }
1125
1126
630
#if HEIF_WITH_OMAF
1127
    // Image projection (OMAF)
1128
630
    auto prfr = get_property<Box_prfr>();
1129
630
    if (prfr) {
1130
0
      img->set_omaf_image_projection(prfr->get_omaf_image_projection());
1131
0
    }
1132
630
#endif
1133
630
  }
1134
1135
1136
630
  return img;
1137
630
}
1138
1139
#if 0
1140
Result<std::vector<uint8_t>> ImageItem::read_bitstream_configuration_data_override(heif_item_id itemId, heif_compression_format format) const
1141
{
1142
  auto item_codec = ImageItem::alloc_for_compression_format(const_cast<HeifContext*>(get_context()), format);
1143
  assert(item_codec);
1144
1145
  Error err = item_codec->init_decoder_from_item(itemId);
1146
  if (err) {
1147
    return err;
1148
  }
1149
1150
  return item_codec->read_bitstream_configuration_data(itemId);
1151
}
1152
#endif
1153
1154
Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_compressed_image(const heif_decoding_options& options,
1155
                                                                           bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
1156
                                                                           std::set<heif_item_id> processed_ids) const
1157
9.03k
{
1158
9.03k
  if (processed_ids.contains(m_id)) {
1159
0
    return Error{heif_error_Invalid_input,
1160
0
                 heif_suberror_Unspecified,
1161
0
                 "'iref' has cyclic references"};
1162
0
  }
1163
1164
9.03k
  processed_ids.insert(m_id);
1165
1166
1167
9.03k
  DataExtent extent;
1168
9.03k
  extent.set_from_image_item(get_file(), get_id());
1169
1170
9.03k
  auto decoderResult = get_decoder();
1171
9.03k
  if (!decoderResult) {
1172
0
    return decoderResult.error();
1173
0
  }
1174
1175
9.03k
  auto decoder = *decoderResult;
1176
1177
9.03k
  decoder->set_data_extent(std::move(extent));
1178
1179
  // Tighten max_image_size_pixels for this decode so a decoder plugin (e.g.
1180
  // dav1d) cannot allocate buffers far larger than the ispe-declared size
1181
  // when the codec bitstream lies about its dimensions.
1182
9.03k
  heif_security_limits tightened = tighten_image_size_limit_for_ispe(
1183
9.03k
      get_context()->get_security_limits(),
1184
9.03k
      get_width(), get_height(),
1185
9.03k
      max_coding_unit_size_for_codec(get_compression_format()));
1186
1187
9.03k
  return decoder->decode_single_frame_from_compressed_data(options, &tightened);
1188
9.03k
}
1189
1190
1191
Error ImageItem::check_decoded_image_size(const HeifPixelImage& img,
1192
                                          bool decode_tile_only,
1193
                                          uint32_t tile_x0, uint32_t tile_y0) const
1194
935
{
1195
935
  uint32_t expected_w, expected_h;
1196
1197
935
  if (decode_tile_only) {
1198
    // The decoded buffer is a single tile, sized to the signaled tile size.
1199
0
    get_tile_size(expected_w, expected_h);
1200
0
  }
1201
935
  else {
1202
    // Pre-transform coded size from the 'ispe' property.
1203
935
    expected_w = get_ispe_width();
1204
935
    expected_h = get_ispe_height();
1205
935
  }
1206
1207
  // No 'ispe' / no tile size known -> cannot validate (a missing-'ispe' warning is
1208
  // already emitted upstream). Skip rather than reject.
1209
935
  if (expected_w == 0 || expected_h == 0) {
1210
124
    return Error::Ok;
1211
124
  }
1212
1213
811
  if (!img.primary_planes_have_size(expected_w, expected_h)) {
1214
262
    return Error{heif_error_Invalid_input,
1215
262
                 heif_suberror_Invalid_image_size,
1216
262
                 "Decoded image does not have the size signaled in the file."};
1217
262
  }
1218
1219
549
  return Error::Ok;
1220
811
}
1221
1222
1223
heif_image_tiling ImageItem::get_heif_image_tiling() const
1224
0
{
1225
  // --- Return a dummy tiling consisting of only a single tile for the whole image
1226
1227
0
  heif_image_tiling tiling{};
1228
1229
0
  tiling.version = 1;
1230
0
  tiling.num_columns = 1;
1231
0
  tiling.num_rows = 1;
1232
1233
0
  tiling.tile_width = m_width;
1234
0
  tiling.tile_height = m_height;
1235
0
  tiling.image_width = m_width;
1236
0
  tiling.image_height = m_height;
1237
1238
0
  tiling.top_offset = 0;
1239
0
  tiling.left_offset = 0;
1240
0
  tiling.number_of_extra_dimensions = 0;
1241
1242
0
  for (uint32_t& s : tiling.extra_dimension_size) {
1243
0
    s = 0;
1244
0
  }
1245
1246
0
  return tiling;
1247
0
}
1248
1249
1250
Result<std::vector<std::shared_ptr<Box>>> ImageItem::get_properties() const
1251
673
{
1252
673
  std::vector<std::shared_ptr<Box>> properties;
1253
673
  auto ipco_box = get_file()->get_ipco_box();
1254
673
  auto ipma_box = get_file()->get_ipma_box();
1255
673
  Error error = ipco_box->get_properties_for_item_ID(m_id, ipma_box, properties);
1256
673
  if (error) {
1257
0
    return error;
1258
0
  }
1259
1260
673
  return properties;
1261
673
}
1262
1263
1264
bool ImageItem::has_essential_property_other_than(const std::set<uint32_t>& props) const
1265
0
{
1266
0
  Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
1267
0
  if (!propertiesResult) {
1268
0
    return false;
1269
0
  }
1270
1271
0
  for (const auto& property : *propertiesResult) {
1272
0
    if (is_property_essential(property) &&
1273
0
        props.find(property->get_short_type()) == props.end()) {
1274
0
      return true;
1275
0
    }
1276
0
  }
1277
1278
0
  return false;
1279
0
}
1280
1281
1282
Error ImageItem::process_image_transformations_on_tiling(heif_image_tiling& tiling) const
1283
0
{
1284
0
  Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
1285
0
  if (!propertiesResult) {
1286
0
    return propertiesResult.error();
1287
0
  }
1288
1289
0
  const std::vector<std::shared_ptr<Box>>& properties = *propertiesResult;
1290
1291
0
  uint32_t left_excess = 0;
1292
0
  uint32_t top_excess = 0;
1293
0
  uint32_t right_excess;
1294
0
  uint32_t bottom_excess;
1295
1296
  // Prevent divide by zero.
1297
1298
0
  if (tiling.tile_width != 0 && tiling.tile_height != 0) {
1299
0
    right_excess = tiling.image_width % tiling.tile_width;
1300
0
    bottom_excess = tiling.image_height % tiling.tile_height;
1301
0
  }
1302
0
  else {
1303
0
    right_excess = 0;
1304
0
    bottom_excess = 0;
1305
0
  }
1306
1307
1308
0
  for (const auto& property : properties) {
1309
1310
    // --- rotation
1311
1312
0
    if (auto rot = std::dynamic_pointer_cast<Box_irot>(property)) {
1313
0
      int angle = rot->get_rotation_ccw();
1314
0
      if (angle == 90 || angle == 270) {
1315
0
        std::swap(tiling.tile_width, tiling.tile_height);
1316
0
        std::swap(tiling.image_width, tiling.image_height);
1317
0
        std::swap(tiling.num_rows, tiling.num_columns);
1318
0
      }
1319
1320
0
      switch (angle) {
1321
0
        case 0:
1322
0
          break;
1323
0
        case 180:
1324
0
          std::swap(left_excess, right_excess);
1325
0
          std::swap(top_excess, bottom_excess);
1326
0
          break;
1327
0
        case 90: {
1328
0
          uint32_t old_top_excess = top_excess;
1329
0
          top_excess = right_excess;
1330
0
          right_excess = bottom_excess;
1331
0
          bottom_excess = left_excess;
1332
0
          left_excess = old_top_excess;
1333
0
          break;
1334
0
        }
1335
0
        case 270: {
1336
0
          uint32_t old_top_excess = top_excess;
1337
0
          top_excess = left_excess;
1338
0
          left_excess = bottom_excess;
1339
0
          bottom_excess = right_excess;
1340
0
          right_excess = old_top_excess;
1341
0
          break;
1342
0
        }
1343
0
        default:
1344
0
          assert(false);
1345
0
          break;
1346
0
      }
1347
0
    }
1348
1349
    // --- mirror
1350
1351
0
    if (auto mirror = std::dynamic_pointer_cast<Box_imir>(property)) {
1352
0
      switch (mirror->get_mirror_direction()) {
1353
0
        case heif_transform_mirror_direction_horizontal:
1354
0
          std::swap(left_excess, right_excess);
1355
0
          break;
1356
0
        case heif_transform_mirror_direction_vertical:
1357
0
          std::swap(top_excess, bottom_excess);
1358
0
          break;
1359
0
        default:
1360
0
          assert(false);
1361
0
          break;
1362
0
      }
1363
0
    }
1364
1365
    // --- crop
1366
1367
0
    if (auto clap = std::dynamic_pointer_cast<Box_clap>(property)) {
1368
0
      std::shared_ptr<HeifPixelImage> clap_img;
1369
1370
0
      int left = clap->left_rounded(tiling.image_width);
1371
0
      int right = clap->right_rounded(tiling.image_width);
1372
0
      int top = clap->top_rounded(tiling.image_height);
1373
0
      int bottom = clap->bottom_rounded(tiling.image_height);
1374
1375
0
      if (left < 0) { left = 0; }
1376
0
      if (top < 0) { top = 0; }
1377
1378
0
      if ((uint32_t)right >= tiling.image_width) { right = tiling.image_width - 1; }
1379
0
      if ((uint32_t)bottom >= tiling.image_height) { bottom = tiling.image_height - 1; }
1380
1381
0
      if (left > right ||
1382
0
          top > bottom) {
1383
0
        return {heif_error_Invalid_input,
1384
0
                heif_suberror_Invalid_clean_aperture};
1385
0
      }
1386
1387
0
      left_excess += left;
1388
0
      right_excess += right;
1389
0
      top_excess += top;
1390
0
      bottom_excess += bottom;
1391
0
    }
1392
0
  }
1393
1394
0
  tiling.left_offset = left_excess;
1395
0
  tiling.top_offset = top_excess;
1396
1397
0
  return Error::Ok;
1398
0
}