Coverage Report

Created: 2025-08-23 07:06

/src/libheif/libheif/file.cc
Line
Count
Source (jump to first uncovered line)
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 "file.h"
22
#include "box.h"
23
#include "libheif/heif.h"
24
#include "libheif/heif_properties.h"
25
#include "compression.h"
26
#include "image-items/jpeg2000.h"
27
#include "image-items/jpeg.h"
28
#include "image-items/vvc.h"
29
#include "codecs/avif_boxes.h"
30
#include "codecs/hevc_boxes.h"
31
#include "sequences/seq_boxes.h"
32
33
#include <cstdint>
34
#include <fstream>
35
#include <limits>
36
#include <sstream>
37
#include <utility>
38
#include <cstring>
39
#include <cassert>
40
#include <algorithm>
41
42
#include "libheif/heif_cxx.h"
43
44
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
45
46
#ifndef NOMINMAX
47
#define NOMINMAX 1
48
#endif
49
50
#include <windows.h>
51
#endif
52
53
54
#if WITH_UNCOMPRESSED_CODEC
55
#include "image-items/unc_image.h"
56
#endif
57
58
// TODO: make this a decoder option
59
680
#define STRICT_PARSING false
60
61
62
HeifFile::HeifFile()
63
1.27k
{
64
1.27k
  m_file_layout = std::make_shared<FileLayout>();
65
1.27k
}
66
67
1.27k
HeifFile::~HeifFile() = default;
68
69
std::vector<heif_item_id> HeifFile::get_item_IDs() const
70
313
{
71
313
  std::vector<heif_item_id> IDs;
72
73
4.06k
  for (const auto& infe : m_infe_boxes) {
74
4.06k
    IDs.push_back(infe.second->get_item_ID());
75
4.06k
  }
76
77
313
  return IDs;
78
313
}
79
80
81
std::shared_ptr<const Box_infe> HeifFile::get_infe_box(heif_item_id ID) const
82
14.4k
{
83
14.4k
  auto iter = m_infe_boxes.find(ID);
84
14.4k
  if (iter == m_infe_boxes.end()) {
85
43
    return nullptr;
86
43
  }
87
88
14.4k
  return iter->second;
89
14.4k
}
90
91
92
std::shared_ptr<Box_infe> HeifFile::get_infe_box(heif_item_id ID)
93
5.13k
{
94
5.13k
  auto iter = m_infe_boxes.find(ID);
95
5.13k
  if (iter == m_infe_boxes.end()) {
96
0
    return nullptr;
97
0
  }
98
99
5.13k
  return iter->second;
100
5.13k
}
101
102
103
Error HeifFile::read_from_file(const char* input_filename)
104
0
{
105
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
106
  auto input_stream_istr = std::unique_ptr<std::istream>(new std::ifstream(convert_utf8_path_to_utf16(input_filename).c_str(), std::ios_base::binary));
107
#else
108
0
  auto input_stream_istr = std::unique_ptr<std::istream>(new std::ifstream(input_filename, std::ios_base::binary));
109
0
#endif
110
0
  if (!input_stream_istr->good()) {
111
0
    std::stringstream sstr;
112
0
    sstr << "Error opening file: " << strerror(errno) << " (" << errno << ")\n";
113
0
    return Error(heif_error_Input_does_not_exist, heif_suberror_Unspecified, sstr.str());
114
0
  }
115
116
0
  auto input_stream = std::make_shared<StreamReader_istream>(std::move(input_stream_istr));
117
0
  return read(input_stream);
118
0
}
119
120
121
Error HeifFile::read_from_memory(const void* data, size_t size, bool copy)
122
637
{
123
637
  auto input_stream = std::make_shared<StreamReader_memory>((const uint8_t*) data, size, copy);
124
125
637
  return read(input_stream);
126
637
}
127
128
129
Error HeifFile::read(const std::shared_ptr<StreamReader>& reader)
130
637
{
131
637
  assert(m_limits);
132
133
637
  m_input_stream = reader;
134
135
637
  Error err;
136
637
  err = m_file_layout->read(reader, m_limits);
137
637
  if (err) {
138
165
    return err;
139
165
  }
140
141
472
  Error error = parse_heif_file();
142
472
  return error;
143
637
}
144
145
146
void HeifFile::new_empty_file()
147
637
{
148
  //m_input_stream.reset();
149
637
  m_top_level_boxes.clear();
150
151
637
  m_ftyp_box = std::make_shared<Box_ftyp>();
152
637
  m_top_level_boxes.push_back(m_ftyp_box);
153
637
}
154
155
156
void HeifFile::init_for_image()
157
0
{
158
0
  if (m_meta_box) {
159
0
    return;
160
0
  }
161
162
0
  m_hdlr_box = std::make_shared<Box_hdlr>();
163
0
  m_meta_box = std::make_shared<Box_meta>();
164
0
  m_ipco_box = std::make_shared<Box_ipco>();
165
0
  m_ipma_box = std::make_shared<Box_ipma>();
166
0
  m_iloc_box = std::make_shared<Box_iloc>();
167
0
  m_iinf_box = std::make_shared<Box_iinf>();
168
0
  m_iprp_box = std::make_shared<Box_iprp>();
169
0
  m_pitm_box = std::make_shared<Box_pitm>();
170
171
0
  m_meta_box->append_child_box(m_hdlr_box);
172
0
  m_meta_box->append_child_box(m_pitm_box);
173
0
  m_meta_box->append_child_box(m_iloc_box);
174
0
  m_meta_box->append_child_box(m_iinf_box);
175
0
  m_meta_box->append_child_box(m_iprp_box);
176
177
0
  m_iprp_box->append_child_box(m_ipco_box);
178
0
  m_iprp_box->append_child_box(m_ipma_box);
179
180
0
  m_infe_boxes.clear();
181
182
0
  m_top_level_boxes.push_back(m_meta_box);
183
#if ENABLE_EXPERIMENTAL_MINI_FORMAT
184
  // TODO: do not create 'mini' box as we cannot write them yet.
185
  //       if we include it in the top_level_boxes, it will be written into every file.
186
  //m_mini_box = std::make_shared<Box_mini>();
187
  //m_top_level_boxes.push_back(m_mini_box);
188
#endif
189
0
}
190
191
192
void HeifFile::init_for_sequence()
193
0
{
194
0
  if (m_moov_box) {
195
0
    return;
196
0
  }
197
198
0
  m_moov_box = std::make_shared<Box_moov>();
199
0
  m_top_level_boxes.push_back(m_moov_box);
200
201
0
  m_mvhd_box = std::make_shared<Box_mvhd>();
202
0
  m_moov_box->append_child_box(m_mvhd_box);
203
0
}
204
205
206
size_t HeifFile::append_mdat_data(const std::vector<uint8_t>& data)
207
0
{
208
0
  if (!m_mdat_data) {
209
0
    m_mdat_data = std::make_unique<MdatData_Memory>();
210
0
  }
211
212
0
  return m_mdat_data->append_data(data);
213
0
}
214
215
216
void HeifFile::derive_box_versions()
217
0
{
218
0
  for (auto& box : m_top_level_boxes) {
219
0
    box->derive_box_version_recursive();
220
0
  }
221
0
}
222
223
224
void HeifFile::write(StreamWriter& writer)
225
0
{
226
0
  for (auto& box : m_top_level_boxes) {
227
#if ENABLE_EXPERIMENTAL_MINI_FORMAT
228
    if (box == nullptr) {
229
      // Either mini or meta will be null, just ignore that one
230
      continue;
231
    }
232
#endif
233
0
    Error err = box->write(writer);
234
0
    (void)err; // TODO: error ?
235
0
  }
236
237
0
  if (m_iloc_box) {
238
    // TODO: rewrite to use MdatData class
239
0
    Error err = m_iloc_box->write_mdat_after_iloc(writer);
240
0
    (void)err; // TODO: error ?
241
0
  }
242
243
0
  if (m_mdat_data) {
244
0
    Result<size_t> mdatResult = write_mdat(writer);
245
0
    if (!mdatResult) {
246
0
      return; // TODO: error ?
247
0
    }
248
249
0
    for (auto& box : m_top_level_boxes) {
250
0
      box->patch_file_pointers_recursively(writer, *mdatResult);
251
0
    }
252
0
  }
253
0
}
254
255
256
std::string HeifFile::debug_dump_boxes() const
257
0
{
258
0
  std::stringstream sstr;
259
260
0
  bool first = true;
261
262
0
  for (const auto& box : m_top_level_boxes) {
263
#if ENABLE_EXPERIMENTAL_MINI_FORMAT
264
    if (box == nullptr) {
265
      // Either mini or meta will be null, just ignore that one
266
      continue;
267
    }
268
#endif
269
    // dump box content for debugging
270
271
0
    if (first) {
272
0
      first = false;
273
0
    }
274
0
    else {
275
0
      sstr << "\n";
276
0
    }
277
278
0
    Indent indent;
279
0
    sstr << box->dump(indent);
280
0
  }
281
282
0
  return sstr.str();
283
0
}
284
285
286
Error HeifFile::parse_heif_file()
287
472
{
288
  // --- read all top-level boxes
289
290
#if 0
291
  for (;;) {
292
    std::shared_ptr<Box> box;
293
    Error error = Box::read(range, &box);
294
295
    if (range.error() || range.eof()) {
296
      break;
297
    }
298
299
    // When an EOF error is returned, this is not really a fatal exception,
300
    // but simply the indication that we reached the end of the file.
301
    // TODO: this design should be cleaned up
302
    if (error.error_code == heif_error_Invalid_input && error.sub_error_code == heif_suberror_End_of_data) {
303
      break;
304
    }
305
306
    if (error != Error::Ok) {
307
      return error;
308
    }
309
310
    m_top_level_boxes.push_back(box);
311
312
313
    // extract relevant boxes (ftyp, meta)
314
315
    if (box->get_short_type() == fourcc("meta")) {
316
      m_meta_box = std::dynamic_pointer_cast<Box_meta>(box);
317
    }
318
319
    if (box->get_short_type() == fourcc("ftyp")) {
320
      m_ftyp_box = std::dynamic_pointer_cast<Box_ftyp>(box);
321
    }
322
  }
323
#endif
324
325
472
  m_ftyp_box = m_file_layout->get_ftyp_box();
326
472
  if (!m_ftyp_box) {
327
119
    return Error(heif_error_Invalid_input,
328
119
                 heif_suberror_No_ftyp_box);
329
119
  }
330
331
353
  m_top_level_boxes.push_back(m_ftyp_box);
332
333
353
  bool is_brand_msf1 = m_ftyp_box->has_compatible_brand(heif_brand2_msf1);
334
335
  // --- check whether this is a HEIF file and its structural format
336
337
353
  if (!m_ftyp_box->has_compatible_brand(heif_brand2_heic) &&
338
353
      !m_ftyp_box->has_compatible_brand(heif_brand2_heix) &&
339
353
      !m_ftyp_box->has_compatible_brand(heif_brand2_mif1) &&
340
353
      !m_ftyp_box->has_compatible_brand(heif_brand2_avif) &&
341
353
      !m_ftyp_box->has_compatible_brand(heif_brand2_1pic) &&
342
#if ENABLE_EXPERIMENTAL_MINI_FORMAT
343
      !(m_ftyp_box->get_major_brand() == heif_brand2_mif3) &&
344
#endif
345
353
      !m_ftyp_box->has_compatible_brand(heif_brand2_jpeg) &&
346
353
      !is_brand_msf1) {
347
13
    std::stringstream sstr;
348
13
    sstr << "File does not include any supported brands.\n";
349
350
13
    return Error(heif_error_Unsupported_filetype,
351
13
                 heif_suberror_Unspecified,
352
13
                 sstr.str());
353
13
      }
354
355
#if ENABLE_EXPERIMENTAL_MINI_FORMAT
356
  m_mini_box = m_file_layout->get_mini_box();
357
  m_top_level_boxes.push_back(m_mini_box);
358
359
  if (m_mini_box) {
360
    Error err = m_mini_box->create_expanded_boxes(this);
361
    if (err) {
362
      return err;
363
    }
364
    return Error::Ok;
365
  }
366
#endif
367
368
340
  m_meta_box = m_file_layout->get_meta_box();
369
340
  if (m_meta_box) {
370
340
    m_top_level_boxes.push_back(m_meta_box);
371
340
  }
372
373
  // TODO: we are missing 'mdat' top level boxes
374
375
340
  m_moov_box = m_file_layout->get_moov_box();
376
340
  if (m_moov_box) {
377
0
    m_top_level_boxes.push_back(m_moov_box);
378
0
  }
379
380
  // if we didn't find the mini box, meta is required
381
382
340
  if (!m_meta_box && !is_brand_msf1) {
383
0
    return Error(heif_error_Invalid_input,
384
0
                 heif_suberror_No_meta_box);
385
0
  }
386
387
340
  if (!m_moov_box && is_brand_msf1) {
388
0
    return Error(heif_error_Invalid_input,
389
0
                 heif_suberror_No_moov_box);
390
0
  }
391
392
340
  if (m_meta_box) {
393
340
    auto err = parse_heif_images();
394
340
    if (err) {
395
27
      return err;
396
27
    }
397
340
  }
398
399
313
  if (m_moov_box) {
400
0
    auto err = parse_heif_sequences();
401
0
    if (err) {
402
0
      return err;
403
0
    }
404
0
  }
405
406
313
  return Error::Ok;
407
313
}
408
409
410
Error HeifFile::parse_heif_images()
411
340
{
412
340
  m_hdlr_box = m_meta_box->get_child_box<Box_hdlr>();
413
340
  if (STRICT_PARSING && !m_hdlr_box) {
414
0
    return Error(heif_error_Invalid_input,
415
0
                 heif_suberror_No_hdlr_box);
416
0
  }
417
418
340
  if (m_hdlr_box &&
419
340
      m_hdlr_box->get_handler_type() != fourcc("pict")) {
420
0
    return Error(heif_error_Invalid_input,
421
0
                 heif_suberror_No_pict_handler);
422
0
  }
423
424
425
  // --- find mandatory boxes needed for image decoding
426
427
340
  m_pitm_box = m_meta_box->get_child_box<Box_pitm>();
428
340
  if (!m_pitm_box) {
429
4
    return Error(heif_error_Invalid_input,
430
4
                 heif_suberror_No_pitm_box);
431
4
  }
432
433
336
  m_iprp_box = m_meta_box->get_child_box<Box_iprp>();
434
336
  if (!m_iprp_box) {
435
4
    return Error(heif_error_Invalid_input,
436
4
                 heif_suberror_No_iprp_box);
437
4
  }
438
439
332
  m_ipco_box = m_iprp_box->get_child_box<Box_ipco>();
440
332
  if (!m_ipco_box) {
441
1
    return Error(heif_error_Invalid_input,
442
1
                 heif_suberror_No_ipco_box);
443
1
  }
444
445
331
  auto ipma_boxes = m_iprp_box->get_child_boxes<Box_ipma>();
446
331
  if (ipma_boxes.empty()) {
447
0
    return Error(heif_error_Invalid_input,
448
0
                 heif_suberror_No_ipma_box);
449
0
  }
450
385
  for (size_t i=1;i<ipma_boxes.size();i++) {
451
54
    ipma_boxes[0]->insert_entries_from_other_ipma_box(*ipma_boxes[i]);
452
54
  }
453
331
  m_ipma_box = ipma_boxes[0];
454
455
331
  m_iloc_box = m_meta_box->get_child_box<Box_iloc>();
456
331
  if (!m_iloc_box) {
457
1
    return Error(heif_error_Invalid_input,
458
1
                 heif_suberror_No_iloc_box);
459
1
  }
460
461
330
  m_idat_box = m_meta_box->get_child_box<Box_idat>();
462
463
330
  m_iref_box = m_meta_box->get_child_box<Box_iref>();
464
330
  if (m_iref_box) {
465
188
    Error error = check_for_ref_cycle(get_primary_image_ID(), m_iref_box);
466
188
    if (error) {
467
3
      return error;
468
3
    }
469
188
  }
470
471
327
  m_iinf_box = m_meta_box->get_child_box<Box_iinf>();
472
327
  if (!m_iinf_box) {
473
14
    return Error(heif_error_Invalid_input,
474
14
                 heif_suberror_No_iinf_box);
475
14
  }
476
477
313
  m_grpl_box = m_meta_box->get_child_box<Box_grpl>();
478
479
480
  // --- build list of images
481
482
313
  std::vector<std::shared_ptr<Box_infe>> infe_boxes = m_iinf_box->get_child_boxes<Box_infe>();
483
484
4.27k
  for (auto& infe_box : infe_boxes) {
485
4.27k
    if (!infe_box) {
486
0
      return Error(heif_error_Invalid_input,
487
0
                   heif_suberror_No_infe_box);
488
0
    }
489
490
4.27k
    m_infe_boxes.insert(std::make_pair(infe_box->get_item_ID(), infe_box));
491
4.27k
  }
492
493
313
  return Error::Ok;
494
313
}
495
496
497
Error HeifFile::parse_heif_sequences()
498
0
{
499
0
  m_mvhd_box = m_moov_box->get_child_box<Box_mvhd>();
500
0
  if (!m_mvhd_box) {
501
0
    return {heif_error_Invalid_input,
502
0
            heif_suberror_Unspecified,
503
0
            "No mvhd box in image sequence."};
504
0
  }
505
506
0
  return Error::Ok;
507
0
}
508
509
510
Error HeifFile::check_for_ref_cycle(heif_item_id ID,
511
                                    const std::shared_ptr<Box_iref>& iref_box) const
512
188
{
513
188
  std::unordered_set<heif_item_id> parent_items;
514
188
  return check_for_ref_cycle_recursion(ID, iref_box, parent_items);
515
188
}
516
517
518
Error HeifFile::check_for_ref_cycle_recursion(heif_item_id ID,
519
                                    const std::shared_ptr<Box_iref>& iref_box,
520
1.93k
                                    std::unordered_set<heif_item_id>& parent_items) const {
521
1.93k
  if (parent_items.find(ID) != parent_items.end()) {
522
3
    return Error(heif_error_Invalid_input,
523
3
                 heif_suberror_Item_reference_cycle,
524
3
                 "Image reference cycle");
525
3
  }
526
1.93k
  parent_items.insert(ID);
527
528
1.93k
  std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg"));
529
1.93k
  for (heif_item_id reference_idx : image_references) {
530
1.74k
    Error error = check_for_ref_cycle_recursion(reference_idx, iref_box, parent_items);
531
1.74k
    if (error) {
532
9
      return error;
533
9
    }
534
1.74k
  }
535
536
1.92k
  parent_items.erase(ID);
537
1.92k
  return Error::Ok;
538
1.93k
}
539
540
541
bool HeifFile::item_exists(heif_item_id ID) const
542
2.21k
{
543
2.21k
  auto image_iter = m_infe_boxes.find(ID);
544
2.21k
  return image_iter != m_infe_boxes.end();
545
2.21k
}
546
547
548
bool HeifFile::has_item_with_id(heif_item_id ID) const
549
137
{
550
137
  auto infe_box = get_infe_box(ID);
551
137
  return infe_box != nullptr;
552
137
}
553
554
555
uint32_t HeifFile::get_item_type_4cc(heif_item_id ID) const
556
7.57k
{
557
7.57k
  auto infe_box = get_infe_box(ID);
558
7.57k
  if (!infe_box) {
559
0
    return 0;
560
0
  }
561
562
7.57k
  return infe_box->get_item_type_4cc();
563
7.57k
}
564
565
566
std::string HeifFile::get_content_type(heif_item_id ID) const
567
2.78k
{
568
2.78k
  auto infe_box = get_infe_box(ID);
569
2.78k
  if (!infe_box) {
570
0
    return "";
571
0
  }
572
573
2.78k
  return infe_box->get_content_type();
574
2.78k
}
575
576
std::string HeifFile::get_item_uri_type(heif_item_id ID) const
577
1.77k
{
578
1.77k
  auto infe_box = get_infe_box(ID);
579
1.77k
  if (!infe_box) {
580
0
    return "";
581
0
  }
582
583
1.77k
  return infe_box->get_item_uri_type();
584
1.77k
}
585
586
587
Error HeifFile::get_properties(heif_item_id imageID,
588
                               std::vector<std::shared_ptr<Box>>& properties) const
589
2.30k
{
590
2.30k
  if (!m_ipco_box) {
591
0
    return Error(heif_error_Invalid_input,
592
0
                 heif_suberror_No_ipco_box);
593
0
  }
594
2.30k
  else if (!m_ipma_box) {
595
0
    return Error(heif_error_Invalid_input,
596
0
                 heif_suberror_No_ipma_box);
597
0
  }
598
599
2.30k
  return m_ipco_box->get_properties_for_item_ID(imageID, m_ipma_box, properties);
600
2.30k
}
601
602
603
Result<std::vector<uint8_t>> HeifFile::get_uncompressed_item_data(heif_item_id ID) const
604
2.21k
{
605
2.21k
  assert(m_limits);
606
607
2.21k
#if ENABLE_PARALLEL_TILE_DECODING
608
  // std::lock_guard<std::mutex> guard(m_read_mutex);   // TODO: I think that this is not needed anymore because this function is not used for image data anymore.
609
2.21k
#endif
610
611
2.21k
  if (!item_exists(ID)) {
612
0
    return Error(heif_error_Usage_error,
613
0
                 heif_suberror_Nonexisting_item_referenced);
614
0
  }
615
616
2.21k
  auto infe_box = get_infe_box(ID);
617
2.21k
  if (!infe_box) {
618
0
    return Error(heif_error_Usage_error,
619
0
                 heif_suberror_Nonexisting_item_referenced);
620
0
  }
621
622
623
2.21k
  uint32_t item_type = infe_box->get_item_type_4cc();
624
2.21k
  std::string content_type = infe_box->get_content_type();
625
626
  // --- decompress data
627
628
2.21k
  Error error;
629
630
2.21k
  if (item_type == fourcc("mime")) {
631
2
    std::string encoding = infe_box->get_content_encoding();
632
2
    if (encoding == "compress_zlib") {
633
0
#if HAVE_ZLIB
634
0
      std::vector<uint8_t> compressed_data;
635
0
      error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits);
636
0
      if (error) {
637
0
        return error;
638
0
      }
639
640
0
      return decompress_zlib(compressed_data);
641
#else
642
      return Error(heif_error_Unsupported_feature,
643
                   heif_suberror_Unsupported_header_compression_method,
644
                   encoding);
645
#endif
646
0
    }
647
2
    else if (encoding == "deflate") {
648
0
#if HAVE_ZLIB
649
0
      std::vector<uint8_t> compressed_data;
650
0
      error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits);
651
0
      if (error) {
652
0
        return error;
653
0
      }
654
0
      return decompress_deflate(compressed_data);
655
#else
656
      return Error(heif_error_Unsupported_feature,
657
                   heif_suberror_Unsupported_header_compression_method,
658
                   encoding);
659
#endif
660
0
    }
661
2
    else if (encoding == "br") {
662
0
#if HAVE_BROTLI
663
0
      std::vector<uint8_t> compressed_data;
664
0
      error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits);
665
0
      if (error) {
666
0
        return error;
667
0
      }
668
0
      return decompress_brotli(compressed_data);
669
#else
670
      return Error(heif_error_Unsupported_feature,
671
                   heif_suberror_Unsupported_header_compression_method,
672
                   encoding);
673
#endif
674
0
    }
675
2
    else if (!encoding.empty()) {
676
0
      return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec);
677
0
    }
678
2
  }
679
680
681
  // --- read uncompressed
682
683
2.21k
  std::vector<uint8_t> data;
684
2.21k
  error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &data, m_limits);
685
2.21k
  if (error) {
686
1.66k
    return error;
687
1.66k
  }
688
546
  else {
689
546
    return data;
690
546
  }
691
2.21k
}
692
693
694
Error HeifFile::append_data_from_file_range(std::vector<uint8_t>& out_data, uint64_t offset, uint32_t size) const
695
0
{
696
0
  bool success = m_input_stream->seek(offset);
697
0
  if (!success) {
698
    // TODO: error
699
0
  }
700
701
0
  auto old_size = out_data.size();
702
0
  out_data.resize(old_size + size);
703
704
0
  success = m_input_stream->read(out_data.data() + old_size, size);
705
0
  if (!success) {
706
    // TODO: error
707
0
  }
708
709
0
  return {};
710
0
}
711
712
713
Error HeifFile::append_data_from_iloc(heif_item_id ID, std::vector<uint8_t>& out_data, uint64_t offset, uint64_t size) const
714
49
{
715
49
  const auto& items = m_iloc_box->get_items();
716
49
  const Box_iloc::Item* item = nullptr;
717
223
  for (const auto& i : items) {
718
223
    if (i.item_ID == ID) {
719
44
      item = &i;
720
44
      break;
721
44
    }
722
223
  }
723
49
  if (!item) {
724
5
    std::stringstream sstr;
725
5
    sstr << "Item with ID " << ID << " has no compressed data";
726
727
5
    return {heif_error_Invalid_input,
728
5
            heif_suberror_No_item_data,
729
5
            sstr.str()};
730
5
  }
731
732
44
  return m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &out_data, offset, size, m_limits);
733
49
}
734
735
736
Result<std::vector<uint8_t>> HeifFile::get_item_data(heif_item_id ID, heif_metadata_compression* out_compression) const
737
0
{
738
0
  Error error;
739
740
0
  assert(m_limits);
741
742
0
  auto infe_box = get_infe_box(ID);
743
0
  if (!infe_box) {
744
0
    return Error{heif_error_Usage_error,
745
0
                 heif_suberror_Nonexisting_item_referenced};
746
0
  }
747
748
0
  uint32_t item_type = infe_box->get_item_type_4cc();
749
0
  std::string content_type = infe_box->get_content_type();
750
751
  // --- non 'mime' data (uncompressed)
752
753
0
  if (item_type != fourcc("mime")) {
754
0
    if (out_compression) {
755
0
      *out_compression = heif_metadata_compression_off;
756
0
    }
757
758
0
    std::vector<uint8_t> out_data;
759
0
    Error err = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &out_data, m_limits);
760
0
    if (err) {
761
0
      return err;
762
0
    }
763
0
    else {
764
0
      return out_data;
765
0
    }
766
0
  }
767
768
769
  // --- mime data
770
771
0
  std::string encoding = infe_box->get_content_encoding();
772
773
0
  heif_metadata_compression compression;
774
775
0
  if (encoding.empty()) {
776
    // shortcut for case of uncompressed mime data
777
778
0
    if (out_compression) {
779
0
      *out_compression = heif_metadata_compression_off;
780
0
    }
781
782
0
    std::vector<uint8_t> out_data;
783
0
    Error err = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &out_data, m_limits);
784
0
    if (err) {
785
0
      return err;
786
0
    }
787
0
    else {
788
0
      return out_data;
789
0
    }
790
0
  }
791
0
  else if (encoding == "compress_zlib") {
792
0
    compression = heif_metadata_compression_zlib;
793
0
  }
794
0
  else if (encoding == "deflate") {
795
0
    compression = heif_metadata_compression_deflate;
796
0
  }
797
0
  else if (encoding == "br") {
798
0
    compression = heif_metadata_compression_brotli;
799
0
  }
800
0
  else {
801
0
    compression = heif_metadata_compression_unknown;
802
0
  }
803
804
  // read compressed data
805
806
0
  std::vector<uint8_t> compressed_data;
807
0
  error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits);
808
0
  if (error) {
809
0
    return error;
810
0
  }
811
812
  // return compressed data, if we do not want to have it uncompressed
813
814
0
  const bool return_compressed = (out_compression != nullptr);
815
0
  if (return_compressed) {
816
0
    *out_compression = compression;
817
0
    return compressed_data;
818
0
  }
819
820
  // decompress the data
821
822
0
  switch (compression) {
823
0
#if HAVE_ZLIB
824
0
    case heif_metadata_compression_zlib:
825
0
      return decompress_zlib(compressed_data);
826
0
    case heif_metadata_compression_deflate:
827
0
      return decompress_deflate(compressed_data);
828
0
#endif
829
0
#if HAVE_BROTLI
830
0
    case heif_metadata_compression_brotli:
831
0
      return decompress_brotli(compressed_data);
832
0
#endif
833
0
    default:
834
0
      return Error{heif_error_Unsupported_filetype, heif_suberror_Unsupported_header_compression_method};
835
0
  }
836
0
}
837
838
839
// TODO: we should use a acquire() / release() approach here so that we can get multiple IDs before actually creating infe boxes
840
heif_item_id HeifFile::get_unused_item_id() const
841
0
{
842
0
  heif_item_id max_id = 0;
843
844
  // TODO: replace with better algorithm and data-structure
845
846
0
  for (const auto& infe : m_infe_boxes) {
847
0
    max_id = std::max(max_id, infe.second->get_item_ID());
848
0
  }
849
850
0
  assert(max_id != 0xFFFFFFFF);
851
852
0
  return max_id + 1;
853
0
}
854
855
856
heif_item_id HeifFile::add_new_image(uint32_t item_type)
857
0
{
858
0
  auto box = add_new_infe_box(item_type);
859
0
  return box->get_item_ID();
860
0
}
861
862
863
std::shared_ptr<Box_infe> HeifFile::add_new_infe_box(uint32_t item_type)
864
0
{
865
0
  init_for_image();
866
867
0
  heif_item_id id = get_unused_item_id();
868
869
0
  auto infe = std::make_shared<Box_infe>();
870
0
  infe->set_item_ID(id);
871
0
  infe->set_hidden_item(false);
872
0
  infe->set_item_type_4cc(item_type);
873
874
0
  m_infe_boxes[id] = infe;
875
0
  m_iinf_box->append_child_box(infe);
876
877
0
  return infe;
878
0
}
879
880
881
void HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t height, bool essential)
882
0
{
883
0
  auto ispe = std::make_shared<Box_ispe>();
884
0
  ispe->set_size(width, height);
885
886
0
  uint32_t index = m_ipco_box->find_or_append_child_box(ispe);
887
888
0
  m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
889
0
}
890
891
892
893
heif_property_id HeifFile::add_property(heif_item_id id, const std::shared_ptr<Box>& property, bool essential)
894
0
{
895
0
  uint32_t index = m_ipco_box->find_or_append_child_box(property);
896
897
0
  m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
898
899
0
  return index + 1;
900
0
}
901
902
903
heif_property_id HeifFile::add_property_without_deduplication(heif_item_id id, const std::shared_ptr<Box>& property, bool essential)
904
0
{
905
0
  uint32_t index = m_ipco_box->append_child_box(property);
906
907
0
  m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)});
908
909
0
  return index + 1;
910
0
}
911
912
913
void HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orientation)
914
0
{
915
  // Note: ISO/IEC 23000-22:2019(E) (MIAF) 7.3.6.7 requires the following order:
916
  // clean aperture first, then rotation, then mirror
917
918
0
  int rotation_ccw = 0;
919
0
  heif_transform_mirror_direction mirror;
920
0
  bool has_mirror = false;
921
922
0
  switch (orientation) {
923
0
    case heif_orientation_normal:
924
0
      break;
925
0
    case heif_orientation_flip_horizontally:
926
0
      mirror = heif_transform_mirror_direction_horizontal;
927
0
      has_mirror = true;
928
0
      break;
929
0
    case heif_orientation_rotate_180:
930
0
      rotation_ccw = 180;
931
0
      break;
932
0
    case heif_orientation_flip_vertically:
933
0
      mirror = heif_transform_mirror_direction_vertical;
934
0
      has_mirror = true;
935
0
      break;
936
0
    case heif_orientation_rotate_90_cw_then_flip_horizontally:
937
0
      rotation_ccw = 270;
938
0
      mirror = heif_transform_mirror_direction_horizontal;
939
0
      has_mirror = true;
940
0
      break;
941
0
    case heif_orientation_rotate_90_cw:
942
0
      rotation_ccw = 270;
943
0
      break;
944
0
    case heif_orientation_rotate_90_cw_then_flip_vertically:
945
0
      rotation_ccw = 270;
946
0
      mirror = heif_transform_mirror_direction_vertical;
947
0
      has_mirror = true;
948
0
      break;
949
0
    case heif_orientation_rotate_270_cw:
950
0
      rotation_ccw = 90;
951
0
      break;
952
0
  }
953
954
  // omit rotation when angle is 0
955
0
  if (rotation_ccw!=0) {
956
0
    auto irot = std::make_shared<Box_irot>();
957
0
    irot->set_rotation_ccw(rotation_ccw);
958
959
0
    uint32_t index = m_ipco_box->find_or_append_child_box(irot);
960
961
0
    m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
962
0
  }
963
964
0
  if (has_mirror) {
965
0
    auto imir = std::make_shared<Box_imir>();
966
0
    imir->set_mirror_direction(mirror);
967
968
0
    uint32_t index = m_ipco_box->find_or_append_child_box(imir);
969
970
0
    m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
971
0
  }
972
0
}
973
974
975
Result<heif_item_id> HeifFile::add_infe(uint32_t item_type, const uint8_t* data, size_t size)
976
0
{
977
  // create an infe box describing what kind of data we are storing (this also creates a new ID)
978
979
0
  auto infe_box = add_new_infe_box(item_type);
980
0
  infe_box->set_hidden_item(true);
981
982
0
  heif_item_id metadata_id = infe_box->get_item_ID();
983
984
0
  set_item_data(infe_box, data, size, heif_metadata_compression_off);
985
986
0
  return metadata_id;
987
0
}
988
989
990
void HeifFile::add_infe_box(heif_item_id id, std::shared_ptr<Box_infe> infe)
991
0
{
992
0
  m_infe_boxes.insert(std::make_pair(id, std::move(infe)));
993
0
}
994
995
996
Result<heif_item_id> HeifFile::add_infe_mime(const char* content_type, heif_metadata_compression content_encoding, const uint8_t* data, size_t size)
997
0
{
998
  // create an infe box describing what kind of data we are storing (this also creates a new ID)
999
1000
0
  auto infe_box = add_new_infe_box(fourcc("mime"));
1001
0
  infe_box->set_hidden_item(true);
1002
0
  infe_box->set_content_type(content_type);
1003
1004
0
  heif_item_id metadata_id = infe_box->get_item_ID();
1005
1006
0
  set_item_data(infe_box, data, size, content_encoding);
1007
1008
0
  return metadata_id;
1009
0
}
1010
1011
1012
Result<heif_item_id> HeifFile::add_precompressed_infe_mime(const char* content_type, std::string content_encoding, const uint8_t* data, size_t size)
1013
0
{
1014
  // create an infe box describing what kind of data we are storing (this also creates a new ID)
1015
1016
0
  auto infe_box = add_new_infe_box(fourcc("mime"));
1017
0
  infe_box->set_hidden_item(true);
1018
0
  infe_box->set_content_type(content_type);
1019
1020
0
  heif_item_id metadata_id = infe_box->get_item_ID();
1021
1022
0
  set_precompressed_item_data(infe_box, data, size, std::move(content_encoding));
1023
1024
0
  return metadata_id;
1025
0
}
1026
1027
1028
Result<heif_item_id> HeifFile::add_infe_uri(const char* item_uri_type, const uint8_t* data, size_t size)
1029
0
{
1030
  // create an infe box describing what kind of data we are storing (this also creates a new ID)
1031
1032
0
  auto infe_box = add_new_infe_box(fourcc("uri "));
1033
0
  infe_box->set_hidden_item(true);
1034
0
  infe_box->set_item_uri_type(item_uri_type);
1035
1036
0
  heif_item_id metadata_id = infe_box->get_item_ID();
1037
1038
0
  set_item_data(infe_box, data, size, heif_metadata_compression_off);
1039
1040
0
  return metadata_id;
1041
0
}
1042
1043
1044
Error HeifFile::set_item_data(const std::shared_ptr<Box_infe>& item, const uint8_t* data, size_t size, heif_metadata_compression compression)
1045
0
{
1046
  // --- metadata compression
1047
1048
0
  if (compression == heif_metadata_compression_auto) {
1049
0
    compression = heif_metadata_compression_off; // currently, we don't use header compression by default
1050
0
  }
1051
1052
  // only set metadata compression for MIME type data which has 'content_encoding' field
1053
0
  if (compression != heif_metadata_compression_off &&
1054
0
      item->get_item_type_4cc() != fourcc("mime")) {
1055
    // TODO: error, compression not supported
1056
0
  }
1057
1058
1059
0
  std::vector<uint8_t> data_array;
1060
0
  if (compression == heif_metadata_compression_zlib) {
1061
0
#if HAVE_ZLIB
1062
0
    data_array = compress_zlib((const uint8_t*) data, size);
1063
0
    item->set_content_encoding("compress_zlib");
1064
#else
1065
    return Error(heif_error_Unsupported_feature,
1066
                 heif_suberror_Unsupported_header_compression_method);
1067
#endif
1068
0
  }
1069
0
  else if (compression == heif_metadata_compression_deflate) {
1070
0
#if HAVE_ZLIB
1071
0
    data_array = compress_deflate((const uint8_t*) data, size);
1072
0
    item->set_content_encoding("compress_zlib");
1073
#else
1074
    return Error(heif_error_Unsupported_feature,
1075
                 heif_suberror_Unsupported_header_compression_method);
1076
#endif
1077
0
  }
1078
  // TODO: brotli
1079
0
  else {
1080
    // uncompressed data, plain copy
1081
1082
0
    if (size > 0) { // Note: this 'if' is not necessary, but a workaround to a compiler bug (https://github.com/strukturag/libheif/issues/1421)
1083
0
      data_array.resize(size);
1084
0
      memcpy(data_array.data(), data, size);
1085
0
    }
1086
0
  }
1087
1088
  // copy the data into the file, store the pointer to it in an iloc box entry
1089
1090
0
  append_iloc_data(item->get_item_ID(), data_array, 0);
1091
1092
0
  return Error::Ok;
1093
0
}
1094
1095
1096
Error HeifFile::set_precompressed_item_data(const std::shared_ptr<Box_infe>& item, const uint8_t* data, size_t size, std::string content_encoding)
1097
0
{
1098
  // only set metadata compression for MIME type data which has 'content_encoding' field
1099
0
  if (!content_encoding.empty() &&
1100
0
      item->get_item_type_4cc() != fourcc("mime")) {
1101
    // TODO: error, compression not supported
1102
0
  }
1103
1104
1105
0
  std::vector<uint8_t> data_array;
1106
0
  data_array.resize(size);
1107
0
  memcpy(data_array.data(), data, size);
1108
1109
0
  item->set_content_encoding(content_encoding);
1110
1111
  // copy the data into the file, store the pointer to it in an iloc box entry
1112
1113
0
  append_iloc_data(item->get_item_ID(), data_array, 0);
1114
1115
0
  return Error::Ok;
1116
0
}
1117
1118
1119
void HeifFile::append_iloc_data(heif_item_id id, const std::vector<uint8_t>& nal_packets, uint8_t construction_method)
1120
0
{
1121
0
  m_iloc_box->append_data(id, nal_packets, construction_method);
1122
0
}
1123
1124
1125
void HeifFile::replace_iloc_data(heif_item_id id, uint64_t offset, const std::vector<uint8_t>& data, uint8_t construction_method)
1126
0
{
1127
0
  m_iloc_box->replace_data(id, offset, data, construction_method);
1128
0
}
1129
1130
1131
void HeifFile::set_primary_item_id(heif_item_id id)
1132
0
{
1133
0
  if (!m_pitm_box) {
1134
0
    m_pitm_box = std::make_shared<Box_pitm>();
1135
0
    m_meta_box->replace_child_box(m_pitm_box);
1136
0
  }
1137
1138
0
  m_pitm_box->set_item_ID(id);
1139
0
}
1140
1141
1142
void HeifFile::set_ipco_box(std::shared_ptr<Box_ipco> ipco)
1143
0
{
1144
0
  m_ipco_box = ipco;
1145
0
  m_meta_box->replace_child_box(ipco);
1146
0
}
1147
1148
1149
void HeifFile::set_ipma_box(std::shared_ptr<Box_ipma> ipma)
1150
0
{
1151
0
  m_ipma_box = ipma;
1152
0
  m_meta_box->replace_child_box(ipma);
1153
0
}
1154
1155
1156
void HeifFile::set_iloc_box(std::shared_ptr<Box_iloc> iloc)
1157
0
{
1158
0
  m_iloc_box = iloc;
1159
0
  m_meta_box->replace_child_box(iloc);
1160
0
}
1161
1162
1163
void HeifFile::set_iref_box(std::shared_ptr<Box_iref> iref)
1164
0
{
1165
0
  m_iref_box = iref;
1166
0
  m_meta_box->replace_child_box(iref);
1167
0
}
1168
1169
1170
void HeifFile::add_iref_reference(heif_item_id from, uint32_t type,
1171
                                  const std::vector<heif_item_id>& to)
1172
0
{
1173
0
  if (!m_iref_box) {
1174
0
    m_iref_box = std::make_shared<Box_iref>();
1175
0
    m_meta_box->append_child_box(m_iref_box);
1176
0
  }
1177
1178
0
  m_iref_box->add_references(from, type, to);
1179
0
}
1180
1181
1182
void HeifFile::set_iref_reference(heif_item_id from, uint32_t type, int reference_idx, heif_item_id to_item)
1183
0
{
1184
0
  assert(m_iref_box);
1185
0
  m_iref_box->overwrite_reference(from, type, reference_idx, to_item);
1186
0
}
1187
1188
1189
void HeifFile::add_entity_group_box(const std::shared_ptr<Box>& entity_group_box)
1190
0
{
1191
0
  if (!m_grpl_box) {
1192
0
    m_grpl_box = std::make_shared<Box_grpl>();
1193
0
    m_meta_box->append_child_box(m_grpl_box);
1194
0
  }
1195
1196
0
  m_grpl_box->append_child_box(entity_group_box);
1197
0
}
1198
1199
1200
std::shared_ptr<Box_EntityToGroup> HeifFile::get_entity_group(heif_entity_group_id id)
1201
0
{
1202
0
  if (!m_grpl_box) {
1203
0
    return nullptr;
1204
0
  }
1205
1206
0
  const auto& entityGroups = m_grpl_box->get_all_child_boxes();
1207
0
  for (auto& groupBase : entityGroups) {
1208
0
    auto group = std::dynamic_pointer_cast<Box_EntityToGroup>(groupBase);
1209
0
    assert(group);
1210
1211
0
    if (group->get_group_id() == id) {
1212
0
      return group;
1213
0
    }
1214
0
  }
1215
1216
0
  return nullptr;
1217
0
}
1218
1219
1220
void HeifFile::set_auxC_property(heif_item_id id, const std::string& type)
1221
0
{
1222
0
  auto auxC = std::make_shared<Box_auxC>();
1223
0
  auxC->set_aux_type(type);
1224
1225
0
  uint32_t index = m_ipco_box->find_or_append_child_box(auxC);
1226
1227
0
  m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)});
1228
0
}
1229
1230
#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER)
1231
std::wstring HeifFile::convert_utf8_path_to_utf16(std::string str)
1232
{
1233
  std::wstring ret;
1234
  int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), NULL, 0);
1235
  if (len > 0)
1236
  {
1237
    ret.resize(len);
1238
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), &ret[0], len);
1239
  }
1240
  return ret;
1241
}
1242
#endif
1243
1244
1245
Result<size_t> HeifFile::write_mdat(StreamWriter& writer)
1246
0
{
1247
  // --- write mdat box
1248
1249
0
  size_t mdatSize = m_mdat_data->get_data_size();
1250
1251
0
  if (mdatSize <= 0xFFFFFFFF - 8) {
1252
0
    writer.write32((uint32_t) (mdatSize + 8));
1253
0
    writer.write32(fourcc("mdat"));
1254
0
  }
1255
0
  else {
1256
    // box size > 4 GB
1257
1258
0
    writer.write32(1);
1259
0
    writer.write32(fourcc("mdat"));
1260
0
    writer.write64(mdatSize+8+8);
1261
0
  }
1262
1263
0
  size_t dataStartPos = writer.get_position();
1264
1265
0
  m_mdat_data->write(writer);
1266
1267
0
  return dataStartPos;
1268
0
}