Coverage Report

Created: 2026-10-27 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/plugins/decoder_libde265.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 "libheif/heif.h"
22
#include "libheif/heif_plugin.h"
23
#include "decoder_libde265.h"
24
#include <cassert>
25
#include <memory>
26
#include <cstring>
27
#include <limits>
28
#include <string>
29
30
#include <libde265/de265.h>
31
32
33
34
struct libde265_decoder
35
{
36
  de265_decoder_context* ctx;
37
  bool strict_decoding = false;
38
  std::string error_message;
39
};
40
41
static const char kEmptyString[] = "";
42
static const char kSuccess[] = "Success";
43
44
static const int LIBDE265_PLUGIN_PRIORITY = 100;
45
46
0
#define MAX_PLUGIN_NAME_LENGTH 80
47
48
static char plugin_name[MAX_PLUGIN_NAME_LENGTH];
49
static constexpr char version_prefix[] = ", version ";
50
51
static const char* libde265_plugin_name()
52
0
{
53
0
  strcpy(plugin_name, "libde265 HEVC decoder");
54
55
0
  const char* libde265_version = de265_get_version();
56
57
0
  if (strlen(plugin_name) + strlen(libde265_version) + (sizeof(version_prefix) - 1) < MAX_PLUGIN_NAME_LENGTH) {
58
0
    strcat(plugin_name, version_prefix);
59
0
    strcat(plugin_name, libde265_version);
60
0
  }
61
62
0
  return plugin_name;
63
0
}
64
65
66
static void libde265_init_plugin()
67
22.8k
{
68
22.8k
  de265_init();
69
22.8k
}
70
71
72
static void libde265_deinit_plugin()
73
22.8k
{
74
22.8k
  de265_free();
75
22.8k
}
76
77
78
static int libde265_does_support_format(heif_compression_format format)
79
39.7k
{
80
39.7k
  if (format == heif_compression_HEVC) {
81
27.0k
    return LIBDE265_PLUGIN_PRIORITY;
82
27.0k
  }
83
12.6k
  else {
84
12.6k
    return 0;
85
12.6k
  }
86
39.7k
}
87
88
89
[[maybe_unused]]
90
static int libde265_does_support_format2(const heif_decoder_plugin_compressed_format_description* format)
91
0
{
92
0
  return libde265_does_support_format(format->format);
93
0
}
94
95
96
97
static heif_error convert_libde265_image_to_heif_image(libde265_decoder* decoder,
98
                                                       const de265_image* de265img,
99
                                                       heif_image** image,
100
                                                       const heif_security_limits* limits)
101
8.20k
{
102
8.20k
  bool is_mono = (de265_get_chroma_format(de265img) == de265_chroma_mono);
103
104
8.20k
  heif_error err;
105
8.20k
  err = heif_image_create(de265_get_image_width(de265img, 0),
106
8.20k
                          de265_get_image_height(de265img, 0),
107
8.20k
                          is_mono ? heif_colorspace_monochrome : heif_colorspace_YCbCr,
108
8.20k
                          (heif_chroma) de265_get_chroma_format(de265img),
109
8.20k
                          image);
110
8.20k
  if (err.code) {
111
0
    return err;
112
0
  }
113
114
  // --- transfer data from de265_image to HeifPixelImage
115
116
8.20k
  heif_channel channel2plane[3] = {
117
8.20k
      heif_channel_Y,
118
8.20k
      heif_channel_Cb,
119
8.20k
      heif_channel_Cr
120
8.20k
  };
121
122
123
8.20k
  int bpp = de265_get_bits_per_pixel(de265img, 0);
124
125
8.20k
  int num_planes = (is_mono ? 1 : 3);
126
127
30.2k
  for (int c = 0; c < num_planes; c++) {
128
22.8k
    if (de265_get_bits_per_pixel(de265img, c) != bpp) {
129
731
      heif_image_release(*image);
130
731
      err = {heif_error_Unsupported_feature,
131
731
             heif_suberror_Unsupported_color_conversion,
132
731
             "Channels with different number of bits per pixel are not supported"};
133
731
      return err;
134
731
    }
135
136
22.0k
    int stride;
137
22.0k
    const uint8_t* data = de265_get_image_plane(de265img, c, &stride);
138
139
22.0k
    int w = de265_get_image_width(de265img, c);
140
22.0k
    int h = de265_get_image_height(de265img, c);
141
22.0k
    if (w <= 0 || h <= 0) {
142
0
      heif_image_release(*image);
143
0
      err = {heif_error_Decoder_plugin_error,
144
0
             heif_suberror_Invalid_image_size,
145
0
             kEmptyString};
146
0
      return err;
147
0
    }
148
149
22.0k
    err = heif_image_add_plane_safe(*image, channel2plane[c], w,h, bpp, limits);
150
22.0k
    if (err.code) {
151
      // copy error message to decoder object because heif_image will be released
152
0
      decoder->error_message = err.message;
153
0
      err.message = decoder->error_message.c_str();
154
155
0
      heif_image_release(*image);
156
0
      return err;
157
0
    }
158
159
22.0k
    size_t dst_stride;
160
22.0k
    uint8_t* dst_mem = heif_image_get_plane2(*image, channel2plane[c], &dst_stride);
161
162
22.0k
    int bytes_per_pixel = (bpp + 7) / 8;
163
164
6.63M
    for (int y = 0; y < h; y++) {
165
6.60M
      memcpy(dst_mem + y * dst_stride, data + static_cast<size_t>(y) * stride, static_cast<size_t>(w) * bytes_per_pixel);
166
6.60M
    }
167
22.0k
  }
168
169
170
7.47k
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
171
8.20k
}
172
173
174
175
// Create a new decoder context for decoding an image
176
heif_error libde265_new_decoder2(void** dec, const heif_decoder_plugin_options* options)
177
9.07k
{
178
9.07k
  libde265_decoder* decoder = new libde265_decoder();
179
9.07k
  heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
180
181
9.07k
  decoder->ctx = de265_new_decoder();
182
183
9.07k
#if LIBDE265_NUMERIC_VERSION >= 0x01010000
184
  // Pass libheif's max_image_size_pixels through to libde265 so the decoder
185
  // rejects bitstreams whose SPS declares an image far larger than what the
186
  // surrounding HEIF file claims. libheif tightens this limit per-decode to
187
  // ispe + one coding-unit margin (see tighten_image_size_limit_for_ispe),
188
  // so the padding margin is already baked into the value we see here.
189
9.07k
  {
190
18.4E
    const heif_security_limits* limits = options->limits ? options->limits : heif_get_global_security_limits();
191
9.07k
    de265_security_limits* de265_limits = de265_get_security_limits(decoder->ctx);
192
9.07k
    if (limits->max_image_size_pixels > std::numeric_limits<uint32_t>::max()) {
193
22
      de265_limits->max_image_size_pixels = 0;
194
22
    }
195
9.05k
    else {
196
9.05k
      de265_limits->max_image_size_pixels = static_cast<uint32_t>(limits->max_image_size_pixels);
197
9.05k
    }
198
199
    // libde265 rejects NAL units larger than 16 MiB by default. libheif has already
200
    // read the whole item into memory under max_memory_block_size, so use that
201
    // limit for the NAL size too. Otherwise, large images (e.g. lossless) that
202
    // libheif can write could not be decoded, even with the limits disabled.
203
9.07k
    if (limits->max_memory_block_size > std::numeric_limits<uint32_t>::max()) {
204
0
      de265_limits->max_NAL_size_bytes = 0;
205
0
    }
206
9.07k
    else {
207
9.07k
      de265_limits->max_NAL_size_bytes = static_cast<uint32_t>(limits->max_memory_block_size);
208
9.07k
    }
209
9.07k
  }
210
9.07k
#endif
211
212
#if defined(__EMSCRIPTEN__)
213
  // Speed up decoding from JavaScript.
214
  de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_DEBLOCKING, 1);
215
  de265_set_parameter_bool(decoder->ctx, DE265_DECODER_PARAM_DISABLE_SAO, 1);
216
#else
217
9.07k
  int nThreads = (options->num_threads ? options->num_threads : 1);
218
219
  // Worker threads are not supported when running on Emscripten.
220
9.07k
  de265_start_worker_threads(decoder->ctx, nThreads);
221
9.07k
#endif
222
223
9.07k
  decoder->strict_decoding = options->strict_decoding;
224
225
9.07k
  *dec = decoder;
226
9.07k
  return err;
227
9.07k
}
228
229
230
static heif_error libde265_new_decoder(void** dec)
231
0
{
232
0
  heif_decoder_plugin_options options{};
233
0
  options.format = heif_compression_HEVC;
234
0
  options.num_threads = 0;
235
0
  options.strict_decoding = false;
236
237
0
  return libde265_new_decoder2(dec, &options);
238
0
}
239
240
static void libde265_free_decoder(void* decoder_raw)
241
9.07k
{
242
9.07k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
243
244
9.07k
  de265_error err = de265_free_decoder(decoder->ctx);
245
9.07k
  (void) err;
246
247
9.07k
  delete decoder;
248
9.07k
}
249
250
251
void libde265_set_strict_decoding(void* decoder_raw, int flag)
252
0
{
253
0
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
254
255
0
  decoder->strict_decoding = flag;
256
0
}
257
258
259
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
260
261
static heif_error libde265_v2_push_data(void* decoder_raw, const void* data, size_t size)
262
{
263
  libde265_decoder* decoder = (libde265_decoder*)decoder_raw;
264
265
  const uint8_t* cdata = (const uint8_t*)data;
266
267
  size_t ptr=0;
268
  while (ptr < size) {
269
    if (4 > size - ptr) {
270
      return { heif_error_Decoder_plugin_error,
271
               heif_suberror_End_of_data,
272
               kEmptyString };
273
    }
274
275
    // TODO: the size of the NAL unit length variable is defined in the hvcC header.
276
    // We should not assume that it is always 4 bytes.
277
    uint32_t nal_size = (uint32_t)((cdata[ptr]<<24) | (cdata[ptr+1]<<16) | (cdata[ptr+2]<<8) | (cdata[ptr+3]));
278
    ptr+=4;
279
280
    if (nal_size > size - ptr) {
281
      //sstr << "NAL size (" << size32 << ") exceeds available data in file ("
282
      //<< data_bytes_left_to_read << ")";
283
284
      return { heif_error_Decoder_plugin_error,
285
               heif_suberror_End_of_data,
286
               kEmptyString };
287
    }
288
289
    de265_push_NAL(decoder->ctx, cdata+ptr, nal_size, 0, nullptr);
290
    ptr += nal_size;
291
  }
292
293
294
  return { heif_error_Ok, heif_suberror_Unspecified, kSuccess };
295
}
296
297
298
static heif_error libde265_v2_decode_next_image(void* decoder_raw,
299
                                                heif_image** out_img,
300
                                                const heif_security_limits* limits)
301
{
302
  libde265_decoder* decoder = (libde265_decoder*)decoder_raw;
303
304
  de265_push_end_of_stream(decoder->ctx);
305
306
  int action = de265_get_action(decoder->ctx, 1);
307
308
  // TODO: read NCLX from h265 bitstream
309
310
  // TODO(farindk): Set "err" if no image was decoded.
311
  if (action==de265_action_get_image) {
312
    const de265_image* img = de265_get_next_picture(decoder->ctx);
313
    if (img) {
314
      heif_error err = convert_libde265_image_to_heif_image(decoder, img,
315
                                                                   out_img, limits);
316
      de265_release_picture(img);
317
318
      return err;
319
    }
320
  }
321
322
  return { heif_error_Decoder_plugin_error, heif_suberror_Unspecified, kEmptyString };
323
}
324
325
static heif_error libde265_v2_decode_image(void* decoder_raw,
326
                                           heif_image** out_img)
327
{
328
  auto* limits = heif_get_global_security_limits();
329
  return libde265_v2_decode_next_image(decoder_raw, out_img, limits);
330
}
331
#else
332
333
static heif_error libde265_v1_push_data2(void* decoder_raw, const void* data, size_t size, uintptr_t user_data)
334
9.07k
{
335
9.07k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
336
337
9.07k
  const uint8_t* cdata = (const uint8_t*) data;
338
339
9.07k
  size_t ptr = 0;
340
61.6k
  while (ptr < size) {
341
52.5k
    if (4 > size - ptr) {
342
0
      return {
343
0
        heif_error_Decoder_plugin_error,
344
0
        heif_suberror_End_of_data,
345
0
        kEmptyString
346
0
      };
347
0
    }
348
349
52.5k
    uint32_t nal_size = static_cast<uint32_t>((cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]));
350
52.5k
    ptr += 4;
351
352
52.5k
    if (nal_size > size - ptr) {
353
0
      return {
354
0
        heif_error_Decoder_plugin_error,
355
0
        heif_suberror_End_of_data,
356
0
        kEmptyString
357
0
      };
358
0
    }
359
360
    // de265_push_NAL() takes the length as 'int'. Larger sizes would turn negative,
361
    // which libde265 < 1.1.3 does not reject before copying the data.
362
52.5k
    if (nal_size > static_cast<uint32_t>(std::numeric_limits<int>::max())) {
363
0
      return {
364
0
        heif_error_Decoder_plugin_error,
365
0
        heif_suberror_Unspecified,
366
0
        "NAL unit size exceeds 2 GB"
367
0
      };
368
0
    }
369
370
#if 0
371
    FILE* fh = fopen("data.h265", "a");
372
    fputc(0, fh);
373
    fputc(0, fh);
374
    fputc(1, fh);
375
    fwrite(cdata + ptr, nal_size, 1, fh);
376
    fclose(fh);
377
378
    printf("put nal with size %d %x\n", nal_size, *(cdata+ptr));
379
#endif
380
381
    // The codec hands this opaque pointer back unchanged; it carries an integer, not an address.
382
52.5k
    de265_error push_err = de265_push_NAL(decoder->ctx, cdata + ptr, nal_size, 0, (void*)user_data); // NOLINT(performance-no-int-to-ptr)
383
52.5k
    if (!de265_isOK(push_err)) {
384
0
      return {
385
0
        heif_error_Decoder_plugin_error,
386
0
        heif_suberror_Unspecified,
387
0
        de265_get_error_text(push_err)
388
0
      };
389
0
    }
390
391
52.5k
    ptr += nal_size;
392
52.5k
  }
393
394
  //de265_push_data(decoder->ctx, data, size, 0, nullptr);
395
396
9.07k
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
397
9.07k
}
398
399
static heif_error libde265_v1_push_data(void* decoder_raw, const void* data, size_t size)
400
0
{
401
0
  return libde265_v1_push_data2(decoder_raw, data, size, 0);
402
0
}
403
404
static heif_error libde265_flush_data(void* decoder_raw)
405
9.07k
{
406
9.07k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
407
408
9.07k
  de265_flush_data(decoder->ctx);
409
410
9.07k
  return heif_error_ok;
411
9.07k
}
412
413
414
415
static heif_error libde265_v1_decode_next_image2(void* decoder_raw,
416
                                                 heif_image** out_img,
417
                                                 uintptr_t* out_user_data,
418
                                                 const heif_security_limits* limits)
419
54.7k
{
420
54.7k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
421
54.7k
  heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
422
423
  // TODO(251119) : de265_flush_data(decoder->ctx);
424
425
  // TODO(farindk): Set "err" if no image was decoded.
426
54.7k
  int more;
427
54.7k
  de265_error decode_err;
428
54.7k
  *out_img = nullptr;
429
95.2k
  do {
430
95.2k
    more = 0;
431
95.2k
    decode_err = de265_decode(decoder->ctx, &more);
432
95.2k
    if (decode_err != DE265_OK) {
433
      // printf("Error decoding: %s (%d)\n", de265_get_error_text(decode_err), decode_err);
434
4.55k
      break;
435
4.55k
    }
436
437
    // TODO: read NCLX from h265 bitstream
438
439
90.7k
    const de265_image* image = de265_get_next_picture(decoder->ctx);
440
90.7k
    if (image) {
441
      // TODO(farindk): Should we return the first image instead?
442
8.20k
      if (*out_img) {
443
0
        heif_image_release(*out_img);
444
0
      }
445
446
8.20k
      if (out_user_data) {
447
0
        *out_user_data = (uintptr_t)de265_get_image_user_data(image);
448
0
      }
449
450
8.20k
      err = convert_libde265_image_to_heif_image(decoder, image, out_img, limits);
451
8.20k
      if (err.code != heif_error_Ok) {
452
731
        return err;
453
731
      }
454
455
7.47k
      heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc();
456
7.47k
#if LIBDE265_NUMERIC_VERSION >= 0x01000700
457
7.47k
      HEIF_WARN_OR_FAIL(decoder->strict_decoding, *out_img, heif_nclx_color_profile_set_color_primaries(nclx, static_cast<uint16_t>(de265_get_image_colour_primaries(image))),
458
7.47k
                        {
459
7.47k
                          heif_nclx_color_profile_free(nclx);
460
7.47k
                          heif_image_release(*out_img);
461
7.47k
                          *out_img = nullptr;
462
7.47k
                        });
463
7.47k
      HEIF_WARN_OR_FAIL(decoder->strict_decoding, *out_img, heif_nclx_color_profile_set_transfer_characteristics(nclx, static_cast<uint16_t>(de265_get_image_transfer_characteristics(image))),
464
7.47k
                        {
465
7.47k
                          heif_nclx_color_profile_free(nclx);
466
7.47k
                          heif_image_release(*out_img);
467
7.47k
                          *out_img = nullptr;
468
7.47k
                        });
469
7.47k
      HEIF_WARN_OR_FAIL(decoder->strict_decoding, *out_img, heif_nclx_color_profile_set_matrix_coefficients(nclx, static_cast<uint16_t>(de265_get_image_matrix_coefficients(image))),
470
7.47k
                        {
471
7.47k
                          heif_nclx_color_profile_free(nclx);
472
7.47k
                          heif_image_release(*out_img);
473
7.47k
                          *out_img = nullptr;
474
7.47k
                        });
475
6.08k
      nclx->full_range_flag = (bool) de265_get_image_full_range_flag(image);
476
6.08k
#endif
477
6.08k
      heif_image_set_nclx_color_profile(*out_img, nclx);
478
6.08k
      heif_nclx_color_profile_free(nclx);
479
480
6.08k
      de265_release_next_picture(decoder->ctx);
481
6.08k
      return heif_error_ok;
482
7.47k
    }
483
90.7k
  } while (more);
484
485
46.5k
  return err;
486
54.7k
}
487
488
489
static heif_error libde265_v1_decode_next_image(void* decoder_raw,
490
                                                heif_image** out_img,
491
                                                const heif_security_limits* limits)
492
0
{
493
0
  return libde265_v1_decode_next_image2(decoder_raw, out_img, nullptr, limits);
494
0
}
495
496
static heif_error libde265_v1_decode_image(void* decoder_raw,
497
                                           heif_image** out_img)
498
0
{
499
0
  auto* limits = heif_get_global_security_limits();
500
0
  return libde265_v1_decode_next_image(decoder_raw, out_img, limits);
501
0
}
502
503
#endif
504
505
506
#if LIBDE265_NUMERIC_VERSION >= 0x02000000
507
508
static const heif_decoder_plugin decoder_libde265
509
{
510
  1,
511
  libde265_plugin_name,
512
  libde265_init_plugin,
513
  libde265_deinit_plugin,
514
  libde265_does_support_format,
515
  libde265_new_decoder,
516
  libde265_free_decoder,
517
  libde265_v2_push_data,
518
  libde265_v2_decode_image,
519
  libde265_set_strict_decoding,
520
  "libde265",
521
  libde265_v2_decode_next_image
522
};
523
524
#else
525
526
static const heif_decoder_plugin decoder_libde265
527
    {
528
        5,
529
        libde265_plugin_name,
530
        libde265_init_plugin,
531
        libde265_deinit_plugin,
532
        libde265_does_support_format,
533
        libde265_new_decoder,
534
        libde265_free_decoder,
535
        libde265_v1_push_data,
536
        libde265_v1_decode_image,
537
        libde265_set_strict_decoding,
538
        "libde265",
539
        libde265_v1_decode_next_image,
540
        /* minimum_required_libheif_version */ LIBHEIF_MAKE_VERSION(1,21,0),
541
        libde265_does_support_format2,
542
        libde265_new_decoder2,
543
        libde265_v1_push_data2,
544
        libde265_flush_data,
545
        libde265_v1_decode_next_image2
546
    };
547
548
#endif
549
550
const heif_decoder_plugin* get_decoder_plugin_libde265()
551
22.8k
{
552
22.8k
  return &decoder_libde265;
553
22.8k
}
554
555
556
557
#if PLUGIN_LIBDE265
558
heif_plugin_info plugin_info {
559
  1,
560
  heif_plugin_type_decoder,
561
  &decoder_libde265
562
};
563
#endif