Coverage Report

Created: 2025-11-14 07:32

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 <ranges>
44
45
#if WITH_UNCOMPRESSED_CODEC
46
#include "image-items/unc_image.h"
47
#endif
48
49
50
ImageItem::ImageItem(HeifContext* context)
51
16.8k
    : m_heif_context(context)
52
16.8k
{
53
16.8k
  memset(&m_depth_representation_info, 0, sizeof(m_depth_representation_info));
54
16.8k
}
55
56
57
ImageItem::ImageItem(HeifContext* context, heif_item_id id)
58
16.8k
    : ImageItem(context)
59
16.8k
{
60
16.8k
  m_id = id;
61
16.8k
}
62
63
64
bool ImageItem::is_property_essential(const std::shared_ptr<Box>& property) const
65
0
{
66
0
  if (property->get_short_type() == fourcc("ispe")) {
67
0
    return is_ispe_essential();
68
0
  }
69
0
  else {
70
0
    return property->is_essential();
71
0
  }
72
0
}
73
74
75
std::shared_ptr<HeifFile> ImageItem::get_file() const
76
11.1k
{
77
11.1k
  return m_heif_context->get_heif_file();
78
11.1k
}
79
80
81
heif_property_id ImageItem::add_property(std::shared_ptr<Box> property, bool essential)
82
4.09k
{
83
4.09k
  if (!property) {
84
53
    return 0;
85
53
  }
86
87
  // TODO: is this correct? What happens when add_property does deduplicate the property?
88
4.04k
  m_properties.push_back(property);
89
4.04k
  return get_file()->add_property(get_id(), property, essential);
90
4.09k
}
91
92
93
heif_property_id ImageItem::add_property_without_deduplication(std::shared_ptr<Box> property, bool essential)
94
0
{
95
0
  if (!property) {
96
0
    return 0;
97
0
  }
98
99
0
  m_properties.push_back(property);
100
0
  return get_file()->add_property_without_deduplication(get_id(), property, essential);
101
0
}
102
103
104
heif_compression_format ImageItem::compression_format_from_fourcc_infe_type(uint32_t type)
105
0
{
106
0
  switch (type) {
107
0
    case fourcc("jpeg"):
108
0
      return heif_compression_JPEG;
109
0
    case fourcc("hvc1"):
110
0
      return heif_compression_HEVC;
111
0
    case fourcc("av01"):
112
0
      return heif_compression_AV1;
113
0
    case fourcc("vvc1"):
114
0
      return heif_compression_VVC;
115
0
    case fourcc("j2k1"):
116
0
      return heif_compression_JPEG2000;
117
0
    case fourcc("unci"):
118
0
      return heif_compression_uncompressed;
119
0
    case fourcc("mski"):
120
0
      return heif_compression_mask;
121
0
    default:
122
0
      return heif_compression_undefined;
123
0
  }
124
0
}
125
126
uint32_t ImageItem::compression_format_to_fourcc_infe_type(heif_compression_format format)
127
0
{
128
0
  switch (format) {
129
0
    case heif_compression_JPEG:
130
0
      return fourcc("jpeg");
131
0
    case heif_compression_HEVC:
132
0
      return fourcc("hvc1");
133
0
    case heif_compression_AV1:
134
0
      return fourcc("av01");
135
0
    case heif_compression_VVC:
136
0
      return fourcc("vvc1");
137
0
    case heif_compression_JPEG2000:
138
0
      return fourcc("j2k1");
139
0
    case heif_compression_uncompressed:
140
0
      return fourcc("unci");
141
0
    case heif_compression_mask:
142
0
      return fourcc("mski");
143
0
    default:
144
0
      return 0;
145
0
  }
146
0
}
147
148
149
std::shared_ptr<ImageItem> ImageItem::alloc_for_infe_box(HeifContext* ctx, const std::shared_ptr<Box_infe>& infe)
150
18.0k
{
151
18.0k
  uint32_t item_type = infe->get_item_type_4cc();
152
18.0k
  heif_item_id id = infe->get_item_ID();
153
154
18.0k
  if (item_type == fourcc("jpeg") ||
155
17.8k
      (item_type == fourcc("mime") && infe->get_content_type() == "image/jpeg")) {
156
229
    return std::make_shared<ImageItem_JPEG>(ctx, id);
157
229
  }
158
17.8k
  else if (item_type == fourcc("hvc1")) {
159
10.4k
    return std::make_shared<ImageItem_HEVC>(ctx, id);
160
10.4k
  }
161
7.45k
  else if (item_type == fourcc("av01")) {
162
1.84k
    return std::make_shared<ImageItem_AVIF>(ctx, id);
163
1.84k
  }
164
5.60k
  else if (item_type == fourcc("vvc1")) {
165
20
    return std::make_shared<ImageItem_VVC>(ctx, id);
166
20
  }
167
5.58k
  else if (item_type == fourcc("avc1")) {
168
49
    return std::make_shared<ImageItem_AVC>(ctx, id);
169
49
  }
170
5.53k
  else if (item_type == fourcc("unci")) {
171
#if WITH_UNCOMPRESSED_CODEC
172
    return std::make_shared<ImageItem_uncompressed>(ctx, id);
173
#else
174
    // It is an image item type that we do not support. Thus, generate an ImageItem_Error.
175
176
9
    std::stringstream sstr;
177
9
    sstr << "Image item of type '" << fourcc_to_string(item_type) << "' is not supported.";
178
9
    Error err{ heif_error_Unsupported_feature, heif_suberror_Unsupported_image_type, sstr.str() };
179
9
    return std::make_shared<ImageItem_Error>(item_type, id, err);
180
9
#endif
181
9
  }
182
5.52k
  else if (item_type == fourcc("j2k1")) {
183
32
    return std::make_shared<ImageItem_JPEG2000>(ctx, id);
184
32
  }
185
5.49k
  else if (item_type == fourcc("lhv1")) {
186
3
    return std::make_shared<ImageItem_Error>(item_type, id,
187
3
                                             Error{heif_error_Unsupported_feature,
188
3
                                                   heif_suberror_Unsupported_image_type,
189
3
                                                   "Layered HEVC images (lhv1) are not supported yet"});
190
3
  }
191
5.49k
  else if (item_type == fourcc("mski")) {
192
35
    return std::make_shared<ImageItem_mask>(ctx, id);
193
35
  }
194
5.45k
  else if (item_type == fourcc("grid")) {
195
804
    return std::make_shared<ImageItem_Grid>(ctx, id);
196
804
  }
197
4.65k
  else if (item_type == fourcc("iovl")) {
198
292
    return std::make_shared<ImageItem_Overlay>(ctx, id);
199
292
  }
200
4.36k
  else if (item_type == fourcc("iden")) {
201
78
    return std::make_shared<ImageItem_iden>(ctx, id);
202
78
  }
203
4.28k
  else if (item_type == fourcc("tili")) {
204
12
    return std::make_shared<ImageItem_Tiled>(ctx, id);
205
12
  }
206
4.27k
  else {
207
    // This item has an unknown type. It could be an image or anything else.
208
    // Do not process the item.
209
210
4.27k
    return nullptr;
211
4.27k
  }
212
18.0k
}
213
214
215
std::shared_ptr<ImageItem> ImageItem::alloc_for_compression_format(HeifContext* ctx, heif_compression_format format)
216
0
{
217
0
  switch (format) {
218
0
    case heif_compression_JPEG:
219
0
      return std::make_shared<ImageItem_JPEG>(ctx);
220
0
    case heif_compression_HEVC:
221
0
      return std::make_shared<ImageItem_HEVC>(ctx);
222
0
    case heif_compression_AV1:
223
0
      return std::make_shared<ImageItem_AVIF>(ctx);
224
0
    case heif_compression_VVC:
225
0
      return std::make_shared<ImageItem_VVC>(ctx);
226
#if WITH_UNCOMPRESSED_CODEC
227
    case heif_compression_uncompressed:
228
      return std::make_shared<ImageItem_uncompressed>(ctx);
229
#endif
230
0
    case heif_compression_JPEG2000:
231
0
    case heif_compression_HTJ2K:
232
0
      return std::make_shared<ImageItem_JPEG2000>(ctx);
233
0
    case heif_compression_mask:
234
0
      return std::make_shared<ImageItem_mask>(ctx);
235
0
    default:
236
0
      assert(false);
237
0
      return nullptr;
238
0
  }
239
0
}
240
241
242
Result<Encoder::CodedImageData> ImageItem::encode_to_bitstream_and_boxes(const std::shared_ptr<HeifPixelImage>& image,
243
                                                                           heif_encoder* encoder,
244
                                                                           const heif_encoding_options& options,
245
                                                                           heif_image_input_class input_class)
246
0
{
247
  // === generate compressed image bitstream
248
249
0
  Result<Encoder::CodedImageData> encodeResult = encode(image, encoder, options, input_class);
250
0
  if (!encodeResult) {
251
0
    return encodeResult;
252
0
  }
253
254
0
  Encoder::CodedImageData& codedImage = *encodeResult;
255
256
  // === generate properties
257
258
  // --- choose which color profile to put into 'colr' box
259
260
0
  auto colr_boxes = add_color_profile(image, options, input_class, options.output_nclx_profile);
261
0
  codedImage.properties.insert(codedImage.properties.end(),
262
0
                               colr_boxes.begin(),
263
0
                               colr_boxes.end());
264
265
266
  // --- ispe
267
  // Note: 'ispe' must come before the transformation properties
268
269
0
  uint32_t input_width, input_height;
270
0
  input_width = image->get_width();
271
0
  input_height = image->get_height();
272
273
  // --- get the real size of the encoded image
274
275
  // highest priority: codedImageData
276
0
  uint32_t encoded_width = codedImage.encoded_image_width;
277
0
  uint32_t encoded_height = codedImage.encoded_image_height;
278
279
  // second priority: query plugin API
280
0
  if (encoded_width == 0 &&
281
0
      encoder->plugin->plugin_api_version >= 3 &&
282
0
      encoder->plugin->query_encoded_size != nullptr) {
283
284
0
    encoder->plugin->query_encoded_size(encoder->encoder,
285
0
                                        input_width, input_height,
286
0
                                        &encoded_width,
287
0
                                        &encoded_height);
288
0
  }
289
0
  else if (encoded_width == 0) {
290
    // fallback priority: use input size
291
0
    encoded_width = input_width;
292
0
    encoded_height = input_height;
293
0
  }
294
295
0
  auto ispe = std::make_shared<Box_ispe>();
296
0
  ispe->set_size(encoded_width, encoded_height);
297
0
  ispe->set_is_essential(is_ispe_essential());
298
0
  codedImage.properties.push_back(ispe);
299
300
301
  // --- clap (if needed)
302
303
0
  if (input_width != encoded_width ||
304
0
      input_height != encoded_height) {
305
306
0
    auto clap = std::make_shared<Box_clap>();
307
0
    clap->set(input_width, input_height, encoded_width, encoded_height);
308
0
    codedImage.properties.push_back(clap);
309
0
  }
310
311
312
313
  // --- add common metadata properties (pixi, ...)
314
315
0
  auto colorspace = image->get_colorspace();
316
0
  auto chroma = image->get_chroma_format();
317
318
319
  // --- write PIXI property
320
321
0
  std::shared_ptr<Box_pixi> pixi = std::make_shared<Box_pixi>();
322
0
  if (colorspace == heif_colorspace_monochrome) {
323
0
    pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y));
324
0
  }
325
0
  else if (colorspace == heif_colorspace_YCbCr) {
326
0
    pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y));
327
0
    pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cb));
328
0
    pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cr));
329
0
  }
330
0
  else if (colorspace == heif_colorspace_RGB) {
331
0
    if (chroma == heif_chroma_444) {
332
0
      pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_R));
333
0
      pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_G));
334
0
      pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_B));
335
0
    }
336
0
    else if (chroma == heif_chroma_interleaved_RGB ||
337
0
             chroma == heif_chroma_interleaved_RGBA ||
338
0
             chroma == heif_chroma_interleaved_RRGGBB_LE ||
339
0
             chroma == heif_chroma_interleaved_RRGGBB_BE ||
340
0
             chroma == heif_chroma_interleaved_RRGGBBAA_LE ||
341
0
             chroma == heif_chroma_interleaved_RRGGBBAA_BE) {
342
0
      uint8_t bpp = image->get_bits_per_pixel(heif_channel_interleaved);
343
0
      pixi->add_channel_bits(bpp);
344
0
      pixi->add_channel_bits(bpp);
345
0
      pixi->add_channel_bits(bpp);
346
0
    }
347
0
  }
348
0
  codedImage.properties.push_back(pixi);
349
350
  // --- generate properties for image extra data
351
352
  // copy over ImageExtraData into image item
353
0
  *static_cast<ImageExtraData*>(this) = static_cast<ImageExtraData>(*image);
354
355
0
  auto extra_data_properties = image->generate_property_boxes();
356
0
  codedImage.properties.insert(codedImage.properties.end(),
357
0
                               extra_data_properties.begin(),
358
0
                               extra_data_properties.end());
359
360
0
  return encodeResult;
361
0
}
362
363
364
Error ImageItem::encode_to_item(HeifContext* ctx,
365
                                const std::shared_ptr<HeifPixelImage>& image,
366
                                heif_encoder* encoder,
367
                                const heif_encoding_options& options,
368
                                heif_image_input_class input_class)
369
0
{
370
0
  uint32_t input_width = image->get_width();
371
0
  uint32_t input_height = image->get_height();
372
373
0
  set_size(input_width, input_height);
374
375
376
  // compress image and assign data to item
377
378
0
  Result<Encoder::CodedImageData> codingResult = encode_to_bitstream_and_boxes(image, encoder, options, input_class);
379
0
  if (!codingResult) {
380
0
    return codingResult.error();
381
0
  }
382
383
0
  Encoder::CodedImageData& codedImage = *codingResult;
384
385
0
  auto infe_box = ctx->get_heif_file()->add_new_infe_box(get_infe_type());
386
0
  heif_item_id image_id = infe_box->get_item_ID();
387
0
  set_id(image_id);
388
389
0
  ctx->get_heif_file()->append_iloc_data(image_id, codedImage.bitstream, 0);
390
391
392
  // set item properties
393
394
0
  for (auto& propertyBox : codingResult->properties) {
395
0
    bool essential = is_property_essential(propertyBox);
396
397
    // TODO: can we simply use add_property() ?
398
0
    int index = ctx->get_heif_file()->get_ipco_box()->find_or_append_child_box(propertyBox);
399
0
    ctx->get_heif_file()->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{essential,
400
0
                                                                                                           uint16_t(index + 1)});
401
0
  }
402
403
404
  // MIAF 7.3.6.7
405
  // This is according to MIAF without Amd2. With Amd2, the restriction has been lifted and the image is MIAF compatible.
406
  // However, since AVIF is based on MIAF, the whole image would be invalid in that case.
407
408
  // We might remove this code at a later point in time when MIAF Amd2 is in wide use.
409
410
0
  if (encoder->plugin->compression_format != heif_compression_AV1 &&
411
0
      image->get_colorspace() == heif_colorspace_YCbCr) {
412
0
    if (!is_integer_multiple_of_chroma_size(image->get_width(),
413
0
                                            image->get_height(),
414
0
                                            image->get_chroma_format())) {
415
0
      mark_not_miaf_compatible();
416
0
    }
417
0
  }
418
419
  // TODO: move this into encode_to_bistream_and_boxes()
420
0
  ctx->get_heif_file()->add_orientation_properties(image_id, options.image_orientation);
421
422
0
  return Error::Ok;
423
0
}
424
425
bool ImageItem::has_ispe_resolution() const
426
0
{
427
0
  return get_property<Box_ispe>() != nullptr;
428
0
}
429
430
uint32_t ImageItem::get_ispe_width() const
431
0
{
432
0
  auto ispe = get_property<Box_ispe>();
433
0
  if (!ispe) {
434
0
    return 0;
435
0
  }
436
0
  else {
437
0
    return ispe->get_width();
438
0
  }
439
0
}
440
441
442
uint32_t ImageItem::get_ispe_height() const
443
0
{
444
0
  auto ispe = get_property<Box_ispe>();
445
0
  if (!ispe) {
446
0
    return 0;
447
0
  }
448
0
  else {
449
0
    return ispe->get_height();
450
0
  }
451
0
}
452
453
454
void ImageItem::get_tile_size(uint32_t& w, uint32_t& h) const
455
0
{
456
0
  w = get_width();
457
0
  h = get_height();
458
0
}
459
460
461
Error ImageItem::postprocess_coded_image_colorspace(heif_colorspace* inout_colorspace, heif_chroma* inout_chroma) const
462
4.20k
{
463
#if 0
464
  auto pixi = m_heif_context->get_heif_file()->get_property<Box_pixi>(id);
465
  if (pixi && pixi->get_num_channels() == 1) {
466
    *out_colorspace = heif_colorspace_monochrome;
467
    *out_chroma = heif_chroma_monochrome;
468
  }
469
#endif
470
471
4.20k
  if (*inout_colorspace == heif_colorspace_YCbCr) {
472
3.65k
    auto nclx = get_color_profile_nclx();
473
3.65k
    if (nclx.get_matrix_coefficients() == 0) {
474
8
      *inout_colorspace = heif_colorspace_RGB;
475
8
      *inout_chroma = heif_chroma_444; // TODO: this or keep the original chroma?
476
8
    }
477
3.65k
  }
478
479
4.20k
  return Error::Ok;
480
4.20k
}
481
482
483
Error ImageItem::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
484
4.43k
{
485
4.43k
  auto decoderResult = get_decoder();
486
4.43k
  if (!decoderResult) {
487
105
    return decoderResult.error();
488
105
  }
489
490
4.32k
  auto decoder = *decoderResult;
491
492
4.32k
  Error err = decoder->get_coded_image_colorspace(out_colorspace, out_chroma);
493
4.32k
  if (err) {
494
128
    return err;
495
128
  }
496
497
4.20k
  postprocess_coded_image_colorspace(out_colorspace, out_chroma);
498
499
4.20k
  return Error::Ok;
500
4.32k
}
501
502
503
int ImageItem::get_luma_bits_per_pixel() const
504
4.34k
{
505
4.34k
  auto decoderResult = get_decoder();
506
4.34k
  if (!decoderResult) {
507
0
    return decoderResult.error();
508
0
  }
509
510
4.34k
  auto decoder = *decoderResult;
511
512
4.34k
  return decoder->get_luma_bits_per_pixel();
513
4.34k
}
514
515
516
int ImageItem::get_chroma_bits_per_pixel() const
517
0
{
518
0
  auto decoderResult = get_decoder();
519
0
  if (!decoderResult) {
520
0
    return decoderResult.error();
521
0
  }
522
523
0
  auto decoder = *decoderResult;
524
525
0
  return decoder->get_chroma_bits_per_pixel();
526
0
}
527
528
529
Result<Encoder::CodedImageData> ImageItem::encode(const std::shared_ptr<HeifPixelImage>& image,
530
                                                  heif_encoder* h_encoder,
531
                                                  const heif_encoding_options& options,
532
                                                  heif_image_input_class input_class)
533
0
{
534
0
  auto encoder = get_encoder();
535
0
  return encoder->encode(image, h_encoder, options, input_class);
536
0
}
537
538
539
std::vector<std::shared_ptr<Box_colr> >
540
ImageItem::add_color_profile(const std::shared_ptr<HeifPixelImage>& image,
541
                             const heif_encoding_options& options,
542
                             heif_image_input_class input_class,
543
                             const heif_color_profile_nclx* target_heif_nclx)
544
0
{
545
0
  std::vector<std::shared_ptr<Box_colr> > colr_boxes;
546
547
0
  if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) {
548
0
    auto icc_profile = image->get_color_profile_icc();
549
0
    if (icc_profile) {
550
0
      auto colr = std::make_shared<Box_colr>();
551
0
      colr->set_color_profile(icc_profile);
552
0
      colr_boxes.push_back(colr);
553
0
    }
554
555
556
    // save nclx profile
557
558
0
    bool save_nclx_profile = (options.output_nclx_profile != nullptr);
559
560
    // if there is an ICC profile, only save NCLX when we chose to save both profiles
561
0
    if (icc_profile && !(options.version >= 3 &&
562
0
                         options.save_two_colr_boxes_when_ICC_and_nclx_available)) {
563
0
      save_nclx_profile = false;
564
0
    }
565
566
    // we might have turned off nclx completely because macOS/iOS cannot read it
567
0
    if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) {
568
0
      save_nclx_profile = false;
569
0
    }
570
571
0
    if (save_nclx_profile) {
572
0
      auto target_nclx_profile = std::make_shared<color_profile_nclx>();
573
0
      target_nclx_profile->set_from_heif_color_profile_nclx(target_heif_nclx);
574
575
0
      auto colr = std::make_shared<Box_colr>();
576
0
      colr->set_color_profile(target_nclx_profile);
577
0
      colr_boxes.push_back(colr);
578
0
    }
579
0
  }
580
581
0
  return colr_boxes;
582
0
}
583
584
585
Error ImageItem::transform_requested_tile_position_to_original_tile_position(uint32_t& tile_x, uint32_t& tile_y) const
586
0
{
587
0
  Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
588
0
  if (!propertiesResult) {
589
0
    return propertiesResult.error();
590
0
  }
591
592
0
  heif_image_tiling tiling = get_heif_image_tiling();
593
594
  //for (auto& prop : std::ranges::reverse_view(propertiesResult.value)) {
595
0
  for (auto propIter = propertiesResult->rbegin(); propIter != propertiesResult->rend(); propIter++) {
596
0
    if (auto irot = std::dynamic_pointer_cast<Box_irot>(*propIter)) {
597
0
      switch (irot->get_rotation_ccw()) {
598
0
        case 90: {
599
0
          uint32_t tx0 = tiling.num_columns - 1 - tile_y;
600
0
          uint32_t ty0 = tile_x;
601
0
          tile_y = ty0;
602
0
          tile_x = tx0;
603
0
          break;
604
0
        }
605
0
        case 270: {
606
0
          uint32_t tx0 = tile_y;
607
0
          uint32_t ty0 = tiling.num_rows - 1 - tile_x;
608
0
          tile_y = ty0;
609
0
          tile_x = tx0;
610
0
          break;
611
0
        }
612
0
        case 180: {
613
0
          tile_x = tiling.num_columns - 1 - tile_x;
614
0
          tile_y = tiling.num_rows - 1 - tile_y;
615
0
          break;
616
0
        }
617
0
        case 0:
618
0
          break;
619
0
        default:
620
0
          assert(false);
621
0
          break;
622
0
      }
623
0
    }
624
625
0
    if (auto imir = std::dynamic_pointer_cast<Box_imir>(*propIter)) {
626
0
      switch (imir->get_mirror_direction()) {
627
0
        case heif_transform_mirror_direction_horizontal:
628
0
          tile_x = tiling.num_columns - 1 - tile_x;
629
0
          break;
630
0
        case heif_transform_mirror_direction_vertical:
631
0
          tile_y = tiling.num_rows - 1 - tile_y;
632
0
          break;
633
0
        default:
634
0
          assert(false);
635
0
          break;
636
0
      }
637
0
    }
638
0
  }
639
640
0
  return Error::Ok;
641
0
}
642
643
644
void ImageItem::set_clli(const heif_content_light_level& clli)
645
0
{
646
0
  ImageExtraData::set_clli(clli);
647
0
  add_property(get_clli_box(), false);
648
0
}
649
650
651
void ImageItem::set_mdcv(const heif_mastering_display_colour_volume& mdcv)
652
0
{
653
0
  ImageExtraData::set_mdcv(mdcv);
654
0
  add_property(get_mdcv_box(), false);
655
0
}
656
657
658
void ImageItem::set_pixel_ratio(uint32_t h, uint32_t v)
659
0
{
660
0
  ImageExtraData::set_pixel_ratio(h, v);
661
0
  add_property(get_pasp_box(), false);
662
0
}
663
664
665
void ImageItem::set_color_profile_nclx(const nclx_profile& profile)
666
477
{
667
477
  ImageExtraData::set_color_profile_nclx(profile);
668
477
  add_property(get_colr_box_nclx(), false);
669
477
}
670
671
672
void ImageItem::set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile)
673
3.62k
{
674
3.62k
  ImageExtraData::set_color_profile_icc(profile);
675
3.62k
  add_property(get_colr_box_icc(), false);
676
3.62k
}
677
678
679
Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_image(const heif_decoding_options& options,
680
                                                                bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const
681
5.29k
{
682
  // --- check whether image size (according to 'ispe') exceeds maximum
683
684
5.29k
  if (!decode_tile_only) {
685
5.29k
    auto ispe = get_property<Box_ispe>();
686
5.29k
    if (ispe) {
687
5.23k
      Error err = check_for_valid_image_size(get_context()->get_security_limits(), ispe->get_width(), ispe->get_height());
688
5.23k
      if (err) {
689
5
        return err;
690
5
      }
691
5.23k
    }
692
5.29k
  }
693
694
695
  // --- transform tile position
696
697
5.29k
  if (decode_tile_only && options.ignore_transformations == false) {
698
0
    if (Error error = transform_requested_tile_position_to_original_tile_position(tile_x0, tile_y0)) {
699
0
      return error;
700
0
    }
701
0
  }
702
703
  // --- decode image
704
705
5.29k
  Result<std::shared_ptr<HeifPixelImage>> decodingResult = decode_compressed_image(options, decode_tile_only, tile_x0, tile_y0);
706
5.29k
  if (!decodingResult) {
707
4.07k
    return decodingResult.error();
708
4.07k
  }
709
710
1.21k
  auto img = *decodingResult;
711
712
1.21k
  std::shared_ptr<HeifFile> file = m_heif_context->get_heif_file();
713
714
715
  // --- apply image transformations
716
717
1.21k
  Error error;
718
719
1.21k
  if (options.ignore_transformations == false) {
720
1.21k
    Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
721
1.21k
    if (!propertiesResult) {
722
0
      return propertiesResult.error();
723
0
    }
724
725
1.21k
    const std::vector<std::shared_ptr<Box>>& properties = *propertiesResult;
726
727
3.10k
    for (const auto& property : properties) {
728
3.10k
      if (auto rot = std::dynamic_pointer_cast<Box_irot>(property)) {
729
0
        auto rotateResult = img->rotate_ccw(rot->get_rotation_ccw(), m_heif_context->get_security_limits());
730
0
        if (!rotateResult) {
731
0
          return error;
732
0
        }
733
734
0
        img = *rotateResult;
735
0
      }
736
737
738
3.10k
      if (auto mirror = std::dynamic_pointer_cast<Box_imir>(property)) {
739
0
        auto mirrorResult = img->mirror_inplace(mirror->get_mirror_direction(),
740
0
                                                get_context()->get_security_limits());
741
0
        if (!mirrorResult) {
742
0
          return error;
743
0
        }
744
0
        img = *mirrorResult;
745
0
      }
746
747
748
3.10k
      if (!decode_tile_only) {
749
        // For tiles decoding, we do not process the 'clap' because this is handled by a shift of the tiling grid.
750
751
3.10k
        if (auto clap = std::dynamic_pointer_cast<Box_clap>(property)) {
752
442
          std::shared_ptr<HeifPixelImage> clap_img;
753
754
442
          uint32_t img_width = img->get_width();
755
442
          uint32_t img_height = img->get_height();
756
757
442
          int left = clap->left_rounded(img_width);
758
442
          int right = clap->right_rounded(img_width);
759
442
          int top = clap->top_rounded(img_height);
760
442
          int bottom = clap->bottom_rounded(img_height);
761
762
442
          if (left < 0) { left = 0; }
763
442
          if (top < 0) { top = 0; }
764
765
442
          if ((uint32_t) right >= img_width) { right = img_width - 1; }
766
442
          if ((uint32_t) bottom >= img_height) { bottom = img_height - 1; }
767
768
442
          if (left > right ||
769
406
              top > bottom) {
770
60
            return Error(heif_error_Invalid_input,
771
60
                         heif_suberror_Invalid_clean_aperture);
772
60
          }
773
774
382
          auto cropResult = img->crop(left, right, top, bottom, m_heif_context->get_security_limits());
775
382
          if (!cropResult) {
776
0
            return cropResult.error();
777
0
          }
778
779
382
          img = *cropResult;
780
382
        }
781
3.10k
      }
782
3.10k
    }
783
1.21k
  }
784
785
786
  // --- add alpha channel, if available
787
788
  // TODO: this if statement is probably wrong. When we have a tiled image with alpha
789
  // channel, then the alpha images should be associated with their respective tiles.
790
  // However, the tile images are not part of the m_all_images list.
791
  // Fix this, when we have a test image available.
792
793
1.15k
  std::shared_ptr<ImageItem> alpha_image = get_alpha_channel();
794
1.15k
  if (alpha_image) {
795
0
    if (alpha_image->get_item_error()) {
796
0
      return alpha_image->get_item_error();
797
0
    }
798
799
0
    auto alphaDecodingResult = alpha_image->decode_image(options, decode_tile_only, tile_x0, tile_y0);
800
0
    if (!alphaDecodingResult) {
801
0
      return alphaDecodingResult.error();
802
0
    }
803
804
0
    std::shared_ptr<HeifPixelImage> alpha = *alphaDecodingResult;
805
806
    // TODO: check that sizes are the same and that we have an Y channel
807
    // BUT: is there any indication in the standard that the alpha channel should have the same size?
808
809
    // TODO: convert in case alpha is decoded as RGB interleaved
810
811
0
    heif_channel channel;
812
0
    switch (alpha->get_colorspace()) {
813
0
      case heif_colorspace_YCbCr:
814
0
      case heif_colorspace_monochrome:
815
0
        channel = heif_channel_Y;
816
0
        break;
817
0
      case heif_colorspace_RGB:
818
0
        channel = heif_channel_R;
819
0
        break;
820
0
      case heif_colorspace_undefined:
821
0
      default:
822
0
        return Error(heif_error_Invalid_input,
823
0
                     heif_suberror_Unsupported_color_conversion);
824
0
    }
825
826
827
    // 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, ...).
828
    //       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.
829
    //       But we can only do this when libheif itself doesn't assume anymore that the alpha channel has the same resolution.
830
831
0
    if ((alpha_image->get_width() != img->get_width()) || (alpha_image->get_height() != img->get_height())) {
832
0
      std::shared_ptr<HeifPixelImage> scaled_alpha;
833
0
      Error err = alpha->scale_nearest_neighbor(scaled_alpha, img->get_width(), img->get_height(), m_heif_context->get_security_limits());
834
0
      if (err) {
835
0
        return err;
836
0
      }
837
0
      alpha = std::move(scaled_alpha);
838
0
    }
839
0
    img->transfer_plane_from_image_as(alpha, channel, heif_channel_Alpha);
840
841
0
    if (is_premultiplied_alpha()) {
842
0
      img->set_premultiplied_alpha(true);
843
0
    }
844
0
  }
845
846
847
  // --- set color profile
848
849
  // If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion.
850
  // Otherwise, use the profile that is stored in the image stream itself and then set the
851
  // (non-NCLX) profile later.
852
1.15k
  auto nclx = get_color_profile_nclx();
853
1.15k
  if (!nclx.is_undefined()) {
854
0
    img->set_color_profile_nclx(nclx);
855
0
  }
856
857
1.15k
  auto icc = get_color_profile_icc();
858
1.15k
  if (icc) {
859
0
    img->set_color_profile_icc(icc);
860
0
  }
861
862
863
  // --- attach metadata to image
864
865
1.15k
  {
866
1.15k
    auto ipco_box = file->get_ipco_box();
867
1.15k
    auto ipma_box = file->get_ipma_box();
868
869
    // CLLI
870
871
1.15k
    auto clli = get_property<Box_clli>();
872
1.15k
    if (clli) {
873
3
      img->set_clli(clli->clli);
874
3
    }
875
876
    // MDCV
877
878
1.15k
    auto mdcv = get_property<Box_mdcv>();
879
1.15k
    if (mdcv) {
880
0
      img->set_mdcv(mdcv->mdcv);
881
0
    }
882
883
    // PASP
884
885
1.15k
    auto pasp = get_property<Box_pasp>();
886
1.15k
    if (pasp) {
887
0
      img->set_pixel_ratio(pasp->hSpacing, pasp->vSpacing);
888
0
    }
889
890
    // TAI
891
892
1.15k
    auto itai = get_property<Box_itai>();
893
1.15k
    if (itai) {
894
0
      img->set_tai_timestamp(itai->get_tai_timestamp_packet());
895
0
    }
896
1.15k
  }
897
898
1.15k
  return img;
899
1.15k
}
900
901
#if 0
902
Result<std::vector<uint8_t>> ImageItem::read_bitstream_configuration_data_override(heif_item_id itemId, heif_compression_format format) const
903
{
904
  auto item_codec = ImageItem::alloc_for_compression_format(const_cast<HeifContext*>(get_context()), format);
905
  assert(item_codec);
906
907
  Error err = item_codec->init_decoder_from_item(itemId);
908
  if (err) {
909
    return err;
910
  }
911
912
  return item_codec->read_bitstream_configuration_data(itemId);
913
}
914
#endif
915
916
Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_compressed_image(const heif_decoding_options& options,
917
                                                                           bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const
918
4.62k
{
919
4.62k
  DataExtent extent;
920
4.62k
  extent.set_from_image_item(get_file(), get_id());
921
922
4.62k
  auto decoderResult = get_decoder();
923
4.62k
  if (!decoderResult) {
924
0
    return decoderResult.error();
925
0
  }
926
927
4.62k
  auto decoder = *decoderResult;
928
929
4.62k
  decoder->set_data_extent(std::move(extent));
930
931
4.62k
  return decoder->decode_single_frame_from_compressed_data(options,
932
4.62k
                                                           get_context()->get_security_limits());
933
4.62k
}
934
935
936
heif_image_tiling ImageItem::get_heif_image_tiling() const
937
0
{
938
  // --- Return a dummy tiling consisting of only a single tile for the whole image
939
940
0
  heif_image_tiling tiling{};
941
942
0
  tiling.version = 1;
943
0
  tiling.num_columns = 1;
944
0
  tiling.num_rows = 1;
945
946
0
  tiling.tile_width = m_width;
947
0
  tiling.tile_height = m_height;
948
0
  tiling.image_width = m_width;
949
0
  tiling.image_height = m_height;
950
951
0
  tiling.top_offset = 0;
952
0
  tiling.left_offset = 0;
953
0
  tiling.number_of_extra_dimensions = 0;
954
955
0
  for (uint32_t& s : tiling.extra_dimension_size) {
956
0
    s = 0;
957
0
  }
958
959
0
  return tiling;
960
0
}
961
962
963
Result<std::vector<std::shared_ptr<Box>>> ImageItem::get_properties() const
964
1.21k
{
965
1.21k
  std::vector<std::shared_ptr<Box>> properties;
966
1.21k
  auto ipco_box = get_file()->get_ipco_box();
967
1.21k
  auto ipma_box = get_file()->get_ipma_box();
968
1.21k
  Error error = ipco_box->get_properties_for_item_ID(m_id, ipma_box, properties);
969
1.21k
  if (error) {
970
0
    return error;
971
0
  }
972
973
1.21k
  return properties;
974
1.21k
}
975
976
977
bool ImageItem::has_essential_property_other_than(const std::set<uint32_t>& props) const
978
0
{
979
0
  Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
980
0
  if (!propertiesResult) {
981
0
    return false;
982
0
  }
983
984
0
  for (const auto& property : *propertiesResult) {
985
0
    if (is_property_essential(property) &&
986
0
        props.find(property->get_short_type()) == props.end()) {
987
0
      return true;
988
0
    }
989
0
  }
990
991
0
  return false;
992
0
}
993
994
995
Error ImageItem::process_image_transformations_on_tiling(heif_image_tiling& tiling) const
996
0
{
997
0
  Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties();
998
0
  if (!propertiesResult) {
999
0
    return propertiesResult.error();
1000
0
  }
1001
1002
0
  const std::vector<std::shared_ptr<Box>>& properties = *propertiesResult;
1003
1004
0
  uint32_t left_excess = 0;
1005
0
  uint32_t top_excess = 0;
1006
0
  uint32_t right_excess;
1007
0
  uint32_t bottom_excess;
1008
1009
  // Prevent divide by zero.
1010
1011
0
  if (tiling.tile_width != 0 && tiling.tile_height != 0) {
1012
0
    right_excess = tiling.image_width % tiling.tile_width;
1013
0
    bottom_excess = tiling.image_height % tiling.tile_height;
1014
0
  }
1015
0
  else {
1016
0
    right_excess = 0;
1017
0
    bottom_excess = 0;
1018
0
  }
1019
1020
1021
0
  for (const auto& property : properties) {
1022
1023
    // --- rotation
1024
1025
0
    if (auto rot = std::dynamic_pointer_cast<Box_irot>(property)) {
1026
0
      int angle = rot->get_rotation_ccw();
1027
0
      if (angle == 90 || angle == 270) {
1028
0
        std::swap(tiling.tile_width, tiling.tile_height);
1029
0
        std::swap(tiling.image_width, tiling.image_height);
1030
0
        std::swap(tiling.num_rows, tiling.num_columns);
1031
0
      }
1032
1033
0
      switch (angle) {
1034
0
        case 0:
1035
0
          break;
1036
0
        case 180:
1037
0
          std::swap(left_excess, right_excess);
1038
0
          std::swap(top_excess, bottom_excess);
1039
0
          break;
1040
0
        case 90: {
1041
0
          uint32_t old_top_excess = top_excess;
1042
0
          top_excess = right_excess;
1043
0
          right_excess = bottom_excess;
1044
0
          bottom_excess = left_excess;
1045
0
          left_excess = old_top_excess;
1046
0
          break;
1047
0
        }
1048
0
        case 270: {
1049
0
          uint32_t old_top_excess = top_excess;
1050
0
          top_excess = left_excess;
1051
0
          left_excess = bottom_excess;
1052
0
          bottom_excess = right_excess;
1053
0
          right_excess = old_top_excess;
1054
0
          break;
1055
0
        }
1056
0
        default:
1057
0
          assert(false);
1058
0
          break;
1059
0
      }
1060
0
    }
1061
1062
    // --- mirror
1063
1064
0
    if (auto mirror = std::dynamic_pointer_cast<Box_imir>(property)) {
1065
0
      switch (mirror->get_mirror_direction()) {
1066
0
        case heif_transform_mirror_direction_horizontal:
1067
0
          std::swap(left_excess, right_excess);
1068
0
          break;
1069
0
        case heif_transform_mirror_direction_vertical:
1070
0
          std::swap(top_excess, bottom_excess);
1071
0
          break;
1072
0
        default:
1073
0
          assert(false);
1074
0
          break;
1075
0
      }
1076
0
    }
1077
1078
    // --- crop
1079
1080
0
    if (auto clap = std::dynamic_pointer_cast<Box_clap>(property)) {
1081
0
      std::shared_ptr<HeifPixelImage> clap_img;
1082
1083
0
      int left = clap->left_rounded(tiling.image_width);
1084
0
      int right = clap->right_rounded(tiling.image_width);
1085
0
      int top = clap->top_rounded(tiling.image_height);
1086
0
      int bottom = clap->bottom_rounded(tiling.image_height);
1087
1088
0
      if (left < 0) { left = 0; }
1089
0
      if (top < 0) { top = 0; }
1090
1091
0
      if ((uint32_t)right >= tiling.image_width) { right = tiling.image_width - 1; }
1092
0
      if ((uint32_t)bottom >= tiling.image_height) { bottom = tiling.image_height - 1; }
1093
1094
0
      if (left > right ||
1095
0
          top > bottom) {
1096
0
        return {heif_error_Invalid_input,
1097
0
                heif_suberror_Invalid_clean_aperture};
1098
0
      }
1099
1100
0
      left_excess += left;
1101
0
      right_excess += right;
1102
0
      top_excess += top;
1103
0
      bottom_excess += bottom;
1104
0
    }
1105
0
  }
1106
1107
0
  tiling.left_offset = left_excess;
1108
0
  tiling.top_offset = top_excess;
1109
1110
0
  return Error::Ok;
1111
0
}