Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image/pixelimage.h
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
22
#ifndef LIBHEIF_IMAGE_H
23
#define LIBHEIF_IMAGE_H
24
25
#include "image_description.h"
26
#include "security_limits.h"
27
28
#include <vector>
29
#include <memory>
30
#include <set>
31
#include <utility>
32
#include <cassert>
33
34
constexpr uint16_t heif_cmpd_component_type_UNDEFINED = 0x7FFF;
35
36
37
heif_chroma chroma_from_subsampling(int h, int v);
38
39
uint32_t chroma_width(uint32_t w, heif_chroma chroma);
40
41
uint32_t chroma_height(uint32_t h, heif_chroma chroma);
42
43
uint32_t channel_width(uint32_t w, heif_chroma chroma, heif_channel channel);
44
45
uint32_t channel_height(uint32_t h, heif_chroma chroma, heif_channel channel);
46
47
bool is_interleaved_with_alpha(heif_chroma chroma);
48
49
int num_interleaved_components_per_plane(heif_chroma chroma);
50
51
bool is_integer_multiple_of_chroma_size(uint32_t width,
52
                                        uint32_t height,
53
                                        heif_chroma chroma);
54
55
// Returns the list of valid heif_chroma values for a given colorspace.
56
std::vector<heif_chroma> get_valid_chroma_values_for_colorspace(heif_colorspace colorspace);
57
58
59
60
// Number of bytes HeifPixelImage uses to store one sample of the given bit depth
61
// (1, 2, 4, 8 or 16). This is the single source of truth for the sample width: plane
62
// allocation uses it, and so does every code path that reinterprets plane memory as
63
// uint8_t/uint16_t samples (color conversion in particular), so that they cannot drift.
64
//
65
// A bit depth of 0 is what get_bits_per_pixel() reports for a plane that does not exist.
66
// It maps to 0 bytes, so that a missing plane can never pass for a valid one-byte plane
67
// at the call sites that compare the width against sizeof(uint8_t).
68
inline int bytes_per_sample_for_bit_depth(int bit_depth)
69
45.1k
{
70
45.1k
  if (bit_depth <= 0) {
71
0
    return 0;
72
0
  }
73
45.1k
  if (bit_depth <= 8) {
74
28.8k
    return 1;
75
28.8k
  }
76
16.3k
  if (bit_depth <= 16) {
77
16.3k
    return 2;
78
16.3k
  }
79
0
  if (bit_depth <= 32) {
80
0
    return 4;
81
0
  }
82
0
  if (bit_depth <= 64) {
83
0
    return 8;
84
0
  }
85
0
  return 16;
86
0
}
87
88
89
class HeifPixelImage : public std::enable_shared_from_this<HeifPixelImage>,
90
                       public ImageDescription,
91
                       public ErrorBuffer
92
{
93
public:
94
5.99k
  explicit HeifPixelImage() = default;
95
96
  ~HeifPixelImage() override;
97
98
  void create(uint32_t width, uint32_t height, heif_colorspace colorspace, heif_chroma chroma);
99
100
  Error create_clone_image_at_new_size(const std::shared_ptr<const HeifPixelImage>& source, uint32_t w, uint32_t h,
101
                                       const heif_security_limits* limits);
102
103
  Error add_channel(heif_channel channel, uint32_t width, uint32_t height, int bit_depth,
104
                  const heif_security_limits* limits,
105
                  heif_component_datatype datatype = heif_component_datatype_unsigned_integer);
106
107
  bool has_channel(heif_channel channel) const;
108
109
  // Has alpha information either as a separate channel or in the interleaved format.
110
  bool has_alpha() const;
111
112
  // Get logical image size. Individual components may vary.
113
2.56k
  uint32_t get_width() const { return m_width; }
114
115
  // Get logical image size. Individual components may vary.
116
2.55k
  uint32_t get_height() const { return m_height; }
117
118
  uint32_t get_width(heif_channel channel) const;
119
120
  uint32_t get_height(heif_channel channel) const;
121
122
  uint32_t get_width(uint32_t component_id) const;
123
124
  uint32_t get_height(uint32_t component_id) const;
125
126
0
  bool has_odd_width() const { return !!(m_width & 1); }
127
128
0
  bool has_odd_height() const { return !!(m_height & 1); }
129
130
  // Returns true if the "primary" pixel plane(s) have exactly the given size.
131
  // Which plane is "primary" depends on the colorspace:
132
  //   YCbCr / monochrome  -> the Y plane (Cb/Cr may be legitimately subsampled)
133
  //   RGB planar          -> R, G and B planes must all be present and all equal
134
  //   RGB interleaved     -> the interleaved plane
135
  //   filter_array        -> the filter_array plane
136
  //   undefined / custom  -> not checked here; returns true
137
  bool primary_planes_have_size(uint32_t width, uint32_t height) const;
138
139
  // Returns true if every plane actually stored in m_storage -- not just the
140
  // "primary" ones for the current colorspace -- has the size that plane's
141
  // channel is expected to have: m_width x m_height for Y/Alpha/R/G/B/
142
  // interleaved, and the chroma-subsampled size (via get_subsampled_size())
143
  // for Cb/Cr. Any other channel type fails the check. Unlike
144
  // primary_planes_have_size(), this checks each stored component directly
145
  // rather than going through get_width(channel)/get_height(channel) (which
146
  // only ever sees the first plane for a channel), so it also catches a
147
  // same-channel duplicate whose size doesn't match, not just a missing or
148
  // undersized single plane.
149
  bool has_standard_plane_sizes() const;
150
151
  // Checks that the stored planes are exactly those of the image's colorspace and chroma
152
  // format: the colour planes of that layout (R/G/B, Y/Cb/Cr, Y alone, the interleaved plane,
153
  // or the filter array), optionally one separate alpha plane on the planar layouts, each
154
  // present exactly once and with the size its channel implies (chroma-subsampled for Cb/Cr,
155
  // the image size otherwise). Planes with channel heif_channel_unknown carry multi-component
156
  // data without a colour meaning (e.g. the padding components of 'unci'); they are tolerated
157
  // and ignored. A custom colorspace has no defined layout and is rejected; a caller that
158
  // accepts such images (the uncompressed encoder) has to test for it first.
159
  //
160
  // This is a check at the entry of the color conversion pipeline and of the encoders, not a
161
  // constraint on HeifPixelImage itself: an image may hold any set of planes, including several
162
  // of the same channel (multi-spectral images consist of several monochrome planes).
163
  // Returns a Usage_error naming the offending plane.
164
  Error check_plane_layout() const;
165
166
  // Checks that no sample exceeds the value range of its plane's bit depth. A plane stores
167
  // its samples in whole bytes (e.g. 10-bit samples in 16-bit words), so the memory can hold
168
  // larger values than the bit depth allows, and the bit depth is only a promise of whoever
169
  // filled the plane. Code that derives table sizes or bit counts from the bit depth (third
170
  // party encoders, libsharpyuv) relies on that promise. Only planes with unsigned integer
171
  // samples are checked.
172
  // Like check_plane_layout(), this is a check for the places where pixel data enters from the
173
  // outside, not something HeifPixelImage enforces by itself.
174
  // Returns a Usage_error naming the offending plane.
175
  Error check_sample_value_ranges() const;
176
177
5.19k
  heif_chroma get_chroma_format() const { return m_chroma; }
178
179
3.38k
  heif_colorspace get_colorspace() const { return m_colorspace; }
180
181
  std::set<heif_channel> get_channel_set() const;
182
183
  uint16_t get_storage_bits_per_pixel(heif_channel channel) const;
184
185
  uint16_t get_bits_per_pixel(heif_channel channel) const;
186
187
  // Get the maximum bit depth of a visual channel (YCbCr or RGB).
188
  uint16_t get_visual_image_bits_per_pixel() const;
189
190
  heif_component_datatype get_datatype(heif_channel channel) const;
191
192
  int get_number_of_interleaved_components(heif_channel channel) const;
193
194
  // Note: we are using size_t as stride type since the stride is usually involved in a multiplication with the line number.
195
  //       For very large images (e.g. >2 GB), this can result in an integer overflow and corresponding illegal memory access.
196
  //       (see https://github.com/strukturag/libheif/issues/1419)
197
11.6k
  uint8_t* get_channel_memory(heif_channel channel, size_t* out_stride) { return get_channel_memory<uint8_t>(channel, out_stride); }
198
199
4.69k
  const uint8_t* get_channel_memory(heif_channel channel, size_t* out_stride) const { return get_channel_memory<uint8_t>(channel, out_stride); }
200
201
  template <typename T>
202
  T* get_channel_memory(heif_channel channel, size_t* out_stride)
203
16.3k
  {
204
16.3k
    auto* comp = find_storage_for_channel(channel);
205
16.3k
    if (!comp) {
206
0
      if (out_stride)
207
0
        *out_stride = 0;
208
209
0
      return nullptr;
210
0
    }
211
212
16.3k
    if (out_stride) {
213
16.3k
      *out_stride = static_cast<int>(comp->stride);
214
16.3k
    }
215
216
16.3k
    return static_cast<T*>(comp->mem);
217
16.3k
  }
unsigned char* HeifPixelImage::get_channel_memory<unsigned char>(heif_channel, unsigned long*)
Line
Count
Source
203
16.3k
  {
204
16.3k
    auto* comp = find_storage_for_channel(channel);
205
16.3k
    if (!comp) {
206
0
      if (out_stride)
207
0
        *out_stride = 0;
208
209
0
      return nullptr;
210
0
    }
211
212
16.3k
    if (out_stride) {
213
16.3k
      *out_stride = static_cast<int>(comp->stride);
214
16.3k
    }
215
216
16.3k
    return static_cast<T*>(comp->mem);
217
16.3k
  }
unsigned short* HeifPixelImage::get_channel_memory<unsigned short>(heif_channel, unsigned long*)
Line
Count
Source
203
2
  {
204
2
    auto* comp = find_storage_for_channel(channel);
205
2
    if (!comp) {
206
0
      if (out_stride)
207
0
        *out_stride = 0;
208
209
0
      return nullptr;
210
0
    }
211
212
2
    if (out_stride) {
213
2
      *out_stride = static_cast<int>(comp->stride);
214
2
    }
215
216
2
    return static_cast<T*>(comp->mem);
217
2
  }
218
219
  template <typename T>
220
  const T* get_channel_memory(heif_channel channel, size_t* out_stride) const
221
4.69k
  {
222
4.69k
    return const_cast<HeifPixelImage*>(this)->get_channel_memory<T>(channel, out_stride);
223
4.69k
  }
224
225
226
  // --- id-based component access (for ISO 23001-17 multi-component images)
227
228
0
  uint32_t get_number_of_used_components() const { return static_cast<uint32_t>(get_component_descriptions().size()); }
229
230
  //uint32_t get_total_number_of_cmpd_components() const { return static_cast<uint32_t>(m_cmpd_component_types.size()); }
231
232
  heif_channel get_component_channel(uint32_t component_id) const;
233
234
  uint32_t get_component_width(uint32_t component_id) const;
235
  uint32_t get_component_height(uint32_t component_id) const;
236
  uint16_t get_component_bits_per_pixel(uint32_t component_id) const;
237
  uint16_t get_component_storage_bits_per_pixel(uint32_t component_id) const;
238
  heif_component_datatype get_component_datatype(uint32_t component_id) const;
239
240
  // Look up the component type from the cmpd table. Works for any cmpd index,
241
  // even those that have no image plane (e.g. bayer reference components).
242
  uint16_t get_component_type(uint32_t component_id) const;
243
244
  //std::vector<uint16_t> get_cmpd_component_types() const { return m_cmpd_component_types; }
245
246
  std::vector<uint32_t> get_component_ids_interleaved() const;
247
248
  //uint32_t get_component_cmpd_index() const { assert(m_cmpd_component_types.size()==1); return m_cmpd_component_types[0]; }
249
250
  // Encoder path: auto-generates component_index by appending to cmpd table.
251
  Result<uint32_t> add_component(uint32_t width, uint32_t height,
252
                                 uint16_t component_type,
253
                                 heif_component_datatype datatype, int bit_depth,
254
                                 const heif_security_limits* limits);
255
256
  // TODO: replace uint16_t component_type with class that also handled the std::string type
257
  uint32_t add_component_without_data(uint16_t component_type);
258
259
  // Decoder path: copy the per-component description from a source
260
  // ImageDescription (typically the ImageItem the decoder is about to populate).
261
  // After this, allocate_buffer_for_component() can be called with each id
262
  // already present in m_components to allocate its pixel buffer.
263
  void clone_component_descriptions_from(const ImageDescription& src);
264
265
  // Post-decode reconciliation: rebind this image's component descriptions
266
  // and the m_component_ids on each plane so they match `src` (typically the
267
  // ImageItem). Used after a plugin-based codec decode returns: the plugin
268
  // auto-minted ids via the public C API (heif_image_add_plane_safe), but
269
  // the handle has a canonical description list we want to expose.
270
  // Channels in `src` overwrite this image's matching descriptions.
271
  // Channels present in this image but not in `src` (e.g. alpha-from-aux
272
  // attached after main decode) are kept under fresh ids that won't collide
273
  // with `src`. No-op if `src` has no descriptions.
274
  void apply_descriptions_from(const ImageDescription& src);
275
276
  // Decoder path: allocate a pixel buffer for a component whose description
277
  // (channel, datatype, bit_depth, width, height) is already in m_components.
278
  // No new id is minted. Used after clone_component_descriptions_from().
279
  Error allocate_buffer_for_component(uint32_t component_id,
280
                                      const heif_security_limits* limits);
281
282
  // Decoder path: uses a pre-populated cmpd table to look up the component type.
283
#if 0
284
  Result<uint32_t> add_component_for_index(uint32_t component_index,
285
                                            uint32_t width, uint32_t height,
286
                                            heif_component_datatype datatype, int bit_depth,
287
                                            const heif_security_limits* limits);
288
#endif
289
290
  // Populate the cmpd component types table (decoder path).
291
  //void set_cmpd_component_types(std::vector<uint16_t> types) { m_cmpd_component_types = std::move(types); }
292
293
  //const std::vector<uint16_t>& get_cmpd_component_types() { return m_cmpd_component_types; }
294
295
  // Returns the component ids of all components, in component-description
296
  // order. This includes reference components that have no pixel plane
297
  // (has_data_plane == false); the result size always equals
298
  // get_number_of_used_components().
299
  std::vector<uint32_t> get_used_component_ids() const;
300
301
  std::vector<uint32_t> get_used_planar_component_ids() const;
302
303
  uint8_t* get_component(uint32_t component_id, size_t* out_stride);
304
  const uint8_t* get_component(uint32_t component_id, size_t* out_stride) const;
305
306
  template <typename T>
307
  T* get_component_memory(uint32_t component_id, size_t* out_stride)
308
0
  {
309
0
    auto* comp = find_storage_for_component(component_id);
310
0
    if (!comp) {
311
0
      if (out_stride) *out_stride = 0;
312
0
      return nullptr;
313
0
    }
314
315
0
    if (out_stride) {
316
0
      *out_stride = comp->stride;
317
0
    }
318
0
    return static_cast<T*>(comp->mem);
319
0
  }
320
321
  template <typename T>
322
  const T* get_component_memory(uint32_t component_id, size_t* out_stride) const
323
0
  {
324
0
    return const_cast<HeifPixelImage*>(this)->get_component_memory<T>(component_id, out_stride);
325
0
  }
326
327
  Error copy_new_channel_from(const std::shared_ptr<const HeifPixelImage>& src_image,
328
                            heif_channel src_channel,
329
                            heif_channel dst_channel,
330
                            const heif_security_limits* limits);
331
332
  Error extract_alpha_from_RGBA(const std::shared_ptr<const HeifPixelImage>& srcimage, const heif_security_limits* limits);
333
334
  // Sets all samples of the plane to 'value'. The plane may have up to 64 bits per sample;
335
  // the value is written with the width of the plane's storage word (8, 16, 32 or 64 bits).
336
  // The datatype of the plane is not considered: the caller has to pass a value that is
337
  // meaningful for it.
338
  // Returns an error if there is no such plane or if it has more than 64 bits per sample.
339
  Error fill_channel(heif_channel dst_channel, uint64_t value);
340
341
  Error fill_new_channel(heif_channel dst_channel, uint64_t value, int width, int height, int bpp, const heif_security_limits* limits);
342
343
  Error transfer_channel_from_image_as(const std::shared_ptr<HeifPixelImage>& source,
344
                                    heif_channel src_channel,
345
                                    heif_channel dst_channel);
346
347
  Error copy_image_to(const std::shared_ptr<const HeifPixelImage>& source, uint32_t x0, uint32_t y0);
348
349
  Result<std::shared_ptr<HeifPixelImage>> rotate_ccw(int angle_degrees, const heif_security_limits* limits);
350
351
  Result<std::shared_ptr<HeifPixelImage>> mirror_inplace(heif_transform_mirror_direction, const heif_security_limits* limits);
352
353
  Result<std::shared_ptr<HeifPixelImage>> crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
354
                                               const heif_security_limits* limits) const;
355
356
  Error fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_t a);
357
358
  Error overlay(std::shared_ptr<HeifPixelImage>& overlay, int32_t dx, int32_t dy);
359
360
  Error scale_nearest_neighbor(std::shared_ptr<HeifPixelImage>& output, uint32_t width, uint32_t height,
361
                               const heif_security_limits* limits) const;
362
363
364
  void debug_dump() const;
365
366
  Error extend_padding_to_size(uint32_t width, uint32_t height, bool adjust_size,
367
                               const heif_security_limits* limits);
368
369
  Error extend_to_size_with_zero(uint32_t width, uint32_t height, const heif_security_limits* limits);
370
371
  Result<std::shared_ptr<HeifPixelImage>> extract_image_area(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h,
372
                                                             const heif_security_limits* limits) const;
373
374
375
  // --- warnings
376
377
28
  void add_warning(Error warning) { m_warnings.emplace_back(std::move(warning)); }
378
379
833
  void add_warnings(const std::vector<Error>& warning) { for (const auto& err : warning) m_warnings.emplace_back(err); }
380
381
1.68k
  const std::vector<Error>& get_warnings() const { return m_warnings; }
382
383
private:
384
  struct ComponentStorage
385
  {
386
    heif_channel m_channel = heif_channel_Y;
387
388
    // index into the cmpd component definition table
389
    // Interleaved channels will have a list of indices in the order R,G,B,A
390
    std::vector<uint32_t> m_component_ids;
391
392
    // limits=nullptr disables the limits
393
    Error alloc(uint32_t width, uint32_t height, heif_component_datatype datatype, int bit_depth,
394
                int num_interleaved_components,
395
                const heif_security_limits* limits,
396
                MemoryHandle& memory_handle);
397
398
    heif_component_datatype m_datatype = heif_component_datatype_unsigned_integer;
399
400
    // logical bit depth per component
401
    // For interleaved formats, it is the number of bits for one component.
402
    // It is not the storage width.
403
    uint16_t m_bit_depth = 0; // 1-256
404
    uint8_t m_num_interleaved_components = 1;
405
406
    // Cached at alloc() time so get_bytes_per_pixel() doesn't have to do
407
    // the bit-depth → bytes ladder on every call. Equal to
408
    // bytes_per_component * m_num_interleaved_components.
409
    uint8_t m_bytes_per_pixel = 0;
410
411
    // the "visible" area of the component
412
    uint32_t m_width = 0;
413
    uint32_t m_height = 0;
414
415
    // the allocated memory size
416
    uint32_t m_mem_width = 0;
417
    uint32_t m_mem_height = 0;
418
419
    void* mem = nullptr; // aligned memory start
420
    uint8_t* allocated_mem = nullptr; // unaligned memory we allocated
421
    size_t   allocation_size = 0;
422
    size_t   stride = 0; // bytes per line
423
424
    int get_bytes_per_pixel() const;
425
426
    template <typename T> void mirror_inplace(heif_transform_mirror_direction);
427
428
    template <typename T> void fill(T value);
429
430
    template<typename T>
431
    void rotate_ccw(int angle_degrees, ComponentStorage& out_plane) const;
432
433
    void crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom, int bytes_per_pixel, ComponentStorage& out_plane) const;
434
  };
435
436
  ComponentStorage* find_storage_for_channel(heif_channel channel);
437
  const ComponentStorage* find_storage_for_channel(heif_channel channel) const;
438
439
  ComponentStorage* find_storage_for_component(uint32_t component_id);
440
  const ComponentStorage* find_storage_for_component(uint32_t component_id) const;
441
442
  // After plane.alloc() has succeeded, mints fresh component ids, appends
443
  // them to plane.m_component_ids, and pushes fully-populated
444
  // ComponentDescription entries for each component_type. Channel, datatype,
445
  // bit_depth, width and height are read from `plane`. Must only be called
446
  // once allocation has succeeded so that no descriptions are registered for
447
  // a plane that failed to materialize.
448
  void register_component_descriptions(ComponentStorage& plane,
449
                                       const std::vector<uint16_t>& component_types);
450
451
  // Overload that clones existing ComponentDescriptions (preserving
452
  // component_type, gimi_content_id, has_data_plane, and any other per-id
453
  // metadata). Geometry/datatype/bit_depth/channel fields are overwritten
454
  // from `plane`; component_ids are freshly minted on this image. Use this
455
  // when allocating a new plane that mirrors an existing one (e.g.
456
  // geometry-preserving transforms like rotation and crop).
457
  void register_component_descriptions(ComponentStorage& plane,
458
                                       const std::vector<const ComponentDescription*>& source_descriptions);
459
460
  uint32_t m_width = 0;
461
  uint32_t m_height = 0;
462
  heif_colorspace m_colorspace = heif_colorspace_undefined;
463
  heif_chroma m_chroma = heif_chroma_undefined;
464
465
  std::vector<ComponentStorage> m_storage;
466
  MemoryHandle m_memory_handle;
467
468
  std::vector<Error> m_warnings;
469
};
470
471
#endif