Coverage Report

Created: 2026-09-14 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/codecs/uncompressed/unc_codec.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 "unc_codec.h"
22
23
#include "common_utils.h"
24
#include "context.h"
25
#include "error.h"
26
#include "libheif/heif.h"
27
#include "unc_types.h"
28
#include "unc_boxes.h"
29
30
#include "unc_decoder.h"
31
#include "codecs/decoder.h"
32
33
#include <algorithm>
34
#include <cassert>
35
#include <iostream>
36
#include <map>
37
#include <sstream>
38
#include "security_limits.h"
39
#include <utility>
40
41
42
Error UncompressedImageCodec::get_heif_chroma_uncompressed(const std::shared_ptr<const Box_uncC>& uncC,
43
                                                           const std::shared_ptr<const Box_cmpd>& cmpd,
44
                                                           heif_chroma* out_chroma, heif_colorspace* out_colourspace,
45
                                                           bool* out_has_alpha)
46
3.24k
{
47
3.24k
  bool dummy_has_alpha;
48
3.24k
  if (out_has_alpha == nullptr) {
49
2.17k
    out_has_alpha = &dummy_has_alpha;
50
2.17k
  }
51
52
3.24k
  *out_chroma = heif_chroma_undefined;
53
3.24k
  *out_colourspace = heif_colorspace_undefined;
54
3.24k
  *out_has_alpha = false;
55
56
3.24k
  Error error = check_header_validity(std::nullopt, cmpd, uncC);
57
3.24k
  if (error) {
58
0
    return error;
59
0
  }
60
61
62
3.24k
  if (uncC != nullptr && uncC->get_version() == 1) {
63
3.23k
    switch (uncC->get_profile()) {
64
39
      case fourcc("rgb3"):
65
39
        *out_chroma = heif_chroma_444;
66
39
        *out_colourspace = heif_colorspace_RGB;
67
39
        *out_has_alpha = false;
68
39
        return Error::Ok;
69
70
6
      case fourcc("abgr"):
71
3.19k
      case fourcc("rgba"):
72
3.19k
        *out_chroma = heif_chroma_444;
73
3.19k
        *out_colourspace = heif_colorspace_RGB;
74
3.19k
        *out_has_alpha = true;
75
3.19k
        return Error::Ok;
76
77
0
      default:
78
0
        return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_image_type,
79
0
                     "unci image has unsupported profile");
80
3.23k
    }
81
3.23k
  }
82
83
84
  // each 1-bit represents an existing component in the image
85
8
  uint16_t componentSet = 0;
86
87
8
  for (Box_uncC::Component component : uncC->get_components()) {
88
0
    uint32_t component_index = component.component_index;
89
0
    uint16_t component_type = cmpd->get_components()[component_index].component_type;
90
91
0
    if (component_type > heif_cmpd_component_type_max_valid) {
92
0
      std::stringstream sstr;
93
0
      sstr << "a component_type > " << heif_cmpd_component_type_max_valid << " is not supported";
94
0
      return {heif_error_Unsupported_feature, heif_suberror_Invalid_parameter_value, sstr.str()};
95
0
    }
96
0
    if (component_type == heif_cmpd_component_type_padded) {
97
      // not relevant for determining chroma
98
0
      continue;
99
0
    }
100
0
    componentSet |= (1 << component_type);
101
0
  }
102
103
8
  *out_has_alpha = (componentSet & (1 << heif_cmpd_component_type_alpha)) != 0;
104
105
8
  if (componentSet == ((1 << heif_cmpd_component_type_red) | (1 << heif_cmpd_component_type_green) | (1 << heif_cmpd_component_type_blue)) ||
106
8
      componentSet == ((1 << heif_cmpd_component_type_red) | (1 << heif_cmpd_component_type_green) | (1 << heif_cmpd_component_type_blue) | (1 << heif_cmpd_component_type_alpha))) {
107
0
    *out_chroma = heif_chroma_444;
108
0
    *out_colourspace = heif_colorspace_RGB;
109
0
  }
110
111
8
  if (componentSet == ((1 << heif_cmpd_component_type_Y) | (1 << heif_cmpd_component_type_Cb) | (1 << heif_cmpd_component_type_Cr))) {
112
0
    switch (uncC->get_sampling_type()) {
113
0
      case sampling_mode_no_subsampling:
114
0
        *out_chroma = heif_chroma_444;
115
0
        break;
116
0
      case sampling_mode_422:
117
0
        *out_chroma = heif_chroma_422;
118
0
        break;
119
0
      case sampling_mode_420:
120
0
        *out_chroma = heif_chroma_420;
121
0
        break;
122
0
    }
123
0
    *out_colourspace = heif_colorspace_YCbCr;
124
0
  }
125
126
8
  if (componentSet == (1 << heif_cmpd_component_type_monochrome) || componentSet == ((1 << heif_cmpd_component_type_monochrome) | (1 << heif_cmpd_component_type_alpha)) ||
127
8
      componentSet == (1 << heif_cmpd_component_type_Y) || componentSet == ((1 << heif_cmpd_component_type_Y) | (1 << heif_cmpd_component_type_alpha))) {
128
    // mono or mono + alpha input, mono output.
129
0
    *out_chroma = heif_chroma_monochrome;
130
0
    *out_colourspace = heif_colorspace_monochrome;
131
0
  }
132
133
8
  if (componentSet == (1 << heif_cmpd_component_type_filter_array)) {
134
    // TODO - we should look up the components
135
0
    *out_chroma = heif_chroma_planar;
136
0
    *out_colourspace = heif_colorspace_filter_array;
137
0
  }
138
139
  // TODO: more combinations
140
141
  // out_colourspace remains heif_colorspace_undefined iff no branch above
142
  // matched any known component set.
143
8
  if (*out_colourspace == heif_colorspace_undefined) {
144
8
    return Error(heif_error_Unsupported_feature,
145
8
                 heif_suberror_Unsupported_data_version,
146
8
                 "Could not determine colourspace");
147
8
  }
148
149
0
  return Error::Ok;
150
8
}
151
152
bool map_uncompressed_component_to_channel(const std::shared_ptr<const Box_cmpd>& cmpd,
153
                                           Box_uncC::Component component,
154
                                           heif_channel* channel)
155
4.18k
{
156
4.18k
  uint32_t component_index = component.component_index;
157
4.18k
  uint16_t component_type = cmpd->get_components()[component_index].component_type;
158
159
4.18k
  *channel = map_uncompressed_component_to_channel(component_type);
160
4.18k
  return true;
161
4.18k
}
162
163
164
heif_component_datatype unc_component_format_to_datatype(uint8_t format)
165
4.24k
{
166
  // heif_component_datatype values are aligned with ISO/IEC 23001-17 Table 2.
167
  // is_valid_component_format() in unc_boxes.cc rejects out-of-range bytes
168
  // before they reach here, so any spec-defined byte casts cleanly.
169
4.24k
  if (format > component_format_max_valid) {
170
0
    return heif_component_datatype_undefined;
171
0
  }
172
4.24k
  return static_cast<heif_component_datatype>(format);
173
4.24k
}
174
175
176
static Error validate_component_indices(const std::vector<uint32_t>& indices,
177
                                        size_t cmpd_size,
178
                                        const char* box_name)
179
0
{
180
0
  for (uint32_t idx : indices) {
181
0
    if (idx >= cmpd_size) {
182
0
      return {heif_error_Invalid_input,
183
0
              heif_suberror_Invalid_parameter_value,
184
0
              std::string(box_name) + " component index out of range of cmpd table"};
185
0
    }
186
0
  }
187
0
  return Error::Ok;
188
0
}
189
190
191
Result<std::shared_ptr<HeifPixelImage>> UncompressedImageCodec::create_image(const unci_properties& properties,
192
                                                                             uint32_t width,
193
                                                                             uint32_t height,
194
                                                                             std::vector<uint32_t>& uncC_index_to_comp_ids,
195
                                                                             const heif_security_limits* limits)
196
1.06k
{
197
1.06k
  auto cmpd = properties.cmpd;
198
1.06k
  auto uncC = properties.uncC;
199
200
1.06k
  const auto& components = cmpd->get_components();
201
202
1.06k
  auto img = std::make_shared<HeifPixelImage>();
203
1.06k
  heif_chroma chroma = heif_chroma_undefined;
204
1.06k
  heif_colorspace colourspace = heif_colorspace_undefined;
205
206
1.06k
  Error error = get_heif_chroma_uncompressed(uncC, cmpd, &chroma, &colourspace, nullptr);
207
1.06k
  if (error) {
208
1
    return error;
209
1
  }
210
1.06k
  img->create(width, height,
211
1.06k
              colourspace,
212
1.06k
              chroma);
213
214
215
  // Clone the per-component descriptions (id, channel, type, format, datatype,
216
  // bit_depth) from the source ImageItem, populated at parse time. This is
217
  // what makes component IDs stable across the handle and decoded image.
218
1.06k
  if (properties.source_extra_data) {
219
1.06k
    img->clone_component_descriptions_from(*properties.source_extra_data);
220
221
    // Source descriptions carry the full-ispe plane sizes populated at parse
222
    // time. When this call is for a single tile, width/height are smaller, so
223
    // recompute the per-plane sizes from the actual target dimensions before
224
    // allocate_buffer_for_component() reads desc->width/height.
225
4.20k
    for (const auto& desc_view : img->get_component_descriptions()) {
226
4.20k
      if (!desc_view.has_data_plane) {
227
0
        continue;
228
0
      }
229
4.20k
      ComponentDescription* desc = img->find_component_description(desc_view.component_id);
230
4.20k
      desc->width = channel_width(width, chroma, desc->channel);
231
4.20k
      desc->height = channel_height(height, chroma, desc->channel);
232
4.20k
    }
233
1.06k
  }
234
235
  // Remember which components reference which cmpd indices.
236
  // There can be several component ids referencing the same cmpd index.
237
1.06k
  std::vector<std::vector<uint32_t>> cmpd_index_to_comp_ids(components.size());
238
239
  // Walk uncC. populate_component_descriptions() emitted one description per
240
  // uncC entry first, in order, so the first N descriptions in m_components
241
  // (where N == uncC->get_components().size()) correspond positionally to
242
  // the uncC entries we're walking here.
243
  //
244
  // Capture the number of pre-populated descriptions ONCE, before the loop.
245
  // The fallback branch below calls add_component(), which appends a new
246
  // description to the image; comparing desc_idx against the live
247
  // get_component_descriptions().size() would let those freshly added
248
  // descriptions flip later iterations onto the pre-populated branch. That
249
  // made a 3-component YCbCr sequence (no pre-populated descriptions) create
250
  // the Y plane, then mistake it for a pre-populated description on the next
251
  // iteration and re-reference the Y component instead of creating Cb, so the
252
  // Cb plane was dropped entirely.
253
1.06k
  const size_t num_prepopulated_descriptions = img->get_component_descriptions().size();
254
1.06k
  uint32_t desc_idx = 0;
255
4.19k
  for (Box_uncC::Component component : uncC->get_components()) {
256
4.19k
    if (component.component_index >= components.size()) {
257
0
      return Error{
258
0
        heif_error_Invalid_input,
259
0
        heif_suberror_Unspecified,
260
0
        "Component index out of range."
261
0
      };
262
0
    }
263
264
4.19k
    if (desc_idx >= num_prepopulated_descriptions) {
265
      // Fallback: source did not populate descriptions. This is the path taken
266
      // by the sequence ('uncv') decoder, which builds the properties directly
267
      // from the sample-entry boxes and does not clone descriptions from an
268
      // ImageItem.
269
0
      auto component_type = components[component.component_index].component_type;
270
0
      uint32_t plane_w = width;
271
0
      uint32_t plane_h = height;
272
0
      if (component_type == heif_cmpd_component_type_Cb ||
273
0
          component_type == heif_cmpd_component_type_Cr) {
274
        // Round the chroma plane size UP so that it covers the full logical
275
        // image, matching channel_width()/channel_height() used on the
276
        // description path above and the round-up chroma extent that the RGB
277
        // conversion (e.g. Op_YCbCr420_to_RGB24, which indexes chroma with
278
        // y/2 for every luma row) and primary_planes_have_size() expect. Floor
279
        // division here undersized the Cb/Cr planes by one row/column for odd
280
        // 4:2:0/4:2:2 dimensions, causing a heap-buffer-overflow read during
281
        // RGB conversion of sequence frames (GHSA-4h82-g446-83fm).
282
0
        plane_w = channel_width(width, chroma, heif_channel_Cb);
283
0
        plane_h = channel_height(height, chroma, heif_channel_Cb);
284
0
      }
285
0
      auto result = img->add_component(plane_w, plane_h, component_type,
286
0
                                       unc_component_format_to_datatype(component.component_format),
287
0
                                       component.component_bit_depth, limits);
288
0
      if (result.is_error()) {
289
0
        return result.error();
290
0
      }
291
0
      cmpd_index_to_comp_ids[component.component_index].push_back(*result);
292
0
      uncC_index_to_comp_ids.push_back(*result);
293
0
      continue;
294
0
    }
295
296
    // Pre-populated description path: the cloned description already has
297
    // the correct (chroma-subsampled) dimensions from
298
    // populate_component_descriptions(). Just allocate the buffer.
299
4.19k
    uint32_t comp_id = img->get_component_descriptions()[desc_idx].component_id;
300
4.19k
    if (auto err = img->allocate_buffer_for_component(comp_id, limits)) {
301
6
      return err;
302
6
    }
303
304
4.19k
    cmpd_index_to_comp_ids[component.component_index].push_back(comp_id);
305
4.19k
    uncC_index_to_comp_ids.push_back(comp_id);
306
4.19k
    desc_idx++;
307
4.19k
  }
308
309
310
  // --- assign the metadata boxes
311
312
1.06k
  size_t cmpd_size = cmpd ? cmpd->get_components().size() : 0;
313
314
1.06k
  if (properties.cpat) {
315
0
    const auto& pattern_cmpd = properties.cpat->get_pattern();
316
0
    std::vector<uint32_t> cpat_indices;
317
0
    cpat_indices.reserve(pattern_cmpd.pixels.size());
318
0
    for (const auto& pixel : pattern_cmpd.pixels) {
319
0
      cpat_indices.push_back(pixel.cmpd_index);
320
0
    }
321
0
    Error err = validate_component_indices(cpat_indices, cmpd_size, "cpat");
322
0
    if (err) {
323
0
      return err;
324
0
    }
325
326
    // Build BayerPattern. populate_component_descriptions added one
327
    // reference component per UNIQUE cmpd_index referenced by the pattern,
328
    // in first-occurrence order, immediately after the uncC components.
329
    // We rebuild the cmpd_index -> comp_id map in the same way and reuse
330
    // the existing IDs. (If descriptions weren't populated — fallback
331
    // path — we fall back to minting reference components here.)
332
0
    BayerPattern pattern;
333
0
    pattern.pattern_width = pattern_cmpd.pattern_width;
334
0
    pattern.pattern_height = pattern_cmpd.pattern_height;
335
0
    std::map<uint32_t, uint32_t> cpat_cmpd_idx_to_comp_id;
336
0
    for (auto p : pattern_cmpd.pixels) {
337
0
      uint32_t comp_id;
338
0
      auto it = cpat_cmpd_idx_to_comp_id.find(p.cmpd_index);
339
0
      if (it != cpat_cmpd_idx_to_comp_id.end()) {
340
0
        comp_id = it->second;
341
0
      }
342
0
      else if (desc_idx < num_prepopulated_descriptions) {
343
0
        comp_id = img->get_component_descriptions()[desc_idx].component_id;
344
0
        desc_idx++;
345
0
        cpat_cmpd_idx_to_comp_id[p.cmpd_index] = comp_id;
346
0
      }
347
0
      else {
348
0
        comp_id = img->add_component_without_data(components[p.cmpd_index].component_type);
349
0
        cpat_cmpd_idx_to_comp_id[p.cmpd_index] = comp_id;
350
0
      }
351
0
      pattern.pixels.push_back({comp_id, p.component_gain});
352
0
      cmpd_index_to_comp_ids[p.cmpd_index].push_back(comp_id);
353
0
    }
354
355
0
    img->set_bayer_pattern(pattern);
356
0
  }
357
358
1.06k
  for (const auto& splz_box : properties.splz) {
359
0
    const auto& pattern_cmpd = splz_box->get_pattern();
360
0
    Error err = validate_component_indices(pattern_cmpd.component_ids, cmpd_size, "splz");
361
0
    if (err) {
362
0
      return err;
363
0
    }
364
0
    PolarizationPattern pattern = pattern_cmpd;
365
0
    pattern.component_ids = map_cmpd_to_component_ids(pattern_cmpd.component_ids, cmpd_index_to_comp_ids);
366
0
    img->add_polarization_pattern(pattern);
367
0
  }
368
369
1.06k
  for (const auto& sbpm_box : properties.sbpm) {
370
0
    const auto& bad_pixels_map_cmpd = sbpm_box->get_bad_pixels_map();
371
0
    Error err = validate_component_indices(bad_pixels_map_cmpd.component_ids, cmpd_size, "sbpm");
372
0
    if (err) {
373
0
      return err;
374
0
    }
375
0
    SensorBadPixelsMap bad_pixels_map = bad_pixels_map_cmpd;
376
0
    bad_pixels_map.component_ids = map_cmpd_to_component_ids(bad_pixels_map_cmpd.component_ids, cmpd_index_to_comp_ids);
377
0
    img->add_sensor_bad_pixels_map(bad_pixels_map);
378
0
  }
379
380
1.06k
  for (const auto& snuc_box : properties.snuc) {
381
0
    const auto& nuc_cmpd = snuc_box->get_nuc();
382
0
    Error err = validate_component_indices(nuc_cmpd.component_ids, cmpd_size, "snuc");
383
0
    if (err) {
384
0
      return err;
385
0
    }
386
0
    SensorNonUniformityCorrection nuc = nuc_cmpd;
387
0
    nuc.component_ids = map_cmpd_to_component_ids(nuc_cmpd.component_ids, cmpd_index_to_comp_ids);
388
0
    img->add_sensor_nuc(nuc);
389
0
  }
390
391
1.06k
  if (properties.cloc) {
392
0
    img->set_chroma_location(properties.cloc->get_chroma_location());
393
0
  }
394
395
1.06k
  return img;
396
1.06k
}
397
398
399
Error UncompressedImageCodec::decode_uncompressed_image_tile(const HeifContext* context,
400
                                                             heif_item_id ID,
401
                                                             std::shared_ptr<HeifPixelImage>& img,
402
                                                             uint32_t tile_x0, uint32_t tile_y0)
403
0
{
404
0
  auto file = context->get_heif_file();
405
0
  auto image = context->get_image(ID, false);
406
0
  if (!image) {
407
0
    return {heif_error_Invalid_input,
408
0
            heif_suberror_Nonexisting_item_referenced};
409
0
  }
410
411
0
  UncompressedImageCodec::unci_properties properties;
412
0
  properties.fill_from_image_item(image);
413
414
0
  auto ispe = properties.ispe;
415
0
  auto uncC = properties.uncC;
416
0
  auto cmpd = properties.cmpd;
417
418
0
  Error error = check_header_validity(ispe, cmpd, uncC);
419
0
  if (error) {
420
0
    return error;
421
0
  }
422
423
  // Same uncC layout validation the full-image path runs in
424
  // unc_decoder::decode_full_image(). Without it, a per-tile decode could reach
425
  // a decoder with a component packing the full-image path rejects (e.g. a
426
  // block-pixel layout whose component bit depths sum to more than the block
427
  // width, which produces an undefined shift in
428
  // unc_decoder_block_pixel_interleave::decode_tile). The whole-image size is
429
  // deliberately NOT checked here: tiled images larger than the security limit
430
  // must still be decodable tile by tile.
431
0
  error = check_hard_limits(uncC);
432
0
  if (error) {
433
0
    return error;
434
0
  }
435
436
0
  uint32_t tile_width = ispe->get_width() / uncC->get_number_of_tile_columns();
437
0
  uint32_t tile_height = ispe->get_height() / uncC->get_number_of_tile_rows();
438
439
440
  // Remember which components reference which cmpd indices.
441
  // There can be several component ids referencing the same cmpd index.
442
0
  std::vector<std::vector<uint32_t>> cmpd_index_to_comp_ids;
443
0
  std::vector<uint32_t> uncC_index_to_comp_ids;
444
445
0
  Result<std::shared_ptr<HeifPixelImage>> createImgResult = create_image(properties, tile_width, tile_height, uncC_index_to_comp_ids, context->get_security_limits());
446
0
  if (!createImgResult) {
447
0
    return createImgResult.error();
448
0
  }
449
450
0
  img = *createImgResult;
451
452
0
  auto decoderResult = unc_decoder_factory::get_unc_decoder(ispe->get_width(), ispe->get_height(), cmpd, uncC, uncC_index_to_comp_ids);
453
0
  if (!decoderResult) {
454
0
    return decoderResult.error();
455
0
  }
456
457
0
  auto& decoder = *decoderResult;
458
459
0
  DataExtent dataExtent;
460
0
  dataExtent.set_from_image_item(file, ID);
461
462
0
  decoder->ensure_channel_list(img);
463
464
0
  std::vector<uint8_t> tile_data;
465
0
  Error err = decoder->fetch_tile_data(dataExtent, properties, tile_x0, tile_y0, tile_data);
466
0
  if (err) {
467
0
    return err;
468
0
  }
469
470
0
  return decoder->decode_tile(tile_data, img, 0, 0);
471
0
}
472
473
474
Error UncompressedImageCodec::check_header_validity(std::optional<const std::shared_ptr<const Box_ispe>> ispe,
475
                                                    const std::shared_ptr<const Box_cmpd>& cmpd,
476
                                                    const std::shared_ptr<const Box_uncC>& uncC)
477
4.31k
{
478
  // if we miss a required box, show error
479
480
4.31k
  if (!uncC) {
481
0
    return {heif_error_Unsupported_feature,
482
0
            heif_suberror_Unsupported_data_version,
483
0
            "Missing required uncC box for uncompressed codec"};
484
0
  }
485
486
4.31k
  if (!cmpd && (uncC->get_version() != 1)) {
487
0
    return {heif_error_Unsupported_feature,
488
0
            heif_suberror_Unsupported_data_version,
489
0
            "Missing required cmpd or uncC version 1 box for uncompressed codec"};
490
0
  }
491
492
4.31k
  if (cmpd) {
493
16.8k
    for (const auto& comp : uncC->get_components()) {
494
16.8k
      if (comp.component_index >= cmpd->get_components().size()) {
495
0
        return {heif_error_Invalid_input,
496
0
                heif_suberror_Unspecified,
497
0
                "Invalid component index in uncC box"};
498
0
      }
499
500
16.8k
      uint16_t component_type = cmpd->get_components()[comp.component_index].component_type;
501
16.8k
      if (component_type > 7 && component_type != heif_cmpd_component_type_padded && component_type != heif_cmpd_component_type_filter_array) {
502
0
        std::stringstream sstr;
503
0
        sstr << "Uncompressed image with component_type " << ((int) component_type) << " is not implemented yet";
504
0
        return {heif_error_Unsupported_feature,
505
0
                heif_suberror_Unsupported_data_version,
506
0
                sstr.str()};
507
0
      }
508
16.8k
    }
509
4.31k
  }
510
511
512
4.31k
  if (ispe) {
513
1.06k
    if (!*ispe) {
514
0
      return {heif_error_Unsupported_feature,
515
0
              heif_suberror_Unsupported_data_version,
516
0
              "Missing required ispe box for uncompressed codec"};
517
0
    }
518
519
1.06k
    if (uncC->get_number_of_tile_rows() > (*ispe)->get_height() ||
520
1.06k
        uncC->get_number_of_tile_columns() > (*ispe)->get_width()) {
521
0
      return {heif_error_Invalid_input,
522
0
              heif_suberror_Unspecified,
523
0
              "More tiles than pixels in uncC box"};
524
0
    }
525
526
1.06k
    if ((*ispe)->get_height() % uncC->get_number_of_tile_rows() != 0 ||
527
1.06k
        (*ispe)->get_width() % uncC->get_number_of_tile_columns() != 0) {
528
0
      return {heif_error_Invalid_input,
529
0
              heif_suberror_Unspecified,
530
0
              "Invalid tile size (image size not a multiple of the tile size)"};
531
0
    }
532
1.06k
  }
533
534
4.31k
  return Error::Ok;
535
4.31k
}
536
537
538
// TODO: this should be deprecated and replaced with the function taking unci_properties/DataExtent
539
Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* context,
540
                                                        heif_item_id ID,
541
                                                        std::shared_ptr<HeifPixelImage>& img)
542
1.06k
{
543
  // Get the properties for this item
544
  // We need: ispe, cmpd, uncC
545
1.06k
  std::vector<std::shared_ptr<Box>> item_properties;
546
1.06k
  Error error = context->get_heif_file()->get_properties(ID, item_properties);
547
1.06k
  if (error) {
548
0
    return error;
549
0
  }
550
551
1.06k
  auto image = context->get_image(ID, false);
552
1.06k
  if (!image) {
553
0
    return {heif_error_Invalid_input,
554
0
            heif_suberror_Nonexisting_item_referenced};
555
0
  }
556
557
1.06k
  UncompressedImageCodec::unci_properties properties;
558
1.06k
  properties.fill_from_image_item(image);
559
560
1.06k
  auto ispe = properties.ispe;
561
1.06k
  auto uncC = properties.uncC;
562
1.06k
  auto cmpd = properties.cmpd;
563
564
1.06k
  error = check_header_validity(ispe, cmpd, uncC);
565
1.06k
  if (error) {
566
0
    return error;
567
0
  }
568
569
1.06k
  assert(ispe);
570
1.06k
  uint32_t width = ispe->get_width();
571
1.06k
  uint32_t height = ispe->get_height();
572
1.06k
  error = check_for_valid_image_size(context->get_security_limits(), width, height);
573
1.06k
  if (error) {
574
0
    return error;
575
0
  }
576
577
1.06k
  if (uncC->get_pixel_size() > 0 &&
578
1.06k
      UINT32_MAX / uncC->get_pixel_size() / width < height) {
579
0
    return {
580
0
      heif_error_Invalid_input,
581
0
      heif_suberror_Unspecified,
582
0
      "Aligned total image size exceeds maximum integer range"
583
0
    };
584
0
  }
585
586
1.06k
  if (uncC->get_row_align_size() > 0 &&
587
1.06k
      UINT32_MAX / uncC->get_row_align_size() < 8) {
588
0
    return {
589
0
      heif_error_Invalid_input,
590
0
      heif_suberror_Unspecified,
591
0
      "Aligned row size larger than supported maximum"
592
0
    };
593
0
  }
594
595
1.06k
  DataExtent dataExtent;
596
1.06k
  dataExtent.set_from_image_item(context->get_heif_file(), ID);
597
598
1.06k
  auto result = unc_decoder::decode_full_image(properties, dataExtent, context->get_security_limits());
599
1.06k
  if (!result) {
600
985
    return result.error();
601
985
  }
602
603
84
  img = *result;
604
84
  return Error::Ok;
605
1.06k
}
606
607
608
void UncompressedImageCodec::unci_properties::fill_from_image_item(const std::shared_ptr<const ImageItem>& image)
609
1.06k
{
610
1.06k
  ispe = image->get_property<Box_ispe>();
611
1.06k
  auto cmpd_mut = image->get_property<Box_cmpd>();
612
1.06k
  auto uncC_mut = image->get_property<Box_uncC>();
613
1.06k
  if (uncC_mut) {
614
1.06k
    fill_uncC_and_cmpd_from_profile(uncC_mut, cmpd_mut);
615
1.06k
  }
616
1.06k
  cmpd = cmpd_mut;
617
1.06k
  uncC = uncC_mut;
618
1.06k
  cmpC = image->get_property<Box_cmpC>();
619
1.06k
  icef = image->get_property<Box_icef>();
620
1.06k
  cpat = image->get_property<Box_cpat>();
621
1.06k
  splz = image->get_all_properties<Box_splz>();
622
1.06k
  sbpm = image->get_all_properties<Box_sbpm>();
623
1.06k
  snuc = image->get_all_properties<Box_snuc>();
624
1.06k
  cloc = image->get_property<Box_cloc>();
625
626
  // The ImageItem already populated its ImageDescription::m_components in
627
  // ImageItem_uncompressed::populate_component_descriptions(). The decoder
628
  // clones those descriptions into the new HeifPixelImage instead of minting
629
  // ids itself.
630
1.06k
  source_extra_data = image.get();
631
1.06k
}
632
633
634
Result<std::shared_ptr<HeifPixelImage>>
635
UncompressedImageCodec::decode_uncompressed_image(const UncompressedImageCodec::unci_properties& properties,
636
                                                  const DataExtent& extent,
637
                                                  const heif_security_limits* securityLimits)
638
0
{
639
0
  const std::shared_ptr<const Box_ispe>& ispe = properties.ispe;
640
0
  const std::shared_ptr<const Box_cmpd>& cmpd = properties.cmpd;
641
0
  const std::shared_ptr<const Box_uncC>& uncC = properties.uncC;
642
643
0
  Error error = check_header_validity(ispe, cmpd, uncC);
644
0
  if (error) {
645
0
    return error;
646
0
  }
647
648
0
  assert(ispe);
649
0
  uint32_t width = ispe->get_width();
650
0
  uint32_t height = ispe->get_height();
651
0
  error = check_for_valid_image_size(securityLimits, width, height);
652
0
  if (error) {
653
0
    return error;
654
0
  }
655
656
0
  if (uncC->get_pixel_size() > 0 &&
657
0
      UINT32_MAX / uncC->get_pixel_size() / width < height) {
658
0
    return Error{
659
0
      heif_error_Invalid_input,
660
0
      heif_suberror_Unspecified,
661
0
      "Aligned total image size exceeds maximum integer range"
662
0
    };
663
0
  }
664
665
  // Same up-front row-alignment sanity check as the still-image decode path
666
  // (decode_uncompressed_image(context, ID, img)). The sequence path was missing
667
  // it, letting a huge row_align_size flow into the tile decoders. The tile
668
  // decoders themselves are now overflow-safe, but reject the nonsensical value
669
  // here too so both paths behave identically.
670
0
  if (uncC->get_row_align_size() > 0 &&
671
0
      UINT32_MAX / uncC->get_row_align_size() < 8) {
672
0
    return Error{
673
0
      heif_error_Invalid_input,
674
0
      heif_suberror_Unspecified,
675
0
      "Aligned row size larger than supported maximum"
676
0
    };
677
0
  }
678
679
0
  return unc_decoder::decode_full_image(properties, extent, securityLimits);
680
0
}