Coverage Report

Created: 2026-06-16 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image/image_description.h
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2026 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
#ifndef LIBHEIF_IMAGE_DESCRIPTION_H
22
#define LIBHEIF_IMAGE_DESCRIPTION_H
23
24
#include "error.h"
25
#include "nclx.h"
26
#include <libheif/heif_experimental.h>
27
#include <libheif/heif_uncompressed.h>
28
#include "omaf_boxes.h"
29
30
#include <cassert>
31
#include <map>
32
#include <memory>
33
#include <optional>
34
#include <string>
35
#include <vector>
36
#include <algorithm>
37
#include <utility>
38
39
40
// === === Bayer pattern
41
42
// --- Bayer pattern expressed as ISO 23001-17 cmpd component indices
43
44
struct BayerPatternPixelCmpd
45
{
46
  uint32_t cmpd_index;
47
  float component_gain;
48
};
49
50
struct BayerPatternCmpd
51
{
52
  uint16_t pattern_width = 0;
53
  uint16_t pattern_height = 0;
54
  std::vector<BayerPatternPixelCmpd> pixels;
55
};
56
57
// --- Bayer pattern expressed as libheif components IDs
58
59
struct BayerPattern
60
{
61
  uint16_t pattern_width = 0;
62
  uint16_t pattern_height = 0;
63
  std::vector<heif_bayer_pattern_pixel> pixels;
64
65
  [[nodiscard]] BayerPatternCmpd resolve_to_cmpd(std::map<uint32_t, uint32_t> comp_id_to_cmpd) const;
66
};
67
68
69
// === Polarization pattern (ISO 23001-17)
70
71
struct PolarizationPattern
72
{
73
  std::vector<uint32_t> component_ids;  // empty = applies to all components. Either cmpd-index or component-id, depending on context.
74
  uint16_t pattern_width = 0;
75
  uint16_t pattern_height = 0;
76
  std::vector<float> polarization_angles;   // pattern_width * pattern_height entries
77
                                            // 0xFFFFFFFF bit-pattern (NaN) = no polarization filter
78
};
79
80
81
// === Sensor bad pixels map (ISO 23001-17)
82
83
struct SensorBadPixelsMap
84
{
85
  struct BadPixelCoordinate
86
  {
87
    uint32_t row, column;
88
  };
89
90
  std::vector<uint32_t> component_ids;  // empty = applies to all components. Either cmpd-index or component-id, depending on context.
91
  bool correction_applied = false;
92
  std::vector<uint32_t> bad_rows;
93
  std::vector<uint32_t> bad_columns;
94
  std::vector<BadPixelCoordinate> bad_pixels;
95
};
96
97
98
// === Sensor non-uniformity correction (ISO 23001-17)
99
100
struct SensorNonUniformityCorrection
101
{
102
  std::vector<uint32_t> component_ids;  // empty = applies to all components. Either cmpd-index or component-id, depending on context.
103
  bool nuc_is_applied = false;
104
  uint32_t image_width = 0;
105
  uint32_t image_height = 0;
106
  std::vector<float> nuc_gains;    // image_width * image_height entries
107
  std::vector<float> nuc_offsets;  // image_width * image_height entries
108
};
109
110
111
112
// === Image components
113
114
// Given a list of libheif component IDs, map them to a list of cmpd IDs through the given map.
115
// The output does not contain duplicates and is in no particular order.
116
std::vector<uint32_t> map_component_ids_to_cmpd(const std::vector<uint32_t>& component_ids, const std::map<uint32_t, uint32_t>& comp_id_to_cmpd);
117
118
// Map a list of unci component indices to libheif components.
119
// Use to find the libheif components for a metadata box that assigns it through cmpd indices.
120
// The output does not contain duplicates and is in no particular order.
121
std::vector<uint32_t> map_cmpd_to_component_ids(const std::vector<uint32_t>& cmpd_indices, const std::vector<std::vector<uint32_t>>& cmpd_to_comp_ids);
122
123
// Find the closest matching heif_channel for an ISO 23001-17 component type.
124
// Returns heif_channel_unknown if no good mapping exists.
125
heif_channel map_uncompressed_component_to_channel(uint16_t component_type);
126
127
128
// Per-component description, independent of pixel data.
129
// Lives on ImageDescription so both ImageItem (handle side, before decoding)
130
// and HeifPixelImage (decoded side) can carry the same structural view.
131
struct ComponentDescription
132
{
133
  uint32_t component_id = 0;             // stable id, matches HeifPixelImage::m_storage ids
134
135
  heif_channel channel = heif_channel_unknown;
136
  uint16_t component_type = 0;           // heif_cmpd_component_type_*
137
138
  // The numeric values of heif_component_datatype are aligned with the
139
  // ISO 23001-17 Table 2 component_format byte (from the uncC box), so on
140
  // the unci read path this field directly holds the file byte and on the
141
  // write path it is emitted as the file byte. For non-unci codecs
142
  // (HEVC/AVIF/JPEG/...) the codec sets this to the appropriate value
143
  // (typically heif_component_datatype_unsigned_integer).
144
  heif_component_datatype datatype = heif_component_datatype_undefined;
145
146
  uint16_t bit_depth = 0; // logical bit depth (1..256)
147
  uint32_t width = 0;
148
  uint32_t height = 0;
149
  bool has_data_plane = true; // false for cpat reference colors
150
151
  // Empty string means "no content id assigned".
152
  std::string gimi_content_id;
153
};
154
155
156
class ImageDescription
157
{
158
public:
159
  virtual ~ImageDescription();
160
161
  // TODO: Decide who is responsible for writing the colr boxes.
162
  //       Currently it is distributed over various places.
163
  //       Either here, in image_item.cc or in grid.cc.
164
  std::vector<std::shared_ptr<Box>> generate_property_boxes(bool generate_colr_boxes) const;
165
166
167
  // --- color profile
168
169
  bool has_nclx_color_profile() const;
170
171
76.0k
  virtual void set_color_profile_nclx(const nclx_profile& profile) { m_color_profile_nclx = profile; }
172
173
264k
  nclx_profile get_color_profile_nclx() const { return m_color_profile_nclx; }
174
175
  // get the stored nclx fallback or return the default nclx if none is stored
176
  nclx_profile get_color_profile_nclx_with_fallback() const;
177
178
18.5k
  virtual void set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile) { m_color_profile_icc = profile; }
179
180
15.7k
  bool has_icc_color_profile() const { return m_color_profile_icc != nullptr; }
181
182
108k
  const std::shared_ptr<const color_profile_raw>& get_color_profile_icc() const { return m_color_profile_icc; }
183
184
  void set_color_profile(const std::shared_ptr<const color_profile>& profile)
185
33.1k
  {
186
33.1k
    auto icc = std::dynamic_pointer_cast<const color_profile_raw>(profile);
187
33.1k
    if (icc) {
188
15.7k
      set_color_profile_icc(icc);
189
15.7k
    }
190
191
33.1k
    auto nclx = std::dynamic_pointer_cast<const color_profile_nclx>(profile);
192
33.1k
    if (nclx) {
193
17.3k
      set_color_profile_nclx(nclx->get_nclx_color_profile());
194
17.3k
    }
195
33.1k
  }
196
197
198
  // --- premultiplied alpha
199
200
0
  bool is_premultiplied_alpha() const { return m_premultiplied_alpha; }
201
202
0
  virtual void set_premultiplied_alpha(bool flag) { m_premultiplied_alpha = flag; }
203
204
205
  // --- pixel aspect ratio
206
207
0
  bool has_nonsquare_pixel_ratio() const { return m_PixelAspectRatio_h != m_PixelAspectRatio_v; }
208
209
  void get_pixel_ratio(uint32_t* h, uint32_t* v) const
210
0
  {
211
0
    *h = m_PixelAspectRatio_h;
212
0
    *v = m_PixelAspectRatio_v;
213
0
  }
214
215
  virtual void set_pixel_ratio(uint32_t h, uint32_t v)
216
261
  {
217
261
    m_PixelAspectRatio_h = h;
218
261
    m_PixelAspectRatio_v = v;
219
261
  }
220
221
  // --- clli
222
223
0
  bool has_clli() const { return m_clli.max_content_light_level != 0 || m_clli.max_pic_average_light_level != 0; }
224
225
0
  heif_content_light_level get_clli() const { return m_clli; }
226
227
1
  virtual void set_clli(const heif_content_light_level& clli) { m_clli = clli; }
228
229
  // --- mdcv
230
231
0
  bool has_mdcv() const { return m_mdcv.has_value(); }
232
233
0
  heif_mastering_display_colour_volume get_mdcv() const { return *m_mdcv; }
234
235
  virtual void set_mdcv(const heif_mastering_display_colour_volume& mdcv)
236
0
  {
237
0
    m_mdcv = mdcv;
238
0
  }
239
240
0
  void unset_mdcv() { m_mdcv.reset(); }
241
242
  // --- amve (ambient viewing environment)
243
244
0
  bool has_amve() const { return m_amve.has_value(); }
245
246
0
  heif_ambient_viewing_environment get_amve() const { return *m_amve; }
247
248
  virtual void set_amve(const heif_ambient_viewing_environment& amve)
249
0
  {
250
0
    m_amve = amve;
251
0
  }
252
253
0
  void unset_amve() { m_amve.reset(); }
254
255
  // --- ndwt (nominal diffuse white)
256
257
  // Note: a luminance of 0 is a valid value (it selects the ISO/TS 22028-5
258
  // default), so presence is tracked separately from the value via std::optional.
259
260
0
  bool has_nominal_diffuse_white() const { return m_nominal_diffuse_white_luminance.has_value(); }
261
262
  // Nominal diffuse white luminance in units of 0.0001 cd/m^2.
263
0
  uint32_t get_nominal_diffuse_white_luminance() const { return m_nominal_diffuse_white_luminance.value_or(0); }
264
265
  virtual void set_nominal_diffuse_white_luminance(uint32_t luminance)
266
0
  {
267
0
    m_nominal_diffuse_white_luminance = luminance;
268
0
  }
269
270
0
  void unset_nominal_diffuse_white() { m_nominal_diffuse_white_luminance.reset(); }
271
272
0
  virtual Error set_tai_timestamp(const heif_tai_timestamp_packet* tai) {
273
0
    delete m_tai_timestamp;
274
275
0
    m_tai_timestamp = heif_tai_timestamp_packet_alloc();
276
0
    heif_tai_timestamp_packet_copy(m_tai_timestamp, tai);
277
0
    return Error::Ok;
278
0
  }
279
280
0
  [[nodiscard]] const heif_tai_timestamp_packet* get_tai_timestamp() const {
281
0
    return m_tai_timestamp;
282
0
  }
283
284
  // --- GIMI content ID
285
286
0
  virtual void set_gimi_sample_content_id(std::string id) { m_gimi_sample_content_id = std::move(id); }
287
288
0
  [[nodiscard]] bool has_gimi_sample_content_id() const { return !m_gimi_sample_content_id.empty(); }
289
290
0
  [[nodiscard]] const std::string& get_gimi_sample_content_id() const { return m_gimi_sample_content_id; }
291
292
293
  [[nodiscard]] bool has_component_content_ids() const
294
0
  {
295
0
    return std::any_of(m_components.begin(), m_components.end(),
296
0
                       [](const ComponentDescription& c) { return !c.gimi_content_id.empty(); });
297
0
  }
298
299
  // Returns a positional vector: entry i is m_components[i].gimi_content_id
300
  // (empty string if unset). Returned by value because it is reconstructed.
301
  [[nodiscard]] std::vector<std::string> get_component_content_ids() const
302
0
  {
303
0
    std::vector<std::string> ids;
304
0
    ids.reserve(m_components.size());
305
0
    for (const auto& c : m_components) {
306
0
      ids.push_back(c.gimi_content_id);
307
0
    }
308
0
    return ids;
309
0
  }
310
311
312
  // --- per-component descriptions (id-based)
313
  // These describe each component independent of pixel storage. Both ImageItem
314
  // (before decoding) and HeifPixelImage (after decoding) carry the same list.
315
316
243k
  [[nodiscard]] const std::vector<ComponentDescription>& get_component_descriptions() const { return m_components; }
317
318
  // Append a ComponentDescription. The caller is expected to have set
319
  // component_id, either from a fresh mint_component_id() call or by
320
  // matching an id already used by a parallel structure (e.g.
321
  // HeifPixelImage::ComponentStorage::m_component_ids).
322
  void add_component_description(ComponentDescription desc)
323
392k
  {
324
392k
    m_components.push_back(std::move(desc));
325
392k
  }
326
327
  // Bulk-replace the component descriptions and sync the next-id allocator.
328
  // Used by clone/apply paths that rebuild the whole list at once.
329
  void set_component_descriptions(std::vector<ComponentDescription> components, uint32_t next_id)
330
3.63k
  {
331
3.63k
    m_components = std::move(components);
332
3.63k
    m_next_component_id = next_id;
333
3.63k
  }
334
335
  // Erase the description matching this component_id. Returns true if one was
336
  // removed.
337
  bool remove_component_description(uint32_t component_id)
338
48
  {
339
48
    auto it = std::find_if(m_components.begin(), m_components.end(),
340
48
                           [component_id](const ComponentDescription& c) {
341
48
                             return c.component_id == component_id;
342
48
                           });
343
48
    if (it == m_components.end()) return false;
344
48
    m_components.erase(it);
345
48
    return true;
346
48
  }
347
348
  // Mint a fresh component id (monotonically increasing, starting at 1).
349
392k
  [[nodiscard]] uint32_t mint_component_id() { return m_next_component_id++; }
350
351
  // Read-only view of the next id that mint_component_id() would return.
352
  // Used by HeifPixelImage::clone_component_descriptions_from() to keep the
353
  // counter aligned across an item-to-image clone.
354
3.63k
  [[nodiscard]] uint32_t peek_next_component_id() const { return m_next_component_id; }
355
356
  [[nodiscard]] ComponentDescription* find_component_description(uint32_t component_id)
357
5.67k
  {
358
11.5k
    for (auto& c : m_components) {
359
11.5k
      if (c.component_id == component_id) return &c;
360
11.5k
    }
361
0
    return nullptr;
362
5.67k
  }
363
364
  [[nodiscard]] const ComponentDescription* find_component_description(uint32_t component_id) const
365
1.33k
  {
366
2.65k
    for (const auto& c : m_components) {
367
2.65k
      if (c.component_id == component_id) return &c;
368
2.65k
    }
369
0
    return nullptr;
370
1.33k
  }
371
372
373
  // --- bayer pattern
374
375
0
  bool has_bayer_pattern(uint32_t component_id) const { return m_bayer_pattern.has_value(); }
376
377
0
  bool has_any_bayer_pattern() const { return m_bayer_pattern.has_value(); }
378
379
0
  const BayerPattern& get_bayer_pattern(uint32_t component_id) const { assert(has_bayer_pattern(component_id)); return *m_bayer_pattern; }
380
381
0
  const BayerPattern& get_any_bayer_pattern() const { assert(has_any_bayer_pattern()); return *m_bayer_pattern; }
382
383
0
  virtual void set_bayer_pattern(const BayerPattern& pattern) { m_bayer_pattern = pattern; }
384
385
386
  // --- polarization pattern
387
388
0
  bool has_polarization_patterns() const { return !m_polarization_patterns.empty(); }
389
390
0
  const std::vector<PolarizationPattern>& get_polarization_patterns() const { return m_polarization_patterns; }
391
392
0
  virtual void set_polarization_patterns(const std::vector<PolarizationPattern>& p) { m_polarization_patterns = p; }
393
394
0
  virtual void add_polarization_pattern(const PolarizationPattern& p) { m_polarization_patterns.push_back(p); }
395
396
397
  // --- sensor bad pixels map
398
399
0
  bool has_sensor_bad_pixels_maps() const { return !m_sensor_bad_pixels_maps.empty(); }
400
401
0
  const std::vector<SensorBadPixelsMap>& get_sensor_bad_pixels_maps() const { return m_sensor_bad_pixels_maps; }
402
403
0
  virtual void set_sensor_bad_pixels_maps(const std::vector<SensorBadPixelsMap>& m) { m_sensor_bad_pixels_maps = m; }
404
405
0
  virtual void add_sensor_bad_pixels_map(const SensorBadPixelsMap& m) { m_sensor_bad_pixels_maps.push_back(m); }
406
407
408
  // --- sensor non-uniformity correction
409
410
0
  bool has_sensor_nuc() const { return !m_sensor_nuc.empty(); }
411
412
0
  const std::vector<SensorNonUniformityCorrection>& get_sensor_nuc() const { return m_sensor_nuc; }
413
414
0
  virtual void set_sensor_nuc(const std::vector<SensorNonUniformityCorrection>& n) { m_sensor_nuc = n; }
415
416
0
  virtual void add_sensor_nuc(const SensorNonUniformityCorrection& n) { m_sensor_nuc.push_back(n); }
417
418
419
  // --- chroma sample location (ISO 23001-17, Section 6.1.4)
420
421
0
  bool has_chroma_location() const { return m_chroma_location.has_value(); }
422
423
0
  uint8_t get_chroma_location() const { return m_chroma_location.value_or(0); }
424
425
0
  virtual void set_chroma_location(uint8_t loc) { m_chroma_location = loc; }
426
427
428
  // --- sample duration (for images that are frames in a sequence)
429
  // 0 means "no duration assigned" (the default for still images).
430
431
0
  void set_sample_duration(uint32_t d) { m_sample_duration = d; }
432
433
0
  uint32_t get_sample_duration() const { return m_sample_duration; }
434
435
436
0
  bool has_omaf_image_projection() const {
437
0
    return (m_omaf_image_projection != heif_omaf_image_projection_flat);
438
0
  }
439
440
0
  const heif_omaf_image_projection get_omaf_image_projection() const {
441
0
    return m_omaf_image_projection;
442
0
  }
443
444
0
  virtual void set_omaf_image_projection(const heif_omaf_image_projection projection) {
445
0
    m_omaf_image_projection = projection;
446
0
  }
447
448
  // Copies all per-image metadata from `other` (color profiles, premultiplied
449
  // alpha, pixel aspect ratio, clli, mdcv, tai timestamp, gimi sample content
450
  // id, bayer pattern, polarization patterns, sensor maps, sensor nuc, chroma
451
  // location, omaf projection). Per-component descriptions
452
  // (m_components / m_next_component_id) are intentionally not copied; those
453
  // are managed separately by callers (via add_channel / add_component, or
454
  // set_component_descriptions on the destination).
455
  //
456
  // Bayer / polarization / sensor-map metadata refers to image geometry;
457
  // transforms that change orientation or position copy them verbatim and
458
  // would need separate geometry adjustment to remain semantically correct.
459
  void copy_metadata_from(const ImageDescription& other);
460
461
private:
462
  bool m_premultiplied_alpha = false;
463
  nclx_profile m_color_profile_nclx = nclx_profile::undefined();
464
  std::shared_ptr<const color_profile_raw> m_color_profile_icc;
465
466
  uint32_t m_PixelAspectRatio_h = 1;
467
  uint32_t m_PixelAspectRatio_v = 1;
468
  heif_content_light_level m_clli{};
469
  std::optional<heif_mastering_display_colour_volume> m_mdcv;
470
  std::optional<heif_ambient_viewing_environment> m_amve;
471
  std::optional<uint32_t> m_nominal_diffuse_white_luminance;
472
473
  heif_tai_timestamp_packet* m_tai_timestamp = nullptr;
474
475
  // Empty string means "no content id assigned".
476
  std::string m_gimi_sample_content_id;
477
478
  // Per-component description vector. Single source of truth for per-component
479
  // metadata (id, channel, type, format, datatype, bit depth, dims, content ID).
480
  std::vector<ComponentDescription> m_components;
481
482
  // ID allocator for the per-component descriptions above. Used both by
483
  // HeifPixelImage (decoded side) and ImageItem (handle side) via the
484
  // mint_component_id() / peek_next_component_id() accessors.
485
  uint32_t m_next_component_id = 1;
486
487
  std::optional<BayerPattern> m_bayer_pattern;
488
489
  std::vector<PolarizationPattern> m_polarization_patterns;
490
491
  std::vector<SensorBadPixelsMap> m_sensor_bad_pixels_maps;
492
493
  std::vector<SensorNonUniformityCorrection> m_sensor_nuc;
494
495
  std::optional<uint8_t> m_chroma_location;
496
497
  uint32_t m_sample_duration = 0; // duration of a sequence frame, 0 for stills
498
499
  heif_omaf_image_projection m_omaf_image_projection = heif_omaf_image_projection::heif_omaf_image_projection_flat;
500
501
protected:
502
  std::shared_ptr<Box_clli> create_clli_box() const;
503
504
  std::shared_ptr<Box_mdcv> create_mdcv_box() const;
505
506
  std::shared_ptr<Box_amve> create_amve_box() const;
507
508
  std::shared_ptr<Box_ndwt> create_ndwt_box() const;
509
510
  std::shared_ptr<Box_pasp> create_pasp_box() const;
511
512
  std::shared_ptr<Box_colr> create_colr_box_nclx() const;
513
514
  std::shared_ptr<Box_colr> create_colr_box_icc() const;
515
516
  std::shared_ptr<Box_prfr> create_prfr_box() const;
517
};
518
519
#endif