Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image-items/unc_image.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2023 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 <cstdint>
22
#include <cstring>
23
#include <algorithm>
24
#include <map>
25
#include <iostream>
26
#include <cassert>
27
#include <utility>
28
29
#include "common_utils.h"
30
#include "context.h"
31
#include "compression.h"
32
#include "error.h"
33
#include "libheif/heif.h"
34
#include "codecs/uncompressed/unc_types.h"
35
#include "codecs/uncompressed/unc_boxes.h"
36
#include "unc_image.h"
37
#include "codecs/uncompressed/unc_dec.h"
38
#include "codecs/uncompressed/unc_enc.h"
39
#include "codecs/uncompressed/unc_codec.h"
40
#include "image_item.h"
41
#include "codecs/uncompressed/unc_encoder.h"
42
43
44
struct unciHeaders;
45
46
ImageItem_uncompressed::ImageItem_uncompressed(HeifContext* ctx, heif_item_id id)
47
1.09k
    : ImageItem(ctx, id)
48
1.09k
{
49
1.09k
  m_encoder = std::make_shared<Encoder_uncompressed>();
50
1.09k
}
51
52
void ImageItem_uncompressed::populate_component_descriptions()
53
2.17k
{
54
  // Idempotent: this method is called from both set_properties() (where
55
  // unci populates from cmpd/uncC, no decoder needed) and from
56
  // context.cc after initialize_decoder() (where visual codecs populate).
57
  // For unci items the first call wins; the second is a no-op.
58
2.17k
  if (!get_component_descriptions().empty()) {
59
1.06k
    return;
60
1.06k
  }
61
62
1.11k
  auto uncC = get_property<Box_uncC>();
63
1.11k
  if (!uncC) {
64
6
    return;
65
6
  }
66
1.10k
  auto cmpd = get_property<Box_cmpd>();
67
68
  // For minimized (version-1) uncC, expand the profile fourcc into the
69
  // components vector and synthesize cmpd if missing.
70
1.10k
  fill_uncC_and_cmpd_from_profile(uncC, cmpd);
71
1.10k
  if (!cmpd) {
72
3
    return;
73
3
  }
74
75
1.10k
  const auto& cmpd_components = cmpd->get_components();
76
1.10k
  uint32_t img_width = get_ispe_width();
77
1.10k
  uint32_t img_height = get_ispe_height();
78
79
  // Determine chroma format so we can apply the correct subsampling for Cb/Cr.
80
1.10k
  heif_chroma chroma = heif_chroma_undefined;
81
1.10k
  heif_colorspace colourspace = heif_colorspace_undefined;
82
1.10k
  (void) UncompressedImageCodec::get_heif_chroma_uncompressed(uncC, cmpd, &chroma, &colourspace, nullptr);
83
84
  // Track which cmpd indices already got a description from the uncC walk,
85
  // so we don't duplicate them when handling cpat.
86
1.10k
  std::vector<bool> cmpd_index_has_description(cmpd_components.size(), false);
87
88
  // 1. uncC components — these get real planes after decode.
89
4.24k
  for (const auto& uc : uncC->get_components()) {
90
4.24k
    if (uc.component_index >= cmpd_components.size()) {
91
0
      continue; // malformed; skip
92
0
    }
93
4.24k
    uint16_t component_type = cmpd_components[uc.component_index].component_type;
94
95
4.24k
    heif_channel channel = map_uncompressed_component_to_channel(component_type);
96
4.24k
    uint32_t plane_w = channel_width(img_width, chroma, channel);
97
4.24k
    uint32_t plane_h = channel_height(img_height, chroma, channel);
98
99
4.24k
    ComponentDescription desc;
100
4.24k
    desc.component_id = mint_component_id();
101
4.24k
    desc.channel = channel;
102
4.24k
    desc.component_type = component_type;
103
4.24k
    desc.datatype = unc_component_format_to_datatype(uc.component_format);
104
4.24k
    desc.bit_depth = uc.component_bit_depth;
105
4.24k
    desc.width = plane_w;
106
4.24k
    desc.height = plane_h;
107
4.24k
    desc.has_data_plane = true;
108
4.24k
    add_component_description(std::move(desc));
109
110
4.24k
    cmpd_index_has_description[uc.component_index] = true;
111
4.24k
  }
112
113
  // 2. cpat reference components — one per UNIQUE cmpd_index that's
114
  // referenced by the pattern but doesn't have an uncC plane. Two pattern
115
  // pixels that share a cmpd_index share the same component id; the
116
  // BayerPattern only carries per-pixel gain, not per-pixel identity.
117
1.10k
  if (auto cpat = get_property<Box_cpat>()) {
118
0
    for (const auto& pixel : cpat->get_pattern().pixels) {
119
0
      if (pixel.cmpd_index >= cmpd_components.size()) continue;
120
0
      if (cmpd_index_has_description[pixel.cmpd_index]) continue;
121
122
0
      uint16_t component_type = cmpd_components[pixel.cmpd_index].component_type;
123
124
0
      ComponentDescription desc;
125
0
      desc.component_id = mint_component_id();
126
0
      desc.channel = map_uncompressed_component_to_channel(component_type);
127
0
      desc.component_type = component_type;
128
0
      desc.has_data_plane = false;
129
0
      add_component_description(std::move(desc));
130
131
0
      cmpd_index_has_description[pixel.cmpd_index] = true;
132
0
    }
133
0
  }
134
1.10k
}
135
136
137
ImageItem_uncompressed::ImageItem_uncompressed(HeifContext* ctx)
138
0
    : ImageItem(ctx)
139
0
{
140
0
  m_encoder = std::make_shared<Encoder_uncompressed>();
141
0
}
142
143
144
Result<std::shared_ptr<HeifPixelImage>> ImageItem_uncompressed::decode_compressed_image(const heif_decoding_options& options,
145
                                                                                bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
146
                                                                                DecodeTraversalState decode_state) const
147
1.06k
{
148
1.06k
  std::shared_ptr<HeifPixelImage> img;
149
150
1.06k
  std::vector<uint8_t> data;
151
152
1.06k
  Error err;
153
154
1.06k
  if (decode_tile_only) {
155
0
    err = UncompressedImageCodec::decode_uncompressed_image_tile(get_context(),
156
0
                                                                 get_id(),
157
0
                                                                 img,
158
0
                                                                 tile_x0, tile_y0);
159
0
  }
160
1.06k
  else {
161
1.06k
    err = UncompressedImageCodec::decode_uncompressed_image(get_context(),
162
1.06k
                                                            get_id(),
163
1.06k
                                                            img);
164
1.06k
  }
165
166
1.06k
  if (err) {
167
985
    return err;
168
985
  }
169
84
  else {
170
84
    return img;
171
84
  }
172
1.06k
}
173
174
175
Result<Encoder::CodedImageData> ImageItem_uncompressed::encode(const std::shared_ptr<HeifPixelImage>& src_image,
176
                                                                 heif_encoder* encoder,
177
                                                                 const heif_encoding_options& options,
178
                                                                 heif_image_input_class input_class)
179
0
{
180
0
  Result<std::unique_ptr<const unc_encoder>> uncEncoder = unc_encoder_factory::get_unc_encoder(src_image, options);
181
0
  if (!uncEncoder) {
182
0
    return {uncEncoder.error()};
183
0
  }
184
185
0
  return (*uncEncoder)->encode(src_image, options);
186
0
}
187
188
189
Result<std::shared_ptr<ImageItem_uncompressed>> ImageItem_uncompressed::add_unci_item(HeifContext* ctx,
190
                                                                                      const heif_unci_image_parameters* parameters,
191
                                                                                      const heif_encoding_options* encoding_options,
192
                                                                                      const std::shared_ptr<const HeifPixelImage>& prototype)
193
0
{
194
0
  assert(encoding_options != nullptr);
195
196
  // Resolve effective unci parameters: the direct argument takes precedence; otherwise
197
  // fall back to encoding_options->unci_parameters. At least one must be non-null.
198
199
0
  if (parameters == nullptr &&
200
0
      (encoding_options->version < 8 || encoding_options->unci_parameters == nullptr)) {
201
0
    return Error{heif_error_Usage_error,
202
0
                 heif_suberror_Invalid_parameter_value,
203
0
                 "heif_context_add_empty_unci_image: either the 'parameters' argument or "
204
0
                 "heif_encoding_options::unci_parameters must be non-null."};
205
0
  }
206
207
0
  if (parameters == nullptr) {
208
0
    parameters = encoding_options->unci_parameters;
209
0
  }
210
211
  // Check input parameters
212
213
0
  if (parameters->image_width % parameters->tile_width != 0 ||
214
0
      parameters->image_height % parameters->tile_height != 0) {
215
0
    return Error{heif_error_Usage_error,
216
0
                 heif_suberror_Invalid_parameter_value,
217
0
                 "ISO 23001-17 image size must be an integer multiple of the tile size."};
218
0
  }
219
220
221
0
  Result<std::unique_ptr<const unc_encoder>> uncEncoder = unc_encoder_factory::get_unc_encoder(prototype, *encoding_options);
222
0
  if (!uncEncoder) {
223
0
    return {uncEncoder.error()};
224
0
  }
225
226
227
  // Create 'unci' Item
228
229
0
  auto file = ctx->get_heif_file();
230
231
0
  auto unci_id_result = ctx->get_heif_file()->add_new_image(fourcc("unci"));
232
0
  if (!unci_id_result) {
233
0
    return unci_id_result.error();
234
0
  }
235
0
  heif_item_id unci_id = *unci_id_result;
236
0
  auto unci_image = std::make_shared<ImageItem_uncompressed>(ctx, unci_id);
237
0
  unci_image->set_resolution(parameters->image_width, parameters->image_height);
238
0
  unci_image->m_unc_encoder = std::move(*uncEncoder);
239
0
  unci_image->m_tile_encoding_options = *encoding_options;
240
0
  unci_image->m_tile_encoding_options.image_orientation = heif_orientation_normal;
241
242
0
  ctx->insert_image_item(unci_id, unci_image);
243
244
245
  // Generate headers
246
247
  // --- generate configuration property boxes
248
249
0
  auto uncC = unci_image->m_unc_encoder->get_uncC();
250
251
0
  uncC->set_number_of_tile_columns(parameters->image_width / parameters->tile_width);
252
0
  uncC->set_number_of_tile_rows(parameters->image_height / parameters->tile_height);
253
254
0
  unci_image->add_property(uncC, true);
255
0
  if (!uncC->is_minimized()) {
256
0
    unci_image->add_property(unci_image->m_unc_encoder->get_cmpd(), true);
257
0
  }
258
259
260
  // Add cpat property if Bayer pattern is set
261
0
  if (unci_image->m_unc_encoder->get_cpat()) {
262
0
    unci_image->add_property(unci_image->m_unc_encoder->get_cpat(), true);
263
0
  }
264
265
  // Add `ispe` property
266
267
0
  auto ispe = std::make_shared<Box_ispe>();
268
0
  ispe->set_size(static_cast<uint32_t>(parameters->image_width),
269
0
                 static_cast<uint32_t>(parameters->image_height));
270
0
  unci_image->add_property(ispe, true);
271
272
0
  if (parameters->compression != heif_unci_compression_off) {
273
0
    auto cmpC = std::make_shared<Box_cmpC>();
274
0
    cmpC->set_compression_type(unci_compression_to_fourcc(parameters->compression));
275
0
    cmpC->set_compressed_unit_type(heif_cmpC_compressed_unit_type_image_tile);
276
277
0
    unci_image->add_property(cmpC, true);
278
279
0
    auto icef = std::make_shared<Box_icef>();
280
0
    unci_image->add_property_without_deduplication(icef, true); // icef is empty. A normal add_property() would lead to a wrong deduplication.
281
0
  }
282
283
  // Add transformative properties
284
285
0
  if (Error err = ctx->get_heif_file()->add_orientation_properties(unci_id, encoding_options->image_orientation)) {
286
0
    return err;
287
0
  }
288
289
290
  // Create empty image. If we use compression, we append the data piece by piece.
291
292
0
  if (parameters->compression == heif_unci_compression_off) {
293
0
    uint64_t tile_size = unci_image->m_unc_encoder->compute_tile_data_size_bytes(parameters->image_width / uncC->get_number_of_tile_columns(),
294
0
                                                                                 parameters->image_height / uncC->get_number_of_tile_rows());
295
296
0
    std::vector<uint8_t> dummydata;
297
0
    dummydata.resize(tile_size);
298
299
0
    uint32_t nTiles = (parameters->image_width / parameters->tile_width) * (parameters->image_height / parameters->tile_height);
300
301
0
    for (uint64_t i = 0; i < nTiles; i++) {
302
0
      const int construction_method = 0; // 0=mdat 1=idat
303
0
      file->append_iloc_data(unci_id, dummydata, construction_method);
304
0
    }
305
0
  }
306
307
  // Set Brands
308
  //ctx->get_heif_file()->set_brand(heif_compression_uncompressed, unci_image->is_miaf_compatible());
309
310
0
  return {unci_image};
311
0
}
312
313
314
Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr<const HeifPixelImage>& image, bool save_alpha)
315
0
{
316
0
  std::shared_ptr<Box_uncC> uncC = get_property<Box_uncC>();
317
0
  assert(uncC);
318
319
0
  uint32_t tile_width = image->get_width();
320
0
  uint32_t tile_height = image->get_height();
321
322
0
  uint32_t tile_idx = tile_y * uncC->get_number_of_tile_columns() + tile_x;
323
324
0
  if (tile_y >= uncC->get_number_of_tile_rows() ||
325
0
      tile_x >= uncC->get_number_of_tile_columns()) {
326
0
    return Error{heif_error_Usage_error,
327
0
                 heif_suberror_Invalid_parameter_value,
328
0
                 "tile_x and/or tile_y are out of range."};
329
0
  }
330
331
  // All tiles have the same size (add_unci_item() enforces that the image size is an integer
332
  // multiple of the tile size). We compute the position at which the tile data is written from the
333
  // size of the passed image, so a differently sized tile would be written to a wrong offset.
334
335
0
  uint32_t expected_tile_width, expected_tile_height;
336
0
  get_tile_size(expected_tile_width, expected_tile_height);
337
338
0
  if (tile_width != expected_tile_width ||
339
0
      tile_height != expected_tile_height) {
340
0
    return Error{heif_error_Usage_error,
341
0
                 heif_suberror_Invalid_parameter_value,
342
0
                 "Tile image size does not match the tile size of the uncompressed image."};
343
0
  }
344
345
346
0
  if (image->has_alpha() && !save_alpha) {
347
    // TODO: drop alpha
348
0
  }
349
350
0
  Result<std::vector<uint8_t>> codedBitstreamResult = m_unc_encoder->encode_tile(image);
351
0
  if (!codedBitstreamResult) {
352
0
    return codedBitstreamResult.error();
353
0
  }
354
355
0
  std::shared_ptr<Box_cmpC> cmpC = get_property<Box_cmpC>();
356
0
  std::shared_ptr<Box_icef> icef = get_property<Box_icef>();
357
358
0
  if (!icef || !cmpC) {
359
0
    assert(!icef);
360
0
    assert(!cmpC);
361
362
    // uncompressed
363
364
0
    uint64_t tile_data_size = m_unc_encoder->compute_tile_data_size_bytes(tile_width, tile_height);
365
366
0
    get_file()->replace_iloc_data(get_id(), tile_idx * tile_data_size, *codedBitstreamResult, 0);
367
0
  }
368
0
  else {
369
0
    const std::vector<uint8_t>& raw_data = *codedBitstreamResult;
370
371
0
    auto compressed = compress_unci_fourcc(cmpC->get_compression_type(),
372
0
                                            raw_data.data(), raw_data.size());
373
0
    if (!compressed) {
374
0
      return compressed.error();
375
0
    }
376
377
0
    get_file()->append_iloc_data(get_id(), *compressed, 0);
378
379
0
    Box_icef::CompressedUnitInfo unit_info;
380
0
    unit_info.unit_offset = m_next_tile_write_pos;
381
0
    unit_info.unit_size = compressed->size();
382
0
    icef->set_component(tile_idx, unit_info);
383
384
0
    m_next_tile_write_pos += compressed->size();
385
0
  }
386
387
0
  return Error::Ok;
388
0
}
389
390
391
void ImageItem_uncompressed::get_tile_size(uint32_t& w, uint32_t& h) const
392
0
{
393
0
  auto ispe = get_property<Box_ispe>();
394
0
  auto uncC = get_property<Box_uncC>();
395
396
0
  if (!ispe || !uncC) {
397
0
    w = h = 0;
398
0
  }
399
0
  else {
400
0
    w = ispe->get_width() / uncC->get_number_of_tile_columns();
401
0
    h = ispe->get_height() / uncC->get_number_of_tile_rows();
402
0
  }
403
0
}
404
405
406
heif_image_tiling ImageItem_uncompressed::get_heif_image_tiling() const
407
0
{
408
0
  heif_image_tiling tiling{};
409
410
0
  auto ispe = get_property<Box_ispe>();
411
0
  auto uncC = get_property<Box_uncC>();
412
0
  assert(ispe && uncC);
413
414
0
  tiling.num_columns = uncC->get_number_of_tile_columns();
415
0
  tiling.num_rows = uncC->get_number_of_tile_rows();
416
417
0
  tiling.tile_width = ispe->get_width() / tiling.num_columns;
418
0
  tiling.tile_height = ispe->get_height() / tiling.num_rows;
419
420
0
  tiling.image_width = ispe->get_width();
421
0
  tiling.image_height = ispe->get_height();
422
0
  tiling.number_of_extra_dimensions = 0;
423
424
0
  return tiling;
425
0
}
426
427
Result<std::shared_ptr<Decoder>> ImageItem_uncompressed::get_decoder() const
428
1.08k
{
429
1.08k
  return {m_decoder};
430
1.08k
}
431
432
std::shared_ptr<Encoder> ImageItem_uncompressed::get_encoder() const
433
0
{
434
0
  return m_encoder;
435
0
}
436
437
Error ImageItem_uncompressed::initialize_decoder()
438
1.09k
{
439
1.09k
  std::shared_ptr<Box_cmpd> cmpd = get_property<Box_cmpd>();
440
1.09k
  std::shared_ptr<Box_uncC> uncC = get_property<Box_uncC>();
441
1.09k
  std::shared_ptr<Box_ispe> ispe = get_property<Box_ispe>();
442
443
1.09k
  if (!uncC) {
444
6
    return Error{heif_error_Invalid_input,
445
6
                 heif_suberror_Unspecified,
446
6
                 "No 'uncC' box found."};
447
6
  }
448
449
1.08k
  if (!ispe) {
450
8
    return Error{heif_error_Invalid_input,
451
8
                 heif_suberror_Unspecified,
452
8
                 "No 'ispe' box found for uncompressed image item."};
453
8
  }
454
455
  // libheif's pixel allocation and processing (rotate/mirror) only support
456
  // bit depths up to 128 per component. Reject larger values up front so they
457
  // surface as a clean Unsupported_feature error rather than failing inside
458
  // HeifPixelImage::ComponentStorage::alloc().
459
4.22k
  for (const auto& component : uncC->get_components()) {
460
4.22k
    if (component.component_bit_depth > 128) {
461
0
      std::stringstream sstr;
462
0
      sstr << "Uncompressed image with " << component.component_bit_depth
463
0
           << " bits per component is not supported.";
464
0
      return Error{heif_error_Unsupported_feature,
465
0
                   heif_suberror_Unsupported_bit_depth,
466
0
                   sstr.str()};
467
0
    }
468
4.22k
  }
469
470
1.07k
  m_decoder = std::make_shared<Decoder_uncompressed>(uncC, cmpd, ispe);
471
472
1.07k
  return Error::Ok;
473
1.07k
}
474
475
void ImageItem_uncompressed::set_decoder_input_data()
476
1.07k
{
477
1.07k
  DataExtent extent;
478
1.07k
  extent.set_from_image_item(get_context()->get_heif_file(), get_id());
479
480
1.07k
  m_decoder->set_data_extent(std::move(extent));
481
1.07k
}
482
483
484
bool ImageItem_uncompressed::has_coded_alpha_channel() const
485
1.07k
{
486
1.07k
  return m_decoder->has_alpha_component();
487
1.07k
}
488
489
heif_brand2 ImageItem_uncompressed::get_compatible_brand() const
490
0
{
491
0
  return 0; // TODO: not clear to me what to use
492
0
}