Coverage Report

Created: 2026-10-28 07:26

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
9
#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
9
{
53
9
  strcpy(plugin_name, "libde265 HEVC decoder");
54
55
9
  const char* libde265_version = de265_get_version();
56
57
9
  if (strlen(plugin_name) + strlen(libde265_version) + (sizeof(version_prefix) - 1) < MAX_PLUGIN_NAME_LENGTH) {
58
9
    strcat(plugin_name, version_prefix);
59
9
    strcat(plugin_name, libde265_version);
60
9
  }
61
62
9
  return plugin_name;
63
9
}
64
65
66
static void libde265_init_plugin()
67
254
{
68
254
  de265_init();
69
254
}
70
71
72
static void libde265_deinit_plugin()
73
0
{
74
0
  de265_free();
75
0
}
76
77
78
static int libde265_does_support_format(heif_compression_format format)
79
78.8k
{
80
78.8k
  if (format == heif_compression_HEVC) {
81
20.4k
    return LIBDE265_PLUGIN_PRIORITY;
82
20.4k
  }
83
58.4k
  else {
84
58.4k
    return 0;
85
58.4k
  }
86
78.8k
}
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
13.7k
{
102
13.7k
  bool is_mono = (de265_get_chroma_format(de265img) == de265_chroma_mono);
103
104
13.7k
  heif_error err;
105
13.7k
  err = heif_image_create(de265_get_image_width(de265img, 0),
106
13.7k
                          de265_get_image_height(de265img, 0),
107
13.7k
                          is_mono ? heif_colorspace_monochrome : heif_colorspace_YCbCr,
108
13.7k
                          (heif_chroma) de265_get_chroma_format(de265img),
109
13.7k
                          image);
110
13.7k
  if (err.code) {
111
0
    return err;
112
0
  }
113
114
  // --- transfer data from de265_image to HeifPixelImage
115
116
13.7k
  heif_channel channel2plane[3] = {
117
13.7k
      heif_channel_Y,
118
13.7k
      heif_channel_Cb,
119
13.7k
      heif_channel_Cr
120
13.7k
  };
121
122
123
13.7k
  int bpp = de265_get_bits_per_pixel(de265img, 0);
124
125
13.7k
  int num_planes = (is_mono ? 1 : 3);
126
127
43.8k
  for (int c = 0; c < num_planes; c++) {
128
34.1k
    if (de265_get_bits_per_pixel(de265img, c) != bpp) {
129
3.94k
      heif_image_release(*image);
130
3.94k
      err = {heif_error_Unsupported_feature,
131
3.94k
             heif_suberror_Unsupported_color_conversion,
132
3.94k
             "Channels with different number of bits per pixel are not supported"};
133
3.94k
      return err;
134
3.94k
    }
135
136
30.1k
    int stride;
137
30.1k
    const uint8_t* data = de265_get_image_plane(de265img, c, &stride);
138
139
30.1k
    int w = de265_get_image_width(de265img, c);
140
30.1k
    int h = de265_get_image_height(de265img, c);
141
30.1k
    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
30.1k
    err = heif_image_add_plane_safe(*image, channel2plane[c], w,h, bpp, limits);
150
30.1k
    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
30.1k
    size_t dst_stride;
160
30.1k
    uint8_t* dst_mem = heif_image_get_plane2(*image, channel2plane[c], &dst_stride);
161
162
30.1k
    int bytes_per_pixel = (bpp + 7) / 8;
163
164
6.13M
    for (int y = 0; y < h; y++) {
165
6.10M
      memcpy(dst_mem + y * dst_stride, data + static_cast<size_t>(y) * stride, static_cast<size_t>(w) * bytes_per_pixel);
166
6.10M
    }
167
30.1k
  }
168
169
170
9.76k
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
171
13.7k
}
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
16.1k
{
178
16.1k
  libde265_decoder* decoder = new libde265_decoder();
179
16.1k
  heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
180
181
16.1k
  decoder->ctx = de265_new_decoder();
182
183
16.1k
#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
16.1k
  {
190
16.1k
    const heif_security_limits* limits = options->limits ? options->limits : heif_get_global_security_limits();
191
16.1k
    de265_security_limits* de265_limits = de265_get_security_limits(decoder->ctx);
192
16.1k
    if (limits->max_image_size_pixels > std::numeric_limits<uint32_t>::max()) {
193
0
      de265_limits->max_image_size_pixels = 0;
194
0
    }
195
16.1k
    else {
196
16.1k
      de265_limits->max_image_size_pixels = static_cast<uint32_t>(limits->max_image_size_pixels);
197
16.1k
    }
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
16.1k
    if (limits->max_memory_block_size > std::numeric_limits<uint32_t>::max()) {
204
0
      de265_limits->max_NAL_size_bytes = 0;
205
0
    }
206
16.1k
    else {
207
16.1k
      de265_limits->max_NAL_size_bytes = static_cast<uint32_t>(limits->max_memory_block_size);
208
16.1k
    }
209
16.1k
  }
210
16.1k
#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
16.1k
  int nThreads = (options->num_threads ? options->num_threads : 1);
218
219
  // Worker threads are not supported when running on Emscripten.
220
16.1k
  de265_start_worker_threads(decoder->ctx, nThreads);
221
16.1k
#endif
222
223
16.1k
  decoder->strict_decoding = options->strict_decoding;
224
225
16.1k
  *dec = decoder;
226
16.1k
  return err;
227
16.1k
}
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
16.1k
{
242
16.1k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
243
244
16.1k
  de265_error err = de265_free_decoder(decoder->ctx);
245
16.1k
  (void) err;
246
247
16.1k
  delete decoder;
248
16.1k
}
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
16.1k
{
335
16.1k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
336
337
16.1k
  const uint8_t* cdata = (const uint8_t*) data;
338
339
16.1k
  size_t ptr = 0;
340
80.8k
  while (ptr < size) {
341
64.6k
    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
64.6k
    uint32_t nal_size = static_cast<uint32_t>((cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]));
350
64.6k
    ptr += 4;
351
352
64.6k
    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
64.6k
    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
64.6k
    de265_error push_err = de265_push_NAL(decoder->ctx, cdata + ptr, nal_size, 0, (void*)user_data); // NOLINT(performance-no-int-to-ptr)
383
64.6k
    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
64.6k
    ptr += nal_size;
392
64.6k
  }
393
394
  //de265_push_data(decoder->ctx, data, size, 0, nullptr);
395
396
16.1k
  return {heif_error_Ok, heif_suberror_Unspecified, kSuccess};
397
16.1k
}
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
16.1k
{
406
16.1k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
407
408
16.1k
  de265_flush_data(decoder->ctx);
409
410
16.1k
  return heif_error_ok;
411
16.1k
}
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
139k
{
420
139k
  libde265_decoder* decoder = (libde265_decoder*) decoder_raw;
421
139k
  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
139k
  int more;
427
139k
  de265_error decode_err;
428
139k
  *out_img = nullptr;
429
185k
  do {
430
185k
    more = 0;
431
185k
    decode_err = de265_decode(decoder->ctx, &more);
432
185k
    if (decode_err != DE265_OK) {
433
      // printf("Error decoding: %s (%d)\n", de265_get_error_text(decode_err), decode_err);
434
5.20k
      break;
435
5.20k
    }
436
437
    // TODO: read NCLX from h265 bitstream
438
439
180k
    const de265_image* image = de265_get_next_picture(decoder->ctx);
440
180k
    if (image) {
441
      // TODO(farindk): Should we return the first image instead?
442
13.7k
      if (*out_img) {
443
0
        heif_image_release(*out_img);
444
0
      }
445
446
13.7k
      if (out_user_data) {
447
0
        *out_user_data = (uintptr_t)de265_get_image_user_data(image);
448
0
      }
449
450
13.7k
      err = convert_libde265_image_to_heif_image(decoder, image, out_img, limits);
451
13.7k
      if (err.code != heif_error_Ok) {
452
3.94k
        return err;
453
3.94k
      }
454
455
9.76k
      heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc();
456
9.76k
#if LIBDE265_NUMERIC_VERSION >= 0x01000700
457
9.76k
      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
9.76k
                        {
459
9.76k
                          heif_nclx_color_profile_free(nclx);
460
9.76k
                          heif_image_release(*out_img);
461
9.76k
                          *out_img = nullptr;
462
9.76k
                        });
463
9.76k
      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
9.76k
                        {
465
9.76k
                          heif_nclx_color_profile_free(nclx);
466
9.76k
                          heif_image_release(*out_img);
467
9.76k
                          *out_img = nullptr;
468
9.76k
                        });
469
9.76k
      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
9.76k
                        {
471
9.76k
                          heif_nclx_color_profile_free(nclx);
472
9.76k
                          heif_image_release(*out_img);
473
9.76k
                          *out_img = nullptr;
474
9.76k
                        });
475
9.76k
      nclx->full_range_flag = (bool) de265_get_image_full_range_flag(image);
476
9.76k
#endif
477
9.76k
      heif_image_set_nclx_color_profile(*out_img, nclx);
478
9.76k
      heif_nclx_color_profile_free(nclx);
479
480
9.76k
      de265_release_next_picture(decoder->ctx);
481
9.76k
      return heif_error_ok;
482
9.76k
    }
483
180k
  } while (more);
484
485
125k
  return err;
486
139k
}
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
254
{
552
254
  return &decoder_libde265;
553
254
}
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