Coverage Report

Created: 2026-06-14 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/codecs/decoder.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2024 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "codecs/decoder.h"
22
23
#include <utility>
24
#include "error.h"
25
#include "context.h"
26
#include "plugin_registry.h"
27
#include "api_structs.h"
28
#include "security_limits.h"
29
30
#include "codecs/hevc_dec.h"
31
#include "codecs/avif_dec.h"
32
#include "codecs/avc_dec.h"
33
#include "codecs/vvc_dec.h"
34
#include "codecs/jpeg_dec.h"
35
#include "codecs/jpeg2000_dec.h"
36
#include "avc_boxes.h"
37
#include "avif_boxes.h"
38
#include "hevc_boxes.h"
39
#include "vvc_boxes.h"
40
#include "jpeg_boxes.h"
41
#include "jpeg2000_boxes.h"
42
43
#if WITH_UNCOMPRESSED_CODEC
44
#include "codecs/uncompressed/unc_dec.h"
45
#include "codecs/uncompressed/unc_boxes.h"
46
#endif
47
48
void DataExtent::set_from_image_item(std::shared_ptr<HeifFile> file, heif_item_id item)
49
29.1k
{
50
29.1k
  m_file = std::move(file);
51
29.1k
  m_item_id = item;
52
29.1k
  m_source = Source::Image;
53
29.1k
}
54
55
56
void DataExtent::set_file_range(std::shared_ptr<HeifFile> file, uint64_t offset, uint32_t size)
57
0
{
58
0
  m_file = std::move(file);
59
0
  m_source = Source::FileRange;
60
0
  m_offset = offset;
61
0
  m_size = size;
62
0
}
63
64
65
Result<std::vector<uint8_t>*> DataExtent::read_data() const
66
12.7k
{
67
12.7k
  if (!m_raw.empty()) {
68
32
    return &m_raw;
69
32
  }
70
12.7k
  else if (m_source == Source::Image) {
71
12.7k
    assert(m_file);
72
73
    // image
74
12.7k
    Error err = m_file->append_data_from_iloc(m_item_id, m_raw);
75
12.7k
    if (err) {
76
594
      return err;
77
594
    }
78
79
    // Account the (now-known) buffer size against the file's total-memory budget.
80
    // append_data_from_iloc has already enforced max_memory_block_size per extent.
81
12.1k
    if (auto memErr = m_raw_memory_handle.alloc(m_raw.size(), m_file->get_security_limits(),
82
12.1k
                                                "decoder input buffer (iloc)")) {
83
0
      m_raw.clear();
84
0
      m_raw.shrink_to_fit();
85
0
      return memErr;
86
0
    }
87
12.1k
  }
88
1
  else {
89
1
    assert(m_file);
90
91
    // Reserve the buffer in the total-memory tracker before allocating it.
92
    // This also enforces max_memory_block_size and rejects sizes that would
93
    // exceed max_total_memory across all concurrently-live DataExtents.
94
0
    if (auto memErr = m_raw_memory_handle.alloc(m_size, m_file->get_security_limits(),
95
0
                                                "decoder input buffer (sample)")) {
96
0
      return memErr;
97
0
    }
98
99
    // file range
100
0
    Error err = m_file->append_data_from_file_range(m_raw, m_offset, m_size);
101
0
    if (err) {
102
0
      m_raw_memory_handle.free();
103
0
      return err;
104
0
    }
105
0
  }
106
107
12.1k
  return &m_raw;
108
12.7k
}
109
110
111
Result<std::vector<uint8_t>> DataExtent::read_data(uint64_t offset, uint64_t size) const
112
0
{
113
0
  std::vector<uint8_t> data;
114
115
0
  if (!m_raw.empty()) {
116
    // No caller currently reaches this cached path with an out-of-range request, so
117
    // hitting it indicates an internal logic error rather than malformed input. Guard
118
    // it defensively anyway. The subtraction form avoids a uint64_t wrap in
119
    // 'offset + size' that would otherwise allow an out-of-bounds read below.
120
    // TODO: this would be better reported as an internal error; change it once we have
121
    //       a dedicated error code for that.
122
0
    if (offset > m_raw.size() || size > m_raw.size() - offset) {
123
0
      return Error{heif_error_Invalid_input,
124
0
                   heif_suberror_End_of_data,
125
0
                   "Requested data range exceeds the cached extent buffer"};
126
0
    }
127
0
    data.insert(data.begin(), m_raw.begin() + offset, m_raw.begin() + offset + size);
128
0
    return data;
129
0
  }
130
0
  else if (m_source == Source::Image) {
131
    // TODO: cache data
132
133
    // image
134
0
    Error err = m_file->append_data_from_iloc(m_item_id, data, offset, size);
135
0
    if (err) {
136
0
      return err;
137
0
    }
138
0
    return data;
139
0
  }
140
0
  else {
141
    // file range
142
0
    Error err = m_file->append_data_from_file_range(data, m_offset, m_size);
143
0
    if (err) {
144
0
      return err;
145
0
    }
146
147
0
    return data;
148
0
  }
149
0
}
150
151
152
std::shared_ptr<Decoder> Decoder::alloc_for_infe_type(const ImageItem* item)
153
0
{
154
0
  uint32_t format_4cc = item->get_infe_type();
155
156
0
  switch (format_4cc) {
157
0
    case fourcc("hvc1"): {
158
0
      auto hvcC = item->get_property<Box_hvcC>();
159
0
      if (!hvcC) return nullptr;
160
0
      return std::make_shared<Decoder_HEVC>(hvcC);
161
0
    }
162
0
    case fourcc("av01"): {
163
0
      auto av1C = item->get_property<Box_av1C>();
164
0
      if (!av1C) return nullptr;
165
0
      return std::make_shared<Decoder_AVIF>(av1C);
166
0
    }
167
0
    case fourcc("avc1"): {
168
0
      auto avcC = item->get_property<Box_avcC>();
169
0
      if (!avcC) return nullptr;
170
0
      return std::make_shared<Decoder_AVC>(avcC);
171
0
    }
172
0
    case fourcc("j2k1"): {
173
0
      auto j2kH = item->get_property<Box_j2kH>();
174
0
      return std::make_shared<Decoder_JPEG2000>(j2kH);
175
0
    }
176
0
    case fourcc("vvc1"): {
177
0
      auto vvcC = item->get_property<Box_vvcC>();
178
0
      if (!vvcC) return nullptr;
179
0
      return std::make_shared<Decoder_VVC>(vvcC);
180
0
    }
181
0
    case fourcc("jpeg"): {
182
0
      auto jpgC = item->get_property<Box_jpgC>();
183
0
      return std::make_shared<Decoder_JPEG>(jpgC);
184
0
    }
185
#if WITH_UNCOMPRESSED_CODEC
186
    case fourcc("unci"): {
187
      auto uncC = item->get_property<Box_uncC>();
188
      auto cmpd = item->get_property<Box_cmpd>();
189
      auto ispe = item->get_property<Box_ispe>();
190
      auto decoder = std::make_shared<Decoder_uncompressed>(uncC, cmpd, ispe);
191
      decoder->set_cpat(item->get_property<Box_cpat>());
192
      decoder->set_cmpC(item->get_property<Box_cmpC>());
193
      decoder->set_icef(item->get_property<Box_icef>());
194
      decoder->set_cloc(item->get_property<Box_cloc>());
195
      decoder->set_splz(item->get_all_properties<Box_splz>());
196
      decoder->set_sbpm(item->get_all_properties<Box_sbpm>());
197
      decoder->set_snuc(item->get_all_properties<Box_snuc>());
198
      return decoder;
199
    }
200
#endif
201
0
    case fourcc("mski"): {
202
0
      return nullptr; // do we need a decoder for this?
203
0
    }
204
0
    default:
205
0
      return nullptr;
206
0
  }
207
0
}
208
209
210
std::shared_ptr<Decoder> Decoder::alloc_for_sequence_sample_description_box(std::shared_ptr<const Box_VisualSampleEntry> sample_description_box)
211
0
{
212
0
  std::string compressor = sample_description_box->get_VisualSampleEntry_const().compressorname;
213
0
  uint32_t sampleType = sample_description_box->get_short_type();
214
215
0
  switch (sampleType) {
216
0
    case fourcc("hvc1"): {
217
0
      auto hvcC = sample_description_box->get_child_box<Box_hvcC>();
218
0
      if (!hvcC) return nullptr;
219
0
      return std::make_shared<Decoder_HEVC>(hvcC);
220
0
    }
221
222
0
    case fourcc("av01"): {
223
0
      auto av1C = sample_description_box->get_child_box<Box_av1C>();
224
0
      if (!av1C) return nullptr;
225
0
      return std::make_shared<Decoder_AVIF>(av1C);
226
0
    }
227
228
0
    case fourcc("vvc1"): {
229
0
      auto vvcC = sample_description_box->get_child_box<Box_vvcC>();
230
0
      if (!vvcC) return nullptr;
231
0
      return std::make_shared<Decoder_VVC>(vvcC);
232
0
    }
233
234
0
    case fourcc("avc1"): {
235
0
      auto avcC = sample_description_box->get_child_box<Box_avcC>();
236
0
      if (!avcC) return nullptr;
237
0
      return std::make_shared<Decoder_AVC>(avcC);
238
0
    }
239
240
#if WITH_UNCOMPRESSED_CODEC
241
    case fourcc("uncv"): {
242
      auto uncC = sample_description_box->get_child_box<Box_uncC>();
243
      auto cmpd = sample_description_box->get_child_box<Box_cmpd>();
244
      auto ispe = std::make_shared<Box_ispe>();
245
      ispe->set_size(sample_description_box->get_VisualSampleEntry_const().width,
246
                     sample_description_box->get_VisualSampleEntry_const().height);
247
      auto decoder = std::make_shared<Decoder_uncompressed>(uncC, cmpd, ispe);
248
      decoder->set_cpat(sample_description_box->get_child_box<Box_cpat>());
249
      decoder->set_cmpC(sample_description_box->get_child_box<Box_cmpC>());
250
      decoder->set_icef(sample_description_box->get_child_box<Box_icef>());
251
      decoder->set_cloc(sample_description_box->get_child_box<Box_cloc>());
252
      decoder->set_splz(sample_description_box->get_child_boxes<Box_splz>());
253
      decoder->set_sbpm(sample_description_box->get_child_boxes<Box_sbpm>());
254
      decoder->set_snuc(sample_description_box->get_child_boxes<Box_snuc>());
255
      return decoder;
256
    }
257
#endif
258
259
0
    case fourcc("j2ki"): {
260
0
      auto j2kH = sample_description_box->get_child_box<Box_j2kH>();
261
0
      return std::make_shared<Decoder_JPEG2000>(j2kH);
262
0
    }
263
264
0
    case fourcc("mjpg"): {
265
0
      auto jpgC = sample_description_box->get_child_box<Box_jpgC>();
266
0
      return std::make_shared<Decoder_JPEG>(jpgC);
267
0
    }
268
269
0
    default:
270
0
      return nullptr;
271
0
  }
272
0
}
273
274
275
Result<std::vector<uint8_t>> Decoder::get_compressed_data(bool with_configuration_NALs) const
276
12.7k
{
277
  // --- get the compressed image data
278
279
12.7k
  if (with_configuration_NALs) {
280
    // data from configuration blocks
281
282
12.7k
    Result<std::vector<uint8_t>> confData = read_bitstream_configuration_data();
283
12.7k
    if (!confData) {
284
0
      return confData.error();
285
0
    }
286
287
12.7k
    std::vector<uint8_t> data = *confData;
288
289
    // append image data
290
291
12.7k
    auto dataResult = m_data_extent.read_data();
292
12.7k
    if (!dataResult) {
293
594
      return dataResult.error();
294
594
    }
295
296
12.1k
    data.insert(data.end(), (*dataResult)->begin(), (*dataResult)->end());
297
298
12.1k
    return data;
299
12.7k
  }
300
0
  else {
301
0
    auto dataResult = m_data_extent.read_data();
302
0
    if (!dataResult) {
303
0
      return dataResult.error();
304
0
    }
305
306
0
    return {*(*dataResult)};
307
0
  }
308
12.7k
}
309
310
311
Decoder::~Decoder()
312
16.3k
{
313
16.3k
  release_decoder();
314
16.3k
}
315
316
317
void Decoder::release_decoder()
318
29.1k
{
319
29.1k
  if (m_decoder) {
320
12.6k
    assert(m_decoder_plugin);
321
12.6k
    m_decoder_plugin->free_decoder(m_decoder);
322
12.6k
    m_decoder = nullptr;
323
12.6k
  }
324
29.1k
}
325
326
327
Error Decoder::require_decoder_plugin(const heif_decoding_options& options)
328
116k
{
329
116k
  if (!m_decoder_plugin) {
330
12.7k
    if (options.decoder_id && !has_decoder(get_compression_format(), options.decoder_id)) {
331
0
      return {
332
0
        heif_error_Plugin_loading_error,
333
0
        heif_suberror_Unspecified,
334
0
        "No decoder with that ID found."
335
0
      };
336
0
    }
337
338
12.7k
    m_decoder_plugin = get_decoder(get_compression_format(), options.decoder_id);
339
12.7k
    if (!m_decoder_plugin) {
340
1
      return Error(heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed);
341
1
    }
342
343
12.7k
    if (m_decoder_plugin->plugin_api_version < 5) {
344
0
      return Error{
345
0
        heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed,
346
0
        "Decoder plugin needs to be at least version 5."
347
0
      };
348
0
    }
349
12.7k
  }
350
351
116k
  return {};
352
116k
}
353
354
355
Error Decoder::decode_sequence_frame_from_compressed_data(bool upload_configuration_NALs,
356
                                                          const heif_decoding_options& options,
357
                                                          uintptr_t user_data,
358
                                                          const heif_security_limits* limits)
359
12.8k
{
360
12.8k
  auto pluginErr = require_decoder_plugin(options);
361
12.8k
  if (pluginErr) {
362
1
    return pluginErr;
363
1
  }
364
365
  // Reject memory-bomb inputs whose codec configuration record (SPS) declares
366
  // a coded picture size beyond libheif's security limits, before handing any
367
  // bytes to the decoder plugin. Codecs whose configuration record does not
368
  // carry dimensions (e.g. AV1's av1C) return nullopt and skip the check.
369
  //
370
  // TODO: check this also in the decoder plugin since SPS packets may be
371
  //       found within the actual image bitstream.
372
12.8k
  auto codedSize = get_coded_image_size_from_config();
373
12.8k
  if (codedSize.is_error()) {
374
176
    return codedSize.error();
375
176
  }
376
377
12.6k
  if (codedSize->has_value()) {
378
4.32k
    Error sizeErr = check_for_valid_image_size(limits, (*codedSize)->width, (*codedSize)->height);
379
4.32k
    if (sizeErr) {
380
28
      return sizeErr;
381
28
    }
382
4.32k
  }
383
384
  // --- decode image with the plugin
385
386
12.6k
  heif_error err;
387
388
12.6k
  if (!m_decoder) {
389
12.6k
    if (m_decoder_plugin->new_decoder == nullptr) {
390
0
      return Error(heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed,
391
0
                   "Cannot decode with a dummy decoder plugin.");
392
0
    }
393
394
12.6k
    if (m_decoder_plugin->plugin_api_version >= 5) {
395
12.6k
      heif_decoder_plugin_options plugin_options;
396
12.6k
      plugin_options.format = get_compression_format();
397
12.6k
      plugin_options.num_threads = options.num_codec_threads;
398
12.6k
      plugin_options.strict_decoding = options.strict_decoding;
399
12.6k
      plugin_options.limits = limits;
400
401
12.6k
      err = m_decoder_plugin->new_decoder2(&m_decoder, &plugin_options);
402
12.6k
      if (err.code != heif_error_Ok) {
403
0
        return Error(err.code, err.subcode, err.message);
404
0
      }
405
12.6k
    }
406
1
    else {
407
1
      err = m_decoder_plugin->new_decoder(&m_decoder);
408
1
      if (err.code != heif_error_Ok) {
409
0
        return Error(err.code, err.subcode, err.message);
410
0
      }
411
412
      // automatically delete decoder plugin when we leave the scope
413
      //std::unique_ptr<void, void (*)(void*)> decoderSmartPtr(m_decoder, m_decoder_plugin->free_decoder);
414
415
1
      if (m_decoder_plugin->plugin_api_version >= 2) {
416
0
        if (m_decoder_plugin->set_strict_decoding) {
417
0
          m_decoder_plugin->set_strict_decoding(m_decoder, options.strict_decoding);
418
0
        }
419
0
      }
420
1
    }
421
12.6k
  }
422
423
12.6k
  auto dataResult = get_compressed_data(upload_configuration_NALs);
424
12.6k
  if (!dataResult) {
425
567
    return dataResult.error();
426
567
  }
427
428
  // Check that we are pushing at least some data into the decoder.
429
  // Some decoders (e.g. aom) do not complain when the input data is empty and we might
430
  // get stuck in an endless decoding loop, waiting for the decompressed image.
431
432
12.0k
  if (dataResult->size() == 0) {
433
9
    return Error{
434
9
      heif_error_Invalid_input,
435
9
      heif_suberror_Unspecified,
436
9
      "Input with empty data extent."
437
9
    };
438
9
  }
439
440
  //std::cout << "Decoder::decode_sequence_frame_from_compressed_data push " << dataResult->size() << "\n";
441
12.0k
  if (m_decoder_plugin->plugin_api_version >= 5 && m_decoder_plugin->push_data2) {
442
12.0k
    err = m_decoder_plugin->push_data2(m_decoder, dataResult->data(), dataResult->size(), user_data);
443
12.0k
  }
444
18.4E
  else {
445
18.4E
    err = m_decoder_plugin->push_data(m_decoder, dataResult->data(), dataResult->size());
446
18.4E
  }
447
12.0k
  if (err.code != heif_error_Ok) {
448
6.06k
    return Error(err.code, err.subcode, err.message);
449
6.06k
  }
450
451
5.98k
  return {};
452
12.0k
}
453
454
Error Decoder::flush_decoder()
455
5.98k
{
456
5.98k
  assert(m_decoder_plugin);
457
458
5.98k
  if (m_decoder_plugin->plugin_api_version >= 5) {
459
5.98k
    heif_error err = m_decoder_plugin->flush_data(m_decoder);
460
5.98k
    return Error::from_heif_error(err);
461
5.98k
  }
462
463
0
  return {};
464
5.98k
}
465
466
Result<std::shared_ptr<HeifPixelImage> > Decoder::get_decoded_frame(const heif_decoding_options& options,
467
                                                                    uintptr_t* out_user_data,
468
                                                                    const heif_security_limits* limits)
469
103k
{
470
103k
  auto pluginErr = require_decoder_plugin(options);
471
103k
  if (pluginErr) {
472
0
    return pluginErr;
473
0
  }
474
475
  // The plugin's per-decoder context is created lazily on the first push of
476
  // compressed data. If a caller polls for a frame before any data was pushed
477
  // (e.g. when a sequence advances into a new chunk that uses a freshly-
478
  // allocated decoder), there is nothing buffered yet — return nullptr.
479
103k
  if (!m_decoder) {
480
0
    return {nullptr};
481
0
  }
482
483
103k
  heif_image* decoded_img = nullptr;
484
485
103k
  heif_error err;
486
487
103k
  if (m_decoder_plugin->plugin_api_version >= 5 &&
488
103k
      m_decoder_plugin->decode_next_image2 != nullptr) {
489
490
103k
    err = m_decoder_plugin->decode_next_image2(m_decoder, &decoded_img, out_user_data, limits);
491
103k
    if (err.code != heif_error_Ok) {
492
971
      return Error::from_heif_error(err);
493
971
    }
494
103k
  }
495
18.4E
  else if (m_decoder_plugin->plugin_api_version >= 4 &&
496
0
           m_decoder_plugin->decode_next_image != nullptr) {
497
498
0
    err = m_decoder_plugin->decode_next_image(m_decoder, &decoded_img, limits);
499
0
    if (err.code != heif_error_Ok) {
500
0
      return Error::from_heif_error(err);
501
0
    }
502
0
  }
503
18.4E
  else {
504
18.4E
    err = m_decoder_plugin->decode_image(m_decoder, &decoded_img);
505
18.4E
    if (err.code != heif_error_Ok) {
506
0
      return Error::from_heif_error(err);
507
0
    }
508
18.4E
  }
509
510
102k
  if (!decoded_img) {
511
99.1k
    return {nullptr};
512
99.1k
  }
513
514
  // -- cleanup
515
516
3.09k
  std::shared_ptr<HeifPixelImage> img = std::move(decoded_img->image);
517
3.09k
  heif_image_release(decoded_img);
518
519
3.09k
  return img;
520
102k
}
521
522
523
Result<std::shared_ptr<HeifPixelImage>>
524
Decoder::decode_single_frame_from_compressed_data(const heif_decoding_options& options,
525
                                                  const heif_security_limits* limits)
526
12.8k
{
527
12.8k
  Error decodeError = decode_sequence_frame_from_compressed_data(true, options, 0, limits);
528
12.8k
  if (decodeError) {
529
6.84k
    release_decoder();
530
6.84k
    return decodeError;
531
6.84k
  }
532
533
5.98k
  flush_decoder();
534
535
  // We might have to try several times to get an image out of the decoder.
536
  // However, we stop after a maximum number of tries because the decoder might not
537
  // give any image when the input data is incomplete.
538
5.98k
  const int max_decoding_tries = 50; // hardcoded value, should be large enough
539
540
105k
  for (int i = 0; i < max_decoding_tries; i++) {
541
103k
    Result<std::shared_ptr<HeifPixelImage>> imgResult;
542
103k
    imgResult = get_decoded_frame(options, nullptr, limits);
543
103k
    if (imgResult.error()) {
544
971
      release_decoder();
545
971
      return imgResult.error();
546
971
    }
547
548
102k
    if (*imgResult != nullptr) {
549
3.09k
      release_decoder();
550
3.09k
      return imgResult;
551
3.09k
    }
552
102k
  }
553
554
  // We did not receive an image from the decoder. We give up.
555
556
1.91k
  release_decoder();
557
558
1.91k
  return Error{
559
1.91k
    heif_error_Decoder_plugin_error,
560
1.91k
    heif_suberror_Unspecified,
561
1.91k
    "Decoding the input data did not give a decompressed image."
562
1.91k
  };
563
5.98k
}