Coverage Report

Created: 2026-07-25 07:06

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.02k
    : ImageItem(ctx, id)
48
1.02k
{
49
1.02k
  m_encoder = std::make_shared<Encoder_uncompressed>();
50
1.02k
}
51
52
void ImageItem_uncompressed::populate_component_descriptions()
53
1.92k
{
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
1.92k
  if (!get_component_descriptions().empty()) {
59
926
    return;
60
926
  }
61
62
1.00k
  auto uncC = get_property<Box_uncC>();
63
1.00k
  if (!uncC) {
64
54
    return;
65
54
  }
66
949
  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
949
  fill_uncC_and_cmpd_from_profile(uncC, cmpd);
71
949
  if (!cmpd) {
72
5
    return;
73
5
  }
74
75
944
  const auto& cmpd_components = cmpd->get_components();
76
944
  uint32_t img_width = get_ispe_width();
77
944
  uint32_t img_height = get_ispe_height();
78
79
  // Determine chroma format so we can apply the correct subsampling for Cb/Cr.
80
944
  heif_chroma chroma = heif_chroma_undefined;
81
944
  heif_colorspace colourspace = heif_colorspace_undefined;
82
944
  (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
944
  std::vector<bool> cmpd_index_has_description(cmpd_components.size(), false);
87
88
  // 1. uncC components — these get real planes after decode.
89
2.86k
  for (const auto& uc : uncC->get_components()) {
90
2.86k
    if (uc.component_index >= cmpd_components.size()) {
91
25
      continue; // malformed; skip
92
25
    }
93
2.84k
    uint16_t component_type = cmpd_components[uc.component_index].component_type;
94
95
2.84k
    heif_channel channel = map_uncompressed_component_to_channel(component_type);
96
2.84k
    uint32_t plane_w = channel_width(img_width, chroma, channel);
97
2.84k
    uint32_t plane_h = channel_height(img_height, chroma, channel);
98
99
2.84k
    ComponentDescription desc;
100
2.84k
    desc.component_id = mint_component_id();
101
2.84k
    desc.channel = channel;
102
2.84k
    desc.component_type = component_type;
103
2.84k
    desc.datatype = unc_component_format_to_datatype(uc.component_format);
104
2.84k
    desc.bit_depth = uc.component_bit_depth;
105
2.84k
    desc.width = plane_w;
106
2.84k
    desc.height = plane_h;
107
2.84k
    desc.has_data_plane = true;
108
2.84k
    add_component_description(std::move(desc));
109
110
2.84k
    cmpd_index_has_description[uc.component_index] = true;
111
2.84k
  }
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
944
  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
944
}
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
                                                                                std::set<heif_item_id> processed_ids) const
147
921
{
148
921
  std::shared_ptr<HeifPixelImage> img;
149
150
921
  std::vector<uint8_t> data;
151
152
921
  Error err;
153
154
921
  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
921
  else {
161
921
    err = UncompressedImageCodec::decode_uncompressed_image(get_context(),
162
921
                                                            get_id(),
163
921
                                                            img);
164
921
  }
165
166
921
  if (err) {
167
787
    return err;
168
787
  }
169
134
  else {
170
134
    return img;
171
134
  }
172
921
}
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
  ctx->get_heif_file()->add_orientation_properties(unci_id, encoding_options->image_orientation);
286
287
288
  // Create empty image. If we use compression, we append the data piece by piece.
289
290
0
  if (parameters->compression == heif_unci_compression_off) {
291
0
    uint64_t tile_size = unci_image->m_unc_encoder->compute_tile_data_size_bytes(parameters->image_width / uncC->get_number_of_tile_columns(),
292
0
                                                                                 parameters->image_height / uncC->get_number_of_tile_rows());
293
294
0
    std::vector<uint8_t> dummydata;
295
0
    dummydata.resize(tile_size);
296
297
0
    uint32_t nTiles = (parameters->image_width / parameters->tile_width) * (parameters->image_height / parameters->tile_height);
298
299
0
    for (uint64_t i = 0; i < nTiles; i++) {
300
0
      const int construction_method = 0; // 0=mdat 1=idat
301
0
      file->append_iloc_data(unci_id, dummydata, construction_method);
302
0
    }
303
0
  }
304
305
  // Set Brands
306
  //ctx->get_heif_file()->set_brand(heif_compression_uncompressed, unci_image->is_miaf_compatible());
307
308
0
  return {unci_image};
309
0
}
310
311
312
Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr<const HeifPixelImage>& image, bool save_alpha)
313
0
{
314
0
  std::shared_ptr<Box_uncC> uncC = get_property<Box_uncC>();
315
0
  assert(uncC);
316
317
0
  uint32_t tile_width = image->get_width();
318
0
  uint32_t tile_height = image->get_height();
319
320
0
  uint32_t tile_idx = tile_y * uncC->get_number_of_tile_columns() + tile_x;
321
322
0
  if (tile_y >= uncC->get_number_of_tile_rows() ||
323
0
      tile_x >= uncC->get_number_of_tile_columns()) {
324
0
    return Error{heif_error_Usage_error,
325
0
                 heif_suberror_Invalid_parameter_value,
326
0
                 "tile_x and/or tile_y are out of range."};
327
0
  }
328
329
330
0
  if (image->has_alpha() && !save_alpha) {
331
    // TODO: drop alpha
332
0
  }
333
334
0
  Result<std::vector<uint8_t>> codedBitstreamResult = m_unc_encoder->encode_tile(image);
335
0
  if (!codedBitstreamResult) {
336
0
    return codedBitstreamResult.error();
337
0
  }
338
339
0
  std::shared_ptr<Box_cmpC> cmpC = get_property<Box_cmpC>();
340
0
  std::shared_ptr<Box_icef> icef = get_property<Box_icef>();
341
342
0
  if (!icef || !cmpC) {
343
0
    assert(!icef);
344
0
    assert(!cmpC);
345
346
    // uncompressed
347
348
0
    uint64_t tile_data_size = m_unc_encoder->compute_tile_data_size_bytes(tile_width, tile_height);
349
350
0
    get_file()->replace_iloc_data(get_id(), tile_idx * tile_data_size, *codedBitstreamResult, 0);
351
0
  }
352
0
  else {
353
0
    const std::vector<uint8_t>& raw_data = *codedBitstreamResult;
354
355
0
    auto compressed = compress_unci_fourcc(cmpC->get_compression_type(),
356
0
                                            raw_data.data(), raw_data.size());
357
0
    if (!compressed) {
358
0
      return compressed.error();
359
0
    }
360
361
0
    get_file()->append_iloc_data(get_id(), *compressed, 0);
362
363
0
    Box_icef::CompressedUnitInfo unit_info;
364
0
    unit_info.unit_offset = m_next_tile_write_pos;
365
0
    unit_info.unit_size = compressed->size();
366
0
    icef->set_component(tile_idx, unit_info);
367
368
0
    m_next_tile_write_pos += compressed->size();
369
0
  }
370
371
0
  return Error::Ok;
372
0
}
373
374
375
void ImageItem_uncompressed::get_tile_size(uint32_t& w, uint32_t& h) const
376
0
{
377
0
  auto ispe = get_property<Box_ispe>();
378
0
  auto uncC = get_property<Box_uncC>();
379
380
0
  if (!ispe || !uncC) {
381
0
    w = h = 0;
382
0
  }
383
0
  else {
384
0
    w = ispe->get_width() / uncC->get_number_of_tile_columns();
385
0
    h = ispe->get_height() / uncC->get_number_of_tile_rows();
386
0
  }
387
0
}
388
389
390
heif_image_tiling ImageItem_uncompressed::get_heif_image_tiling() const
391
0
{
392
0
  heif_image_tiling tiling{};
393
394
0
  auto ispe = get_property<Box_ispe>();
395
0
  auto uncC = get_property<Box_uncC>();
396
0
  assert(ispe && uncC);
397
398
0
  tiling.num_columns = uncC->get_number_of_tile_columns();
399
0
  tiling.num_rows = uncC->get_number_of_tile_rows();
400
401
0
  tiling.tile_width = ispe->get_width() / tiling.num_columns;
402
0
  tiling.tile_height = ispe->get_height() / tiling.num_rows;
403
404
0
  tiling.image_width = ispe->get_width();
405
0
  tiling.image_height = ispe->get_height();
406
0
  tiling.number_of_extra_dimensions = 0;
407
408
0
  return tiling;
409
0
}
410
411
Result<std::shared_ptr<Decoder>> ImageItem_uncompressed::get_decoder() const
412
2.74k
{
413
2.74k
  return {m_decoder};
414
2.74k
}
415
416
std::shared_ptr<Encoder> ImageItem_uncompressed::get_encoder() const
417
0
{
418
0
  return m_encoder;
419
0
}
420
421
Error ImageItem_uncompressed::initialize_decoder()
422
998
{
423
998
  std::shared_ptr<Box_cmpd> cmpd = get_property<Box_cmpd>();
424
998
  std::shared_ptr<Box_uncC> uncC = get_property<Box_uncC>();
425
998
  std::shared_ptr<Box_ispe> ispe = get_property<Box_ispe>();
426
427
998
  if (!uncC) {
428
54
    return Error{heif_error_Invalid_input,
429
54
                 heif_suberror_Unspecified,
430
54
                 "No 'uncC' box found."};
431
54
  }
432
433
944
  if (!ispe) {
434
10
    return Error{heif_error_Invalid_input,
435
10
                 heif_suberror_Unspecified,
436
10
                 "No 'ispe' box found for uncompressed image item."};
437
10
  }
438
439
  // libheif's pixel allocation and processing (rotate/mirror) only support
440
  // bit depths up to 128 per component. Reject larger values up front so they
441
  // surface as a clean Unsupported_feature error rather than failing inside
442
  // HeifPixelImage::ComponentStorage::alloc().
443
2.83k
  for (const auto& component : uncC->get_components()) {
444
2.83k
    if (component.component_bit_depth > 128) {
445
3
      std::stringstream sstr;
446
3
      sstr << "Uncompressed image with " << component.component_bit_depth
447
3
           << " bits per component is not supported.";
448
3
      return Error{heif_error_Unsupported_feature,
449
3
                   heif_suberror_Unsupported_bit_depth,
450
3
                   sstr.str()};
451
3
    }
452
2.83k
  }
453
454
931
  m_decoder = std::make_shared<Decoder_uncompressed>(uncC, cmpd, ispe);
455
456
931
  return Error::Ok;
457
934
}
458
459
void ImageItem_uncompressed::set_decoder_input_data()
460
931
{
461
931
  DataExtent extent;
462
931
  extent.set_from_image_item(get_context()->get_heif_file(), get_id());
463
464
931
  m_decoder->set_data_extent(std::move(extent));
465
931
}
466
467
468
bool ImageItem_uncompressed::has_coded_alpha_channel() const
469
927
{
470
927
  return m_decoder->has_alpha_component();
471
927
}
472
473
heif_brand2 ImageItem_uncompressed::get_compatible_brand() const
474
0
{
475
0
  return 0; // TODO: not clear to me what to use
476
0
}