Coverage Report

Created: 2026-10-17 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libultrahdr/third_party/libheif/libheif/context.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2017 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 "box.h"
22
#include "error.h"
23
#include "libheif/heif.h"
24
#include "region.h"
25
#include <cstdint>
26
#include <cassert>
27
#include <cstring>
28
#include <algorithm>
29
#include <iostream>
30
#include <limits>
31
#include <cmath>
32
#include <deque>
33
#include "image-items/image_item.h"
34
#include <codecs/hevc_boxes.h>
35
36
#if ENABLE_PARALLEL_TILE_DECODING
37
#include <future>
38
#endif
39
40
#include "context.h"
41
#include "file.h"
42
#include "pixelimage.h"
43
#include "libheif/api_structs.h"
44
#include "security_limits.h"
45
#include "compression.h"
46
#include "color-conversion/colorconversion.h"
47
#include "plugin_registry.h"
48
#include "image-items/hevc.h"
49
#include "image-items/vvc.h"
50
#include "image-items/avif.h"
51
#include "image-items/jpeg.h"
52
#include "image-items/mask_image.h"
53
#include "image-items/jpeg2000.h"
54
#include "image-items/grid.h"
55
#include "image-items/overlay.h"
56
#include "image-items/tiled.h"
57
58
#if WITH_UNCOMPRESSED_CODEC
59
#include "image-items/unc_image.h"
60
#endif
61
62
63
heif_encoder::heif_encoder(const struct heif_encoder_plugin* _plugin)
64
0
    : plugin(_plugin)
65
0
{
66
67
0
}
68
69
heif_encoder::~heif_encoder()
70
0
{
71
0
  release();
72
0
}
73
74
void heif_encoder::release()
75
0
{
76
0
  if (encoder) {
77
0
    plugin->free_encoder(encoder);
78
0
    encoder = nullptr;
79
0
  }
80
0
}
81
82
83
struct heif_error heif_encoder::alloc()
84
0
{
85
0
  if (encoder == nullptr) {
86
0
    struct heif_error error = plugin->new_encoder(&encoder);
87
    // TODO: error handling
88
0
    return error;
89
0
  }
90
91
0
  struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, Error::kSuccess};
92
0
  return err;
93
0
}
94
95
96
HeifContext::HeifContext()
97
15.8k
{
98
15.8k
  const char* security_limits_variable = getenv("LIBHEIF_SECURITY_LIMITS");
99
100
15.8k
  if (security_limits_variable && (strcmp(security_limits_variable, "off") == 0 ||
101
0
                                   strcmp(security_limits_variable, "OFF") == 0)) {
102
0
    m_limits = disabled_security_limits;
103
0
  }
104
15.8k
  else {
105
15.8k
    m_limits = global_security_limits;
106
15.8k
  }
107
108
15.8k
  reset_to_empty_heif();
109
15.8k
}
110
111
112
HeifContext::~HeifContext()
113
15.8k
{
114
  // Break circular references between Images (when a faulty input image has circular image references)
115
15.8k
  for (auto& it : m_all_images) {
116
0
    std::shared_ptr<ImageItem> image = it.second;
117
0
    image->clear();
118
0
  }
119
15.8k
}
120
121
122
static void copy_security_limits(heif_security_limits* dst, const heif_security_limits* src)
123
0
{
124
0
  dst->version = 1;
125
0
  dst->max_image_size_pixels = src->max_image_size_pixels;
126
0
  dst->max_number_of_tiles = src->max_number_of_tiles;
127
0
  dst->max_bayer_pattern_pixels = src->max_bayer_pattern_pixels;
128
0
  dst->max_items = src->max_items;
129
130
0
  dst->max_color_profile_size = src->max_color_profile_size;
131
0
  dst->max_memory_block_size = src->max_memory_block_size;
132
133
0
  dst->max_components = src->max_components;
134
135
0
  dst->max_iloc_extents_per_item = src->max_iloc_extents_per_item;
136
0
  dst->max_size_entity_group = src->max_size_entity_group;
137
138
0
  dst->max_children_per_box = src->max_children_per_box;
139
0
}
140
141
142
void HeifContext::set_security_limits(const heif_security_limits* limits)
143
0
{
144
0
  copy_security_limits(&m_limits, limits);
145
0
}
146
147
148
Error HeifContext::read(const std::shared_ptr<StreamReader>& reader)
149
0
{
150
0
  m_heif_file = std::make_shared<HeifFile>();
151
0
  m_heif_file->set_security_limits(&m_limits);
152
0
  Error err = m_heif_file->read(reader);
153
0
  if (err) {
154
0
    return err;
155
0
  }
156
157
0
  return interpret_heif_file();
158
0
}
159
160
Error HeifContext::read_from_file(const char* input_filename)
161
0
{
162
0
  m_heif_file = std::make_shared<HeifFile>();
163
0
  m_heif_file->set_security_limits(&m_limits);
164
0
  Error err = m_heif_file->read_from_file(input_filename);
165
0
  if (err) {
166
0
    return err;
167
0
  }
168
169
0
  return interpret_heif_file();
170
0
}
171
172
Error HeifContext::read_from_memory(const void* data, size_t size, bool copy)
173
15.8k
{
174
15.8k
  m_heif_file = std::make_shared<HeifFile>();
175
15.8k
  m_heif_file->set_security_limits(&m_limits);
176
15.8k
  Error err = m_heif_file->read_from_memory(data, size, copy);
177
15.8k
  if (err) {
178
15.8k
    return err;
179
15.8k
  }
180
181
0
  return interpret_heif_file();
182
15.8k
}
183
184
void HeifContext::reset_to_empty_heif()
185
15.8k
{
186
15.8k
  m_heif_file = std::make_shared<HeifFile>();
187
15.8k
  m_heif_file->set_security_limits(&m_limits);
188
15.8k
  m_heif_file->new_empty_file();
189
190
15.8k
  m_all_images.clear();
191
15.8k
  m_top_level_images.clear();
192
15.8k
  m_primary_image.reset();
193
15.8k
}
194
195
196
std::vector<std::shared_ptr<ImageItem>> HeifContext::get_top_level_images(bool return_error_images)
197
0
{
198
0
  if (return_error_images) {
199
0
    return m_top_level_images;
200
0
  }
201
0
  else {
202
0
    std::vector<std::shared_ptr<ImageItem>> filtered;
203
0
    for (auto& item : m_top_level_images) {
204
0
      if (!item->get_item_error()) {
205
0
        filtered.push_back(item);
206
0
      }
207
0
    }
208
209
0
    return filtered;
210
0
  }
211
0
}
212
213
214
std::shared_ptr<ImageItem> HeifContext::get_image(heif_item_id id, bool return_error_images)
215
0
{
216
0
  auto iter = m_all_images.find(id);
217
0
  if (iter == m_all_images.end()) {
218
0
    return nullptr;
219
0
  }
220
0
  else {
221
0
    if (iter->second->get_item_error() && !return_error_images) {
222
0
      return nullptr;
223
0
    }
224
0
    else {
225
0
      return iter->second;
226
0
    }
227
0
  }
228
0
}
229
230
231
std::shared_ptr<ImageItem> HeifContext::get_primary_image(bool return_error_image)
232
0
{
233
0
  if (!return_error_image && m_primary_image->get_item_error())
234
0
    return nullptr;
235
0
  else
236
0
    return m_primary_image;
237
0
}
238
239
240
bool HeifContext::is_image(heif_item_id ID) const
241
0
{
242
0
  return m_all_images.find(ID) != m_all_images.end();
243
0
}
244
245
246
std::shared_ptr<RegionItem> HeifContext::add_region_item(uint32_t reference_width, uint32_t reference_height)
247
0
{
248
0
  std::shared_ptr<Box_infe> box = m_heif_file->add_new_infe_box(fourcc("rgan"));
249
0
  box->set_hidden_item(true);
250
251
0
  auto regionItem = std::make_shared<RegionItem>(box->get_item_ID(), reference_width, reference_height);
252
0
  add_region_item(regionItem);
253
254
0
  return regionItem;
255
0
}
256
257
void HeifContext::add_region_referenced_mask_ref(heif_item_id region_item_id, heif_item_id mask_item_id)
258
0
{
259
0
  m_heif_file->add_iref_reference(region_item_id, fourcc("mask"), {mask_item_id});
260
0
}
261
262
void HeifContext::write(StreamWriter& writer)
263
0
{
264
  // --- serialize regions
265
266
0
  for (auto& image : m_all_images) {
267
0
    for (auto region : image.second->get_region_item_ids()) {
268
0
      m_heif_file->add_iref_reference(region,
269
0
                                      fourcc("cdsc"), {image.first});
270
0
    }
271
0
  }
272
273
0
  for (auto& region : m_region_items) {
274
0
    std::vector<uint8_t> data_array;
275
0
    Error err = region->encode(data_array);
276
    // TODO: err
277
278
0
    m_heif_file->append_iloc_data(region->item_id, data_array, 0);
279
0
  }
280
281
  // --- post-process images
282
283
0
  for (auto& img : m_all_images) {
284
0
    img.second->process_before_write();
285
0
  }
286
287
  // --- sort item properties
288
289
0
  m_heif_file->get_ipma_box()->sort_properties(m_heif_file->get_ipco_box());
290
291
  // --- write to file
292
293
0
  m_heif_file->write(writer);
294
0
}
295
296
std::string HeifContext::debug_dump_boxes() const
297
0
{
298
0
  return m_heif_file->debug_dump_boxes();
299
0
}
300
301
302
static bool item_type_is_image(uint32_t item_type, const std::string& content_type)
303
0
{
304
0
  return (item_type == fourcc("hvc1") ||
305
0
          item_type == fourcc("av01") ||
306
0
          item_type == fourcc("grid") ||
307
0
          item_type == fourcc("tili") ||
308
0
          item_type == fourcc("iden") ||
309
0
          item_type == fourcc("iovl") ||
310
0
          item_type == fourcc("avc1") ||
311
0
          item_type == fourcc("unci") ||
312
0
          item_type == fourcc("vvc1") ||
313
0
          item_type == fourcc("jpeg") ||
314
0
          (item_type == fourcc("mime") && content_type == "image/jpeg") ||
315
0
          item_type == fourcc("j2k1") ||
316
0
          item_type == fourcc("mski"));
317
0
}
318
319
320
void HeifContext::remove_top_level_image(const std::shared_ptr<ImageItem>& image)
321
0
{
322
0
  std::vector<std::shared_ptr<ImageItem>> new_list;
323
324
0
  for (const auto& img : m_top_level_images) {
325
0
    if (img != image) {
326
0
      new_list.push_back(img);
327
0
    }
328
0
  }
329
330
0
  m_top_level_images = std::move(new_list);
331
0
}
332
333
334
Error HeifContext::interpret_heif_file()
335
0
{
336
0
  m_all_images.clear();
337
0
  m_top_level_images.clear();
338
0
  m_primary_image.reset();
339
340
  // --- reference all non-hidden images
341
342
0
  std::vector<heif_item_id> image_IDs = m_heif_file->get_item_IDs();
343
0
#if WITH_EXPERIMENTAL_GAIN_MAP
344
0
  bool check_tmap_item = m_heif_file->get_ftyp_box()->has_compatible_brand(heif_brand2_tmap);
345
0
#endif
346
347
0
  for (heif_item_id id : image_IDs) {
348
0
    auto infe_box = m_heif_file->get_infe_box(id);
349
0
    if (!infe_box) {
350
      // TODO(farindk): Should we return an error instead of skipping the invalid id?
351
0
      continue;
352
0
    }
353
354
0
    auto image = ImageItem::alloc_for_infe_box(this, infe_box);
355
0
    if (!image) {
356
      // It is no image item, skip it.
357
0
      continue;
358
0
    }
359
360
0
    m_all_images.insert(std::make_pair(id, image));
361
362
0
    if (!infe_box->is_hidden_item()) {
363
0
      if (id == m_heif_file->get_primary_image_ID()) {
364
0
        image->set_primary(true);
365
0
        m_primary_image = image;
366
0
      }
367
368
0
      m_top_level_images.push_back(image);
369
0
    }
370
371
0
    std::vector<std::shared_ptr<Box>> properties;
372
0
    Error err = m_heif_file->get_properties(id, properties);
373
0
    if (err) {
374
0
      return err;
375
0
    }
376
377
0
    image->set_properties(properties);
378
379
0
    err = image->on_load_file();
380
0
    if (err) {
381
0
      return err;
382
0
    }
383
0
  }
384
385
0
  if (!m_primary_image) {
386
0
    return Error(heif_error_Invalid_input,
387
0
                 heif_suberror_Nonexisting_item_referenced,
388
0
                 "'pitm' box references an unsupported or non-existing image");
389
0
  }
390
391
392
  // --- process image properties
393
394
0
  for (auto& pair : m_all_images) {
395
0
    auto& image = pair.second;
396
397
0
    if (image->get_item_error()) {
398
0
      continue;
399
0
    }
400
401
0
    std::vector<std::shared_ptr<Box>> properties;
402
403
0
    Error err = m_heif_file->get_properties(pair.first, properties);
404
0
    if (err) {
405
0
      return err;
406
0
    }
407
408
409
    // --- are there any 'essential' properties that we did not parse?
410
411
0
    for (const auto& prop : properties) {
412
0
      if (std::dynamic_pointer_cast<Box_other>(prop) &&
413
0
          get_heif_file()->get_ipco_box()->is_property_essential_for_item(pair.first, prop, get_heif_file()->get_ipma_box())) {
414
415
0
        std::stringstream sstr;
416
0
        sstr << "could not parse item property '" << prop->get_type_string() << "'";
417
0
        return {heif_error_Unsupported_feature, heif_suberror_Unsupported_essential_property, sstr.str()};
418
0
      }
419
0
    }
420
421
422
    // --- Are there any parse errors in optional properties? Attach the errors as warnings to the images.
423
424
0
    bool ignore_nonfatal_parse_errors = false; // TODO: this should be a user option. Where should we put this (heif_decoding_options, or while creating the context) ?
425
426
0
    for (const auto& prop : properties) {
427
0
      if (auto errorbox = std::dynamic_pointer_cast<Box_Error>(prop)) {
428
0
        parse_error_fatality fatality = errorbox->get_parse_error_fatality();
429
430
0
        if (fatality == parse_error_fatality::optional ||
431
0
            (fatality == parse_error_fatality::ignorable && ignore_nonfatal_parse_errors)) {
432
0
          image->add_decoding_warning(errorbox->get_error());
433
0
        }
434
0
        else {
435
0
          return errorbox->get_error();
436
0
        }
437
0
      }
438
0
    }
439
440
441
    // --- extract image resolution
442
443
0
    bool ispe_read = false;
444
0
    for (const auto& prop : properties) {
445
0
      auto ispe = std::dynamic_pointer_cast<Box_ispe>(prop);
446
0
      if (ispe) {
447
0
        uint32_t width = ispe->get_width();
448
0
        uint32_t height = ispe->get_height();
449
450
0
        Error sizeError = check_for_valid_image_size(get_security_limits(), width, height);
451
0
        if (sizeError) {
452
0
          return sizeError;
453
0
        }
454
455
0
        image->set_resolution(width, height);
456
0
        ispe_read = true;
457
0
      }
458
0
    }
459
460
    // Note: usually, we would like to check here if an `ispe` property exists as this is mandatory.
461
    // We want to do this if decoding_options.strict_decoding is set, but we cannot because we have no decoding_options
462
    // when parsing the file structure.
463
464
0
    if (!ispe_read) {
465
0
      image->add_decoding_warning({heif_error_Invalid_input, heif_suberror_No_ispe_property});
466
0
    }
467
468
469
0
    for (const auto& prop : properties) {
470
0
      auto colr = std::dynamic_pointer_cast<Box_colr>(prop);
471
0
      if (colr) {
472
0
        auto profile = colr->get_color_profile();
473
0
        image->set_color_profile(profile);
474
0
        continue;
475
0
      }
476
477
0
      auto cmin = std::dynamic_pointer_cast<Box_cmin>(prop);
478
0
      if (cmin) {
479
0
        if (!ispe_read) {
480
0
          return {heif_error_Invalid_input, heif_suberror_No_ispe_property};
481
0
        }
482
483
0
        image->set_intrinsic_matrix(cmin->get_intrinsic_matrix());
484
0
      }
485
486
0
      auto cmex = std::dynamic_pointer_cast<Box_cmex>(prop);
487
0
      if (cmex) {
488
0
        image->set_extrinsic_matrix(cmex->get_extrinsic_matrix());
489
0
      }
490
0
    }
491
492
493
0
    for (const auto& prop : properties) {
494
0
      auto clap = std::dynamic_pointer_cast<Box_clap>(prop);
495
0
      if (clap) {
496
0
        image->set_resolution(clap->get_width_rounded(),
497
0
                              clap->get_height_rounded());
498
499
0
        if (image->has_intrinsic_matrix()) {
500
0
          image->get_intrinsic_matrix().apply_clap(clap.get(), image->get_width(), image->get_height());
501
0
        }
502
0
      }
503
504
0
      auto imir = std::dynamic_pointer_cast<Box_imir>(prop);
505
0
      if (imir) {
506
0
        if (!ispe_read) {
507
0
          return {heif_error_Invalid_input, heif_suberror_No_ispe_property};
508
0
        }
509
510
0
        image->get_intrinsic_matrix().apply_imir(imir.get(), image->get_width(), image->get_height());
511
0
      }
512
513
0
      auto irot = std::dynamic_pointer_cast<Box_irot>(prop);
514
0
      if (irot) {
515
0
        if (irot->get_rotation_ccw() == 90 ||
516
0
            irot->get_rotation_ccw() == 270) {
517
0
          if (!ispe_read) {
518
0
            return {heif_error_Invalid_input, heif_suberror_No_ispe_property};
519
0
          }
520
521
          // swap width and height
522
0
          image->set_resolution(image->get_height(),
523
0
                                image->get_width());
524
0
        }
525
526
        // TODO: apply irot to camera extrinsic matrix
527
0
      }
528
0
    }
529
0
  }
530
531
532
  // --- remove auxiliary from top-level images and assign to their respective image
533
534
0
  auto iref_box = m_heif_file->get_iref_box();
535
0
  if (iref_box) {
536
    // m_top_level_images.clear();
537
538
0
    for (auto& pair : m_all_images) {
539
0
      auto& image = pair.second;
540
541
0
      std::vector<Box_iref::Reference> references = iref_box->get_references_from(image->get_id());
542
543
0
      for (const Box_iref::Reference& ref : references) {
544
0
        uint32_t type = ref.header.get_short_type();
545
546
0
        if (type == fourcc("thmb")) {
547
          // --- this is a thumbnail image, attach to the main image
548
549
0
          std::vector<heif_item_id> refs = ref.to_item_ID;
550
0
          for (heif_item_id ref: refs) {
551
0
            image->set_is_thumbnail();
552
553
0
            auto master_iter = m_all_images.find(ref);
554
0
            if (master_iter == m_all_images.end()) {
555
0
              return Error(heif_error_Invalid_input,
556
0
                          heif_suberror_Nonexisting_item_referenced,
557
0
                          "Thumbnail references a non-existing image");
558
0
            }
559
560
0
            if (master_iter->second->is_thumbnail()) {
561
0
              return Error(heif_error_Invalid_input,
562
0
                          heif_suberror_Nonexisting_item_referenced,
563
0
                          "Thumbnail references another thumbnail");
564
0
            }
565
566
0
            if (image.get() == master_iter->second.get()) {
567
0
              return Error(heif_error_Invalid_input,
568
0
                          heif_suberror_Nonexisting_item_referenced,
569
0
                          "Recursive thumbnail image detected");
570
0
            }
571
0
            master_iter->second->add_thumbnail(image);
572
0
          }
573
0
          remove_top_level_image(image);
574
0
        }
575
0
        else if (type == fourcc("auxl")) {
576
577
          // --- this is an auxiliary image
578
          //     check whether it is an alpha channel and attach to the main image if yes
579
580
0
          std::shared_ptr<Box_auxC> auxC_property = image->get_property<Box_auxC>();
581
0
          if (!auxC_property) {
582
0
            std::stringstream sstr;
583
0
            sstr << "No auxC property for image " << image->get_id();
584
0
            return Error(heif_error_Invalid_input,
585
0
                         heif_suberror_Auxiliary_image_type_unspecified,
586
0
                         sstr.str());
587
0
          }
588
589
0
          std::vector<heif_item_id> refs = ref.to_item_ID;
590
591
          // alpha channel
592
593
0
          if (auxC_property->get_aux_type() == "urn:mpeg:avc:2015:auxid:1" ||   // HEIF (avc)
594
0
              auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:1" ||  // HEIF (h265)
595
0
              auxC_property->get_aux_type() == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha") { // MIAF
596
597
0
            for (heif_item_id ref: refs) {
598
0
              auto master_iter = m_all_images.find(ref);
599
0
              if (master_iter == m_all_images.end()) {
600
601
0
                if (!m_heif_file->has_item_with_id(ref)) {
602
0
                  return Error(heif_error_Invalid_input,
603
0
                               heif_suberror_Nonexisting_item_referenced,
604
0
                               "Non-existing alpha image referenced");
605
0
                }
606
607
0
                continue;
608
0
              }
609
610
0
              auto master_img = master_iter->second;
611
612
0
              if (image.get() == master_img.get()) {
613
0
                return Error(heif_error_Invalid_input,
614
0
                            heif_suberror_Nonexisting_item_referenced,
615
0
                            "Recursive alpha image detected");
616
0
              }
617
618
0
              image->set_is_alpha_channel();
619
0
              master_img->set_alpha_channel(image);
620
0
            }
621
0
          }
622
623
624
          // depth channel
625
626
0
          if (auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:2" || // HEIF
627
0
              auxC_property->get_aux_type() == "urn:mpeg:mpegB:cicp:systems:auxiliary:depth") { // AVIF
628
0
            image->set_is_depth_channel();
629
630
0
            for (heif_item_id ref: refs) {
631
0
              auto master_iter = m_all_images.find(ref);
632
0
              if (master_iter == m_all_images.end()) {
633
634
0
                if (!m_heif_file->has_item_with_id(ref)) {
635
0
                  return Error(heif_error_Invalid_input,
636
0
                               heif_suberror_Nonexisting_item_referenced,
637
0
                               "Non-existing depth image referenced");
638
0
                }
639
640
0
                continue;
641
0
              }
642
0
              if (image.get() == master_iter->second.get()) {
643
0
                return Error(heif_error_Invalid_input,
644
0
                            heif_suberror_Nonexisting_item_referenced,
645
0
                            "Recursive depth image detected");
646
0
              }
647
0
              master_iter->second->set_depth_channel(image);
648
649
0
              const auto& subtypes = auxC_property->get_subtypes();
650
651
0
              if (!subtypes.empty()) {
652
0
                std::vector<std::shared_ptr<SEIMessage>> sei_messages;
653
0
                Error err = decode_hevc_aux_sei_messages(subtypes, sei_messages);
654
0
                if (err) {
655
0
                  return err;
656
0
                }
657
658
0
                for (auto& msg : sei_messages) {
659
0
                  auto depth_msg = std::dynamic_pointer_cast<SEIMessage_depth_representation_info>(msg);
660
0
                  if (depth_msg) {
661
0
                    image->set_depth_representation_info(*depth_msg);
662
0
                  }
663
0
                }
664
0
              }
665
0
            }
666
0
          }
667
668
669
          // --- generic aux image
670
671
0
          image->set_is_aux_image(auxC_property->get_aux_type());
672
673
0
          for (heif_item_id ref: refs) {
674
0
            auto master_iter = m_all_images.find(ref);
675
0
            if (master_iter == m_all_images.end()) {
676
677
0
              if (!m_heif_file->has_item_with_id(ref)) {
678
0
                return Error(heif_error_Invalid_input,
679
0
                             heif_suberror_Nonexisting_item_referenced,
680
0
                             "Non-existing aux image referenced");
681
0
              }
682
683
0
              continue;
684
0
            }
685
0
            if (image.get() == master_iter->second.get()) {
686
0
              return Error(heif_error_Invalid_input,
687
0
                          heif_suberror_Nonexisting_item_referenced,
688
0
                          "Recursive aux image detected");
689
0
            }
690
691
0
            master_iter->second->add_aux_image(image);
692
693
0
            remove_top_level_image(image);
694
0
          }
695
0
        }
696
0
        else {
697
          // 'image' is a normal image, keep it as a top-level image
698
0
        }
699
0
      }
700
0
    }
701
0
  }
702
703
704
  // --- check that HEVC images have an hvcC property
705
706
0
  for (auto& pair : m_all_images) {
707
0
    auto& image = pair.second;
708
709
0
    if (image->get_item_error()) {
710
0
      continue;
711
0
    }
712
713
0
    std::shared_ptr<Box_infe> infe = m_heif_file->get_infe_box(image->get_id());
714
0
    if (infe->get_item_type_4cc() == fourcc("hvc1")) {
715
716
0
      auto ipma = m_heif_file->get_ipma_box();
717
0
      auto ipco = m_heif_file->get_ipco_box();
718
719
0
      if (!ipco->get_property_for_item_ID(image->get_id(), ipma, fourcc("hvcC"))) {
720
0
        return Error(heif_error_Invalid_input,
721
0
                     heif_suberror_No_hvcC_box,
722
0
                     "No hvcC property in hvc1 type image");
723
0
      }
724
0
    }
725
0
    if (infe->get_item_type_4cc() == fourcc("vvc1")) {
726
727
0
      auto ipma = m_heif_file->get_ipma_box();
728
0
      auto ipco = m_heif_file->get_ipco_box();
729
730
0
      if (!ipco->get_property_for_item_ID(image->get_id(), ipma, fourcc("vvcC"))) {
731
0
        return Error(heif_error_Invalid_input,
732
0
                     heif_suberror_No_vvcC_box,
733
0
                     "No vvcC property in vvc1 type image");
734
0
      }
735
0
    }
736
0
  }
737
738
739
  // --- assign color profile from grid tiles to main image when main image has no profile assigned
740
741
0
  for (auto& pair : m_all_images) {
742
0
    auto& image = pair.second;
743
0
    auto id = pair.first;
744
745
0
    if (image->get_item_error()) {
746
0
      continue;
747
0
    }
748
749
0
    auto infe_box = m_heif_file->get_infe_box(id);
750
0
    if (!infe_box) {
751
0
      continue;
752
0
    }
753
754
0
    if (!iref_box) {
755
0
      break;
756
0
    }
757
758
0
    if (infe_box->get_item_type_4cc() == fourcc("grid")) {
759
0
      std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg"));
760
761
0
      if (image_references.empty()) {
762
0
        continue; // TODO: can this every happen?
763
0
      }
764
765
0
      auto tileId = image_references.front();
766
767
0
      auto iter = m_all_images.find(tileId);
768
0
      if (iter == m_all_images.end()) {
769
0
        continue; // invalid grid entry
770
0
      }
771
772
0
      auto tile_img = iter->second;
773
0
      if (image->get_color_profile_icc() == nullptr && tile_img->get_color_profile_icc()) {
774
0
        image->set_color_profile(tile_img->get_color_profile_icc());
775
0
      }
776
777
0
      if (image->get_color_profile_nclx() == nullptr && tile_img->get_color_profile_nclx()) {
778
0
        image->set_color_profile(tile_img->get_color_profile_nclx());
779
0
      }
780
0
    }
781
0
  }
782
783
784
  // --- read metadata and assign to image
785
786
0
  for (heif_item_id id : image_IDs) {
787
0
    uint32_t item_type = m_heif_file->get_item_type_4cc(id);
788
0
    std::string content_type = m_heif_file->get_content_type(id);
789
790
    // 'rgan': skip region annotations, handled next
791
    // 'iden': iden images are no metadata
792
0
    if (item_type_is_image(item_type, content_type) || item_type == fourcc("rgan")) {
793
0
      continue;
794
0
    }
795
796
0
    std::string item_uri_type = m_heif_file->get_item_uri_type(id);
797
798
    // we now assign all kinds of metadata to the image, not only 'Exif' and 'XMP'
799
800
0
    std::shared_ptr<ImageMetadata> metadata = std::make_shared<ImageMetadata>();
801
0
    metadata->item_id = id;
802
0
    metadata->item_type = fourcc_to_string(item_type);
803
0
    metadata->content_type = content_type;
804
0
    metadata->item_uri_type = std::move(item_uri_type);
805
806
0
    Error err = m_heif_file->get_uncompressed_item_data(id, &(metadata->m_data));
807
0
    if (err) {
808
0
      if (item_type == fourcc("Exif") || item_type == fourcc("mime")) {
809
        // these item types should have data
810
0
        return err;
811
0
      }
812
0
#if WITH_EXPERIMENTAL_GAIN_MAP
813
0
      else if (check_tmap_item && item_type == fourcc("tmap")) {
814
        // this also should have data
815
0
        return err;
816
0
      }
817
0
#endif
818
0
      else {
819
        // anything else is probably something that we don't understand yet
820
0
        continue;
821
0
      }
822
0
    }
823
824
825
    // --- assign metadata to the image
826
827
0
    if (iref_box) {
828
0
      std::vector<heif_item_id> references = iref_box->get_references(id, fourcc("cdsc"));
829
0
      for (heif_item_id exif_image_id : references) {
830
0
        auto img_iter = m_all_images.find(exif_image_id);
831
0
        if (img_iter == m_all_images.end()) {
832
0
          if (!m_heif_file->has_item_with_id(exif_image_id)) {
833
0
            return Error(heif_error_Invalid_input,
834
0
                         heif_suberror_Nonexisting_item_referenced,
835
0
                         "Metadata assigned to non-existing image");
836
0
          }
837
838
0
          continue;
839
0
        }
840
0
        img_iter->second->add_metadata(metadata);
841
0
      }
842
0
#if WITH_EXPERIMENTAL_GAIN_MAP
843
0
      if (check_tmap_item && item_type == fourcc("tmap")) {
844
0
        std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg"));
845
        // "'tmap' item MUST be associated with 2 references to images"
846
        // "'tmap' item first entry is expected to be referencing primary image"
847
        // "'tmap' item reference entries MUST not be duplicate"
848
        // "'tmap' item references MUST be pointing to valid image items"
849
0
        if ((int)image_references.size() == 2 &&
850
0
            image_references[0] == m_heif_file->get_primary_image_ID() &&
851
0
            image_references[1] != image_references[0] &&
852
0
            m_all_images.find(image_references[0]) != m_all_images.end() &&
853
0
            m_all_images.find(image_references[1]) != m_all_images.end()) {
854
0
          std::shared_ptr<ImageItem> baseItem = m_all_images.find(image_references[0])->second;
855
0
          std::shared_ptr<ImageItem> gainmapItem = m_all_images.find(image_references[1])->second;
856
857
0
          baseItem->set_gain_map(gainmapItem);
858
859
0
          baseItem->add_metadata(metadata);  // gain map metadata
860
861
0
          auto ipma = m_heif_file->get_ipma_box();
862
0
          auto ipco = m_heif_file->get_ipco_box();
863
0
          auto derived_image_colr = ipco->get_property_for_item_ID(id, ipma, fourcc("colr"));
864
0
          auto colr = std::dynamic_pointer_cast<Box_colr>(derived_image_colr);
865
0
          auto nclx =
866
0
              std::dynamic_pointer_cast<const color_profile_nclx>(colr->get_color_profile());
867
0
          if (nclx) {
868
0
            baseItem->set_derived_img_color_profile(nclx);
869
0
          }
870
0
          auto raw = std::dynamic_pointer_cast<const color_profile_raw>(colr->get_color_profile());
871
0
          if (raw) {
872
0
            baseItem->set_derived_img_color_profile(raw);
873
0
          }
874
0
        }
875
0
      }
876
0
#endif
877
0
    }
878
0
  }
879
880
  // --- set premultiplied alpha flag
881
882
0
  for (heif_item_id id : image_IDs) {
883
0
    if (iref_box) {
884
0
      std::vector<heif_item_id> references = iref_box->get_references(id, fourcc("prem"));
885
0
      for (heif_item_id ref : references) {
886
0
        (void)ref;
887
888
0
        heif_item_id color_image_id = id;
889
0
        auto img_iter = m_all_images.find(color_image_id);
890
0
        if (img_iter == m_all_images.end()) {
891
0
          return Error(heif_error_Invalid_input,
892
0
                       heif_suberror_Nonexisting_item_referenced,
893
0
                       "`prem` link assigned to non-existing image");
894
0
        }
895
896
0
        img_iter->second->set_is_premultiplied_alpha(true);
897
0
      }
898
0
    }
899
0
  }
900
901
  // --- read region item and assign to image(s)
902
903
0
  for (heif_item_id id : image_IDs) {
904
0
    uint32_t item_type = m_heif_file->get_item_type_4cc(id);
905
0
    if (item_type != fourcc("rgan")) {
906
0
      continue;
907
0
    }
908
909
0
    std::shared_ptr<RegionItem> region_item = std::make_shared<RegionItem>();
910
0
    region_item->item_id = id;
911
0
    std::vector<uint8_t> region_data;
912
0
    Error err = m_heif_file->get_uncompressed_item_data(id, &region_data);
913
0
    if (err) {
914
0
      return err;
915
0
    }
916
0
    region_item->parse(region_data);
917
0
    if (iref_box) {
918
0
      std::vector<Box_iref::Reference> references = iref_box->get_references_from(id);
919
0
      for (const auto& ref : references) {
920
0
        if (ref.header.get_short_type() == fourcc("cdsc")) {
921
0
          std::vector<uint32_t> refs = ref.to_item_ID;
922
0
          for (uint32_t ref : refs) {
923
0
            uint32_t image_id = ref;
924
0
            auto img_iter = m_all_images.find(image_id);
925
0
            if (img_iter == m_all_images.end()) {
926
0
              return Error(heif_error_Invalid_input,
927
0
                           heif_suberror_Nonexisting_item_referenced,
928
0
                           "Region item assigned to non-existing image");
929
0
            }
930
0
            img_iter->second->add_region_item_id(id);
931
0
            m_region_items.push_back(region_item);
932
0
          }
933
0
        }
934
935
        /* When the geometry 'mask' of a region is represented by a mask stored in
936
        * another image item the image item containing the mask shall be identified
937
        * by an item reference of type 'mask' from the region item to the image item
938
        * containing the mask. */
939
0
        if (ref.header.get_short_type() == fourcc("mask")) {
940
0
          std::vector<uint32_t> refs = ref.to_item_ID;
941
0
          size_t mask_index = 0;
942
0
          for (int j = 0; j < region_item->get_number_of_regions(); j++) {
943
0
            if (region_item->get_regions()[j]->getRegionType() == heif_region_type_referenced_mask) {
944
0
              std::shared_ptr<RegionGeometry_ReferencedMask> mask_geometry = std::dynamic_pointer_cast<RegionGeometry_ReferencedMask>(region_item->get_regions()[j]);
945
946
0
              if (mask_index >= refs.size()) {
947
0
                return Error(heif_error_Invalid_input,
948
0
                             heif_suberror_Unspecified,
949
0
                             "Region mask reference with non-existing mask image reference");
950
0
              }
951
952
0
              uint32_t mask_image_id = refs[mask_index];
953
0
              if (!is_image(mask_image_id)) {
954
0
                return Error(heif_error_Invalid_input,
955
0
                             heif_suberror_Unspecified,
956
0
                             "Region mask referenced item is not an image");
957
0
              }
958
959
0
              auto mask_image = get_image(mask_image_id, true);
960
0
              if (auto error = mask_image->get_item_error()) {
961
0
                return error;
962
0
              }
963
964
0
              mask_geometry->referenced_item = mask_image_id;
965
0
              if (mask_geometry->width == 0) {
966
0
                mask_geometry->width = mask_image->get_ispe_width();
967
0
              }
968
0
              if (mask_geometry->height == 0) {
969
0
                mask_geometry->height = mask_image->get_ispe_height();
970
0
              }
971
0
              mask_index += 1;
972
0
              remove_top_level_image(mask_image);
973
0
            }
974
0
          }
975
0
        }
976
0
      }
977
0
    }
978
0
  }
979
980
0
  return Error::Ok;
981
0
}
982
983
984
bool HeifContext::has_alpha(heif_item_id ID) const
985
0
{
986
0
  auto imgIter = m_all_images.find(ID);
987
0
  if (imgIter == m_all_images.end()) {
988
0
    return false;
989
0
  }
990
991
0
  auto img = imgIter->second;
992
993
  // --- has the image an auxiliary alpha image?
994
995
0
  if (img->get_alpha_channel() != nullptr) {
996
0
    return true;
997
0
  }
998
999
0
  if (img->has_coded_alpha_channel()) {
1000
0
    return true;
1001
0
  }
1002
1003
0
  heif_colorspace colorspace;
1004
0
  heif_chroma chroma;
1005
0
  Error err = img->get_coded_image_colorspace(&colorspace, &chroma);
1006
0
  if (err) {
1007
0
    return false;
1008
0
  }
1009
1010
0
  if (chroma == heif_chroma_interleaved_RGBA ||
1011
0
      chroma == heif_chroma_interleaved_RRGGBBAA_BE ||
1012
0
      chroma == heif_chroma_interleaved_RRGGBBAA_LE) {
1013
0
    return true;
1014
0
  }
1015
1016
  // --- if the image is a 'grid', check if there is alpha in any of the tiles
1017
1018
  // TODO: move this into ImageItem
1019
1020
0
  uint32_t image_type = m_heif_file->get_item_type_4cc(ID);
1021
0
  if (image_type == fourcc("grid")) {
1022
0
    std::vector<uint8_t> grid_data;
1023
0
    Error error = m_heif_file->get_uncompressed_item_data(ID, &grid_data);
1024
0
    if (error) {
1025
0
      return false;
1026
0
    }
1027
1028
0
    ImageGrid grid;
1029
0
    err = grid.parse(grid_data);
1030
0
    if (err) {
1031
0
      return false;
1032
0
    }
1033
1034
1035
0
    auto iref_box = m_heif_file->get_iref_box();
1036
1037
0
    if (!iref_box) {
1038
0
      return false;
1039
0
    }
1040
1041
0
    std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg"));
1042
1043
0
    if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) {
1044
0
      return false;
1045
0
    }
1046
1047
1048
    // --- check that all image IDs are valid images
1049
1050
0
    for (heif_item_id tile_id : image_references) {
1051
0
      if (!is_image(tile_id)) {
1052
0
        return false;
1053
0
      }
1054
0
    }
1055
1056
    // --- check whether at least one tile has an alpha channel
1057
1058
0
    bool has_alpha = false;
1059
1060
0
    for (heif_item_id tile_id : image_references) {
1061
0
      auto iter = m_all_images.find(tile_id);
1062
0
      if (iter == m_all_images.end()) {
1063
0
        return false;
1064
0
      }
1065
1066
0
      const std::shared_ptr<ImageItem> tileImg = iter->second;
1067
1068
0
      has_alpha |= tileImg->get_alpha_channel() != nullptr;
1069
0
    }
1070
1071
0
    return has_alpha;
1072
0
  }
1073
0
  else {
1074
    // TODO: what about overlays ?
1075
0
    return false;
1076
0
  }
1077
0
}
1078
1079
1080
Error HeifContext::get_id_of_non_virtual_child_image(heif_item_id id, heif_item_id& out) const
1081
0
{
1082
0
  uint32_t image_type = m_heif_file->get_item_type_4cc(id);
1083
0
  if (image_type == fourcc("grid") ||
1084
0
      image_type == fourcc("iden") ||
1085
0
      image_type == fourcc("iovl")) {
1086
0
    auto iref_box = m_heif_file->get_iref_box();
1087
0
    if (!iref_box) {
1088
0
      return Error(heif_error_Invalid_input,
1089
0
                   heif_suberror_No_item_data,
1090
0
                   "Derived image does not reference any other image items");
1091
0
    }
1092
1093
0
    std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg"));
1094
1095
    // TODO: check whether this really can be recursive (e.g. overlay of grid images)
1096
1097
0
    if (image_references.empty() || image_references[0] == id) {
1098
0
      return Error(heif_error_Invalid_input,
1099
0
                   heif_suberror_No_item_data,
1100
0
                   "Derived image does not reference any other image items");
1101
0
    }
1102
0
    else {
1103
0
      return get_id_of_non_virtual_child_image(image_references[0], out);
1104
0
    }
1105
0
  }
1106
0
  else {
1107
0
    if (m_all_images.find(id) == m_all_images.end()) {
1108
0
      std::stringstream sstr;
1109
0
      sstr << "Image item " << id << " referenced, but it does not exist\n";
1110
1111
0
      return Error(heif_error_Invalid_input,
1112
0
        heif_suberror_Nonexisting_item_referenced,
1113
0
        sstr.str());
1114
0
    }
1115
0
    else if (dynamic_cast<ImageItem_Error*>(m_all_images.find(id)->second.get())) {
1116
      // Should er return an error here or leave it to the follow-up code to detect that?
1117
0
    }
1118
1119
0
    out = id;
1120
0
    return Error::Ok;
1121
0
  }
1122
0
}
1123
1124
1125
Result<std::shared_ptr<HeifPixelImage>> HeifContext::decode_image(heif_item_id ID,
1126
                                                                  heif_colorspace out_colorspace,
1127
                                                                  heif_chroma out_chroma,
1128
                                                                  const struct heif_decoding_options& options,
1129
                                                                  bool decode_only_tile, uint32_t tx, uint32_t ty) const
1130
0
{
1131
0
  std::shared_ptr<ImageItem> imgitem;
1132
0
  if (m_all_images.find(ID) != m_all_images.end()) {
1133
0
    imgitem = m_all_images.find(ID)->second;
1134
0
  }
1135
1136
  // Note: this may happen, for example when an 'iden' image references a non-existing image item.
1137
0
  if (imgitem == nullptr) {
1138
0
    return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced);
1139
0
  }
1140
1141
1142
0
  auto decodingResult = imgitem->decode_image(options, decode_only_tile, tx, ty);
1143
0
  if (decodingResult.error) {
1144
0
    return decodingResult.error;
1145
0
  }
1146
1147
0
  std::shared_ptr<HeifPixelImage> img = decodingResult.value;
1148
1149
1150
  // --- convert to output chroma format
1151
1152
0
  heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ?
1153
0
                                       img->get_colorspace() :
1154
0
                                       out_colorspace);
1155
1156
0
  heif_chroma target_chroma = (out_chroma == heif_chroma_undefined ?
1157
0
                               img->get_chroma_format() : out_chroma);
1158
1159
0
  bool different_chroma = (target_chroma != img->get_chroma_format());
1160
0
  bool different_colorspace = (target_colorspace != img->get_colorspace());
1161
1162
0
  uint8_t img_bpp = img->get_visual_image_bits_per_pixel();
1163
0
  uint8_t converted_output_bpp = (options.convert_hdr_to_8bit && img_bpp > 8) ? 8 : 0 /* keep input depth */;
1164
1165
0
  if (different_chroma ||
1166
0
      different_colorspace ||
1167
0
      converted_output_bpp) {
1168
1169
0
    auto img_result = convert_colorspace(img, target_colorspace, target_chroma, nullptr,
1170
0
                                         converted_output_bpp, options.color_conversion_options, get_security_limits());
1171
0
    if (img_result.error) {
1172
0
      return img_result.error;
1173
0
    }
1174
0
    else {
1175
0
      img = *img_result;
1176
0
    }
1177
0
  }
1178
1179
0
  img->add_warnings(imgitem->get_decoding_warnings());
1180
1181
0
  return img;
1182
0
}
1183
1184
1185
static Result<std::shared_ptr<HeifPixelImage>>
1186
create_alpha_image_from_image_alpha_channel(const std::shared_ptr<HeifPixelImage>& image,
1187
                                            const heif_security_limits* limits)
1188
0
{
1189
  // --- generate alpha image
1190
1191
0
  std::shared_ptr<HeifPixelImage> alpha_image = std::make_shared<HeifPixelImage>();
1192
0
  alpha_image->create(image->get_width(), image->get_height(),
1193
0
                      heif_colorspace_monochrome, heif_chroma_monochrome);
1194
1195
0
  if (image->has_channel(heif_channel_Alpha)) {
1196
0
    alpha_image->copy_new_plane_from(image, heif_channel_Alpha, heif_channel_Y, limits);
1197
0
  }
1198
0
  else if (image->get_chroma_format() == heif_chroma_interleaved_RGBA) {
1199
0
    if (auto err = alpha_image->extract_alpha_from_RGBA(image, limits)) {
1200
0
      return err;
1201
0
    }
1202
0
  }
1203
  // TODO: 16 bit
1204
1205
  // --- set nclx profile with full-range flag
1206
1207
0
  auto nclx = std::make_shared<color_profile_nclx>();
1208
0
  nclx->set_undefined();
1209
0
  nclx->set_full_range_flag(true); // this is the default, but just to be sure in case the defaults change
1210
0
  alpha_image->set_color_profile_nclx(nclx);
1211
1212
0
  return alpha_image;
1213
0
}
1214
1215
1216
#if WITH_EXPERIMENTAL_GAIN_MAP
1217
0
Error HeifContext::add_tmap_item(const std::vector<uint8_t>& data, heif_item_id& item_id) {
1218
0
  auto tmap_infe = m_heif_file->add_new_infe_box(fourcc("tmap"));  // gain map metadata
1219
0
  tmap_infe->set_item_name("GMap");
1220
0
  item_id = tmap_infe->get_item_ID();
1221
1222
0
  m_heif_file->append_iloc_data(item_id, data, 0);
1223
1224
0
  return Error::Ok;
1225
0
}
1226
1227
1228
Error HeifContext::link_gain_map(const std::shared_ptr<ImageItem>& primary_image,
1229
                                 const std::shared_ptr<ImageItem>& gain_map_image,
1230
0
                                 const heif_item_id tmap_id) {
1231
0
  m_heif_file->add_iref_reference(tmap_id, fourcc("dimg"),
1232
0
                                  {primary_image->get_id(), gain_map_image->get_id()});
1233
1234
0
  return Error::Ok;
1235
0
}
1236
#endif
1237
1238
1239
Result<std::shared_ptr<ImageItem>> HeifContext::encode_image(const std::shared_ptr<HeifPixelImage>& pixel_image,
1240
                                struct heif_encoder* encoder,
1241
                                const struct heif_encoding_options& in_options,
1242
                                enum heif_image_input_class input_class)
1243
0
{
1244
0
  std::shared_ptr<ImageItem> output_image_item = ImageItem::alloc_for_compression_format(this, encoder->plugin->compression_format);
1245
1246
1247
#if 0
1248
  // TODO: the hdlr box is not the right place for comments
1249
  // m_heif_file->set_hdlr_library_info(encoder->plugin->get_plugin_name());
1250
1251
    case heif_compression_mask: {
1252
      error = encode_image_as_mask(pixel_image,
1253
                                  encoder,
1254
                                  options,
1255
                                  input_class,
1256
                                  out_image);
1257
    }
1258
      break;
1259
1260
    default:
1261
      return Error(heif_error_Encoder_plugin_error, heif_suberror_Unsupported_codec);
1262
  }
1263
#endif
1264
1265
1266
  // --- check whether we have to convert the image color space
1267
1268
  // The reason for doing the color conversion here is that the input might be an RGBA image and the color conversion
1269
  // will extract the alpha plane anyway. We can reuse that plane below instead of having to do a new conversion.
1270
1271
0
  heif_encoding_options options = in_options;
1272
1273
0
  if (const auto* nclx = output_image_item->get_forced_output_nclx()) {
1274
0
    options.output_nclx_profile = const_cast<heif_color_profile_nclx*>(nclx);
1275
0
  }
1276
1277
0
  Result<std::shared_ptr<HeifPixelImage>> srcImageResult = output_image_item->convert_colorspace_for_encoding(pixel_image,
1278
0
                                                                                                              encoder,
1279
0
                                                                                                              options);
1280
0
  if (srcImageResult.error) {
1281
0
    return srcImageResult.error;
1282
0
  }
1283
1284
0
  std::shared_ptr<HeifPixelImage> colorConvertedImage = srcImageResult.value;
1285
1286
1287
0
  Error err = output_image_item->encode_to_item(this,
1288
0
                                                colorConvertedImage,
1289
0
                                                encoder, options, input_class);
1290
0
  if (err) {
1291
0
    return err;
1292
0
  }
1293
1294
0
  insert_image_item(output_image_item->get_id(), output_image_item);
1295
1296
1297
  // --- if there is an alpha channel, add it as an additional image
1298
1299
0
  if (options.save_alpha_channel &&
1300
0
      colorConvertedImage->has_alpha() &&
1301
0
      output_image_item->get_auxC_alpha_channel_type() != nullptr) { // does not need a separate alpha aux image
1302
1303
    // --- generate alpha image
1304
    // TODO: can we directly code a monochrome image instead of the dummy color channels?
1305
1306
0
    std::shared_ptr<HeifPixelImage> alpha_image;
1307
0
    auto alpha_image_result = create_alpha_image_from_image_alpha_channel(colorConvertedImage, get_security_limits());
1308
0
    if (!alpha_image_result) {
1309
0
      return alpha_image_result.error;
1310
0
    }
1311
1312
0
    alpha_image = *alpha_image_result;
1313
1314
1315
    // --- encode the alpha image
1316
1317
0
    auto alphaEncodingResult = encode_image(alpha_image, encoder, options,
1318
0
                         heif_image_input_class_alpha);
1319
0
    if (alphaEncodingResult.error) {
1320
0
      return alphaEncodingResult.error;
1321
0
    }
1322
1323
0
    std::shared_ptr<ImageItem> heif_alpha_image = *alphaEncodingResult;
1324
1325
0
    m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {output_image_item->get_id()});
1326
0
    m_heif_file->set_auxC_property(heif_alpha_image->get_id(), output_image_item->get_auxC_alpha_channel_type());
1327
1328
0
    if (pixel_image->is_premultiplied_alpha()) {
1329
0
      m_heif_file->add_iref_reference(output_image_item->get_id(), fourcc("prem"), {heif_alpha_image->get_id()});
1330
0
    }
1331
0
  }
1332
1333
0
  std::vector<std::shared_ptr<Box>> properties;
1334
0
  err = m_heif_file->get_properties(output_image_item->get_id(), properties);
1335
0
  if (err) {
1336
0
    return err;
1337
0
  }
1338
0
  output_image_item->set_properties(properties);
1339
1340
0
  m_heif_file->set_brand(encoder->plugin->compression_format,
1341
0
                         output_image_item->is_miaf_compatible());
1342
1343
0
  return output_image_item;
1344
0
}
1345
1346
1347
void HeifContext::set_primary_image(const std::shared_ptr<ImageItem>& image)
1348
0
{
1349
  // update heif context
1350
1351
0
  if (m_primary_image) {
1352
0
    m_primary_image->set_primary(false);
1353
0
  }
1354
1355
0
  image->set_primary(true);
1356
0
  m_primary_image = image;
1357
1358
1359
  // update pitm box in HeifFile
1360
1361
0
  m_heif_file->set_primary_item_id(image->get_id());
1362
0
}
1363
1364
1365
Error HeifContext::assign_thumbnail(const std::shared_ptr<ImageItem>& master_image,
1366
                                    const std::shared_ptr<ImageItem>& thumbnail_image)
1367
0
{
1368
0
  m_heif_file->add_iref_reference(thumbnail_image->get_id(),
1369
0
                                  fourcc("thmb"), {master_image->get_id()});
1370
1371
0
  return Error::Ok;
1372
0
}
1373
1374
1375
Result<std::shared_ptr<ImageItem>> HeifContext::encode_thumbnail(const std::shared_ptr<HeifPixelImage>& image,
1376
                                                                 struct heif_encoder* encoder,
1377
                                                                 const struct heif_encoding_options& options,
1378
                                                                 int bbox_size)
1379
0
{
1380
0
  int orig_width = image->get_width();
1381
0
  int orig_height = image->get_height();
1382
1383
0
  int thumb_width, thumb_height;
1384
1385
0
  if (orig_width <= bbox_size && orig_height <= bbox_size) {
1386
    // original image is smaller than thumbnail size -> do not encode any thumbnail
1387
1388
0
    return Error::Ok;
1389
0
  }
1390
0
  else if (orig_width > orig_height) {
1391
0
    thumb_height = orig_height * bbox_size / orig_width;
1392
0
    thumb_width = bbox_size;
1393
0
  }
1394
0
  else {
1395
0
    thumb_width = orig_width * bbox_size / orig_height;
1396
0
    thumb_height = bbox_size;
1397
0
  }
1398
1399
1400
  // round size to even width and height
1401
1402
0
  thumb_width &= ~1;
1403
0
  thumb_height &= ~1;
1404
1405
1406
0
  std::shared_ptr<HeifPixelImage> thumbnail_image;
1407
0
  Error error = image->scale_nearest_neighbor(thumbnail_image, thumb_width, thumb_height, get_security_limits());
1408
0
  if (error) {
1409
0
    return error;
1410
0
  }
1411
1412
0
  auto encodingResult = encode_image(thumbnail_image,
1413
0
                       encoder, options,
1414
0
                       heif_image_input_class_thumbnail);
1415
0
  if (encodingResult.error) {
1416
0
    return encodingResult.error;
1417
0
  }
1418
1419
0
  return *encodingResult;
1420
0
}
1421
1422
1423
Error HeifContext::add_exif_metadata(const std::shared_ptr<ImageItem>& master_image, const void* data, int size)
1424
0
{
1425
  // find location of TIFF header
1426
0
  uint32_t offset = 0;
1427
0
  const char* tiffmagic1 = "MM\0*";
1428
0
  const char* tiffmagic2 = "II*\0";
1429
0
  while (offset + 4 < (unsigned int) size) {
1430
0
    if (!memcmp((uint8_t*) data + offset, tiffmagic1, 4)) break;
1431
0
    if (!memcmp((uint8_t*) data + offset, tiffmagic2, 4)) break;
1432
0
    offset++;
1433
0
  }
1434
0
  if (offset >= (unsigned int) size) {
1435
0
    return Error(heif_error_Usage_error,
1436
0
                 heif_suberror_Invalid_parameter_value,
1437
0
                 "Could not find location of TIFF header in Exif metadata.");
1438
0
  }
1439
1440
1441
0
  std::vector<uint8_t> data_array;
1442
0
  data_array.resize(size + 4);
1443
0
  data_array[0] = (uint8_t) ((offset >> 24) & 0xFF);
1444
0
  data_array[1] = (uint8_t) ((offset >> 16) & 0xFF);
1445
0
  data_array[2] = (uint8_t) ((offset >> 8) & 0xFF);
1446
0
  data_array[3] = (uint8_t) ((offset) & 0xFF);
1447
0
  memcpy(data_array.data() + 4, data, size);
1448
1449
1450
0
  return add_generic_metadata(master_image,
1451
0
                              data_array.data(), (int) data_array.size(),
1452
0
                              fourcc("Exif"), nullptr, nullptr, heif_metadata_compression_off, nullptr);
1453
0
}
1454
1455
1456
Error HeifContext::add_XMP_metadata(const std::shared_ptr<ImageItem>& master_image, const void* data, int size,
1457
                                    heif_metadata_compression compression)
1458
0
{
1459
0
  return add_generic_metadata(master_image, data, size, fourcc("mime"), "application/rdf+xml", nullptr, compression, nullptr);
1460
0
}
1461
1462
1463
Error HeifContext::add_generic_metadata(const std::shared_ptr<ImageItem>& master_image, const void* data, int size,
1464
                                        uint32_t item_type, const char* content_type, const char* item_uri_type, heif_metadata_compression compression,
1465
                                        heif_item_id* out_item_id)
1466
0
{
1467
  // create an infe box describing what kind of data we are storing (this also creates a new ID)
1468
1469
0
  auto metadata_infe_box = m_heif_file->add_new_infe_box(item_type);
1470
0
  metadata_infe_box->set_hidden_item(true);
1471
0
  if (content_type != nullptr) {
1472
0
    metadata_infe_box->set_content_type(content_type);
1473
0
  }
1474
1475
0
  heif_item_id metadata_id = metadata_infe_box->get_item_ID();
1476
0
  if (out_item_id) {
1477
0
    *out_item_id = metadata_id;
1478
0
  }
1479
1480
1481
  // we assign this data to the image
1482
1483
0
  m_heif_file->add_iref_reference(metadata_id,
1484
0
                                  fourcc("cdsc"), {master_image->get_id()});
1485
1486
1487
  // --- metadata compression
1488
1489
0
  if (compression == heif_metadata_compression_auto) {
1490
0
    compression = heif_metadata_compression_off; // currently, we don't use header compression by default
1491
0
  }
1492
1493
  // only set metadata compression for MIME type data which has 'content_encoding' field
1494
0
  if (compression != heif_metadata_compression_off &&
1495
0
      item_type != fourcc("mime")) {
1496
    // TODO: error, compression not supported
1497
0
  }
1498
1499
1500
0
  std::vector<uint8_t> data_array;
1501
0
  if (compression == heif_metadata_compression_zlib) {
1502
#if HAVE_ZLIB
1503
    data_array = compress_zlib((const uint8_t*) data, size);
1504
    metadata_infe_box->set_content_encoding("compress_zlib");
1505
#else
1506
0
    return Error(heif_error_Unsupported_feature,
1507
0
                 heif_suberror_Unsupported_header_compression_method);
1508
0
#endif
1509
0
  }
1510
0
  else if (compression == heif_metadata_compression_deflate) {
1511
#if HAVE_ZLIB
1512
    data_array = compress_zlib((const uint8_t*) data, size);
1513
    metadata_infe_box->set_content_encoding("deflate");
1514
#else
1515
0
    return Error(heif_error_Unsupported_feature,
1516
0
                 heif_suberror_Unsupported_header_compression_method);
1517
0
#endif
1518
0
  }
1519
0
  else {
1520
    // uncompressed data, plain copy
1521
1522
0
    data_array.resize(size);
1523
0
    memcpy(data_array.data(), data, size);
1524
0
  }
1525
1526
  // copy the data into the file, store the pointer to it in an iloc box entry
1527
1528
0
  m_heif_file->append_iloc_data(metadata_id, data_array, 0);
1529
1530
0
  return Error::Ok;
1531
0
}
1532
1533
1534
heif_property_id HeifContext::add_property(heif_item_id targetItem, std::shared_ptr<Box> property, bool essential)
1535
0
{
1536
0
  heif_property_id id = m_heif_file->add_property(targetItem, property, essential);
1537
1538
0
  return id;
1539
0
}
1540
1541
1542
Result<heif_item_id> HeifContext::add_pyramid_group(const std::vector<heif_item_id>& layer_item_ids)
1543
0
{
1544
0
  struct pymd_entry
1545
0
  {
1546
0
    std::shared_ptr<ImageItem> item;
1547
0
    uint32_t width = 0;
1548
0
  };
1549
1550
  // --- sort all images by size
1551
1552
0
  std::vector<pymd_entry> pymd_entries;
1553
0
  for (auto id : layer_item_ids) {
1554
0
    auto image_item = get_image(id, true);
1555
0
    if (auto error = image_item->get_item_error()) {
1556
0
      return error;
1557
0
    }
1558
1559
0
    pymd_entry entry;
1560
0
    entry.item = image_item;
1561
0
    entry.width = image_item->get_width();
1562
0
    pymd_entries.emplace_back(entry);
1563
0
  }
1564
1565
0
  std::sort(pymd_entries.begin(), pymd_entries.end(), [](const pymd_entry& a, const pymd_entry& b) {
1566
0
    return a.width < b.width;
1567
0
  });
1568
1569
1570
  // --- generate pymd box
1571
1572
0
  auto pymd = std::make_shared<Box_pymd>();
1573
0
  std::vector<Box_pymd::LayerInfo> layers;
1574
0
  std::vector<heif_item_id> ids;
1575
1576
0
  auto base_item = pymd_entries.back().item;
1577
1578
0
  uint32_t tile_w=0, tile_h=0;
1579
0
  base_item->get_tile_size(tile_w, tile_h);
1580
1581
0
  uint32_t last_width=0, last_height=0;
1582
1583
0
  for (const auto& entry : pymd_entries) {
1584
0
    auto layer_item = entry.item;
1585
1586
0
    if (false) {
1587
      // according to pymd definition, we should check that all layers have the same tile size
1588
0
      uint32_t item_tile_w = 0, item_tile_h = 0;
1589
0
      base_item->get_tile_size(item_tile_w, item_tile_h);
1590
0
      if (item_tile_w != tile_w || item_tile_h != tile_h) {
1591
        // TODO: add warning that tile sizes are not the same
1592
0
      }
1593
0
    }
1594
1595
0
    heif_image_tiling tiling = layer_item->get_heif_image_tiling();
1596
1597
0
    if (tiling.image_width < last_width || tiling.image_height < last_height) {
1598
0
      return Error{
1599
0
        heif_error_Invalid_input,
1600
0
        heif_suberror_Invalid_parameter_value,
1601
0
        "Multi-resolution pyramid images have to be provided ordered from smallest to largest."
1602
0
      };
1603
0
    }
1604
1605
0
    last_width = tiling.image_width;
1606
0
    last_height = tiling.image_height;
1607
1608
0
    Box_pymd::LayerInfo layer{};
1609
0
    layer.layer_binning = (uint16_t)(base_item->get_width() / tiling.image_width);
1610
0
    layer.tiles_in_layer_row_minus1 = static_cast<uint16_t>(tiling.num_rows - 1);
1611
0
    layer.tiles_in_layer_column_minus1 = static_cast<uint16_t>(tiling.num_columns - 1);
1612
0
    layers.push_back(layer);
1613
0
    ids.push_back(layer_item->get_id());
1614
0
  }
1615
1616
0
  heif_item_id group_id = m_heif_file->get_unused_item_id();
1617
1618
0
  pymd->set_group_id(group_id);
1619
0
  pymd->set_layers((uint16_t)tile_w, (uint16_t)tile_h, layers, ids);
1620
1621
0
  m_heif_file->add_entity_group_box(pymd);
1622
1623
  // add back-references to base image
1624
1625
0
  for (size_t i = 0; i < ids.size() - 1; i++) {
1626
0
    m_heif_file->add_iref_reference(ids[i], fourcc("base"), {ids.back()});
1627
0
  }
1628
1629
0
  return {group_id};
1630
0
}