Coverage Report

Created: 2026-09-14 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image-items/grid.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2024 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 "grid.h"
22
#include "context.h"
23
#include "file.h"
24
#include <cstring>
25
#include <deque>
26
#include <future>
27
#include <mutex>
28
#include <set>
29
#include <algorithm>
30
#include <utility>
31
#include "api_structs.h"
32
#include "security_limits.h"
33
34
35
Error ImageGrid::parse(const std::vector<uint8_t>& data)
36
4.10k
{
37
4.10k
  if (data.size() < 8) {
38
21
    return {heif_error_Invalid_input,
39
21
            heif_suberror_Invalid_grid_data,
40
21
            "Less than 8 bytes of data"};
41
21
  }
42
43
4.07k
  uint8_t version = data[0];
44
4.07k
  if (version != 0) {
45
75
    std::stringstream sstr;
46
75
    sstr << "Grid image version " << ((int) version) << " is not supported";
47
75
    return {heif_error_Unsupported_feature,
48
75
            heif_suberror_Unsupported_data_version,
49
75
            sstr.str()};
50
75
  }
51
52
4.00k
  uint8_t flags = data[1];
53
4.00k
  int field_size = ((flags & 1) ? 32 : 16);
54
55
4.00k
  m_rows = static_cast<uint16_t>(data[2] + 1);
56
4.00k
  m_columns = static_cast<uint16_t>(data[3] + 1);
57
58
4.00k
  if (field_size == 32) {
59
49
    if (data.size() < 12) {
60
28
      return {heif_error_Invalid_input,
61
28
              heif_suberror_Invalid_grid_data,
62
28
              "Grid image data incomplete"};
63
28
    }
64
65
21
    m_output_width = four_bytes_to_uint32(data[4], data[5], data[6], data[7]);
66
21
    m_output_height = four_bytes_to_uint32(data[8], data[9], data[10], data[11]);
67
21
  }
68
3.95k
  else {
69
3.95k
    m_output_width = two_bytes_to_uint16(data[4], data[5]);
70
3.95k
    m_output_height = two_bytes_to_uint16(data[6], data[7]);
71
3.95k
  }
72
73
3.97k
  return Error::Ok;
74
4.00k
}
75
76
77
std::vector<uint8_t> ImageGrid::write() const
78
0
{
79
0
  int field_size;
80
81
0
  if (m_output_width > 0xFFFF ||
82
0
      m_output_height > 0xFFFF) {
83
0
    field_size = 32;
84
0
  }
85
0
  else {
86
0
    field_size = 16;
87
0
  }
88
89
0
  std::vector<uint8_t> data(field_size == 16 ? 8 : 12);
90
91
0
  data[0] = 0; // version
92
93
0
  uint8_t flags = 0;
94
0
  if (field_size == 32) {
95
0
    flags |= 1;
96
0
  }
97
98
0
  data[1] = flags;
99
0
  data[2] = (uint8_t) (m_rows - 1);
100
0
  data[3] = (uint8_t) (m_columns - 1);
101
102
0
  if (field_size == 32) {
103
0
    data[4] = (uint8_t) ((m_output_width >> 24) & 0xFF);
104
0
    data[5] = (uint8_t) ((m_output_width >> 16) & 0xFF);
105
0
    data[6] = (uint8_t) ((m_output_width >> 8) & 0xFF);
106
0
    data[7] = (uint8_t) ((m_output_width) & 0xFF);
107
108
0
    data[8] = (uint8_t) ((m_output_height >> 24) & 0xFF);
109
0
    data[9] = (uint8_t) ((m_output_height >> 16) & 0xFF);
110
0
    data[10] = (uint8_t) ((m_output_height >> 8) & 0xFF);
111
0
    data[11] = (uint8_t) ((m_output_height) & 0xFF);
112
0
  }
113
0
  else {
114
0
    data[4] = (uint8_t) ((m_output_width >> 8) & 0xFF);
115
0
    data[5] = (uint8_t) ((m_output_width) & 0xFF);
116
117
0
    data[6] = (uint8_t) ((m_output_height >> 8) & 0xFF);
118
0
    data[7] = (uint8_t) ((m_output_height) & 0xFF);
119
0
  }
120
121
0
  return data;
122
0
}
123
124
125
std::string ImageGrid::dump() const
126
0
{
127
0
  std::ostringstream sstr;
128
129
0
  sstr << "rows: " << m_rows << "\n"
130
0
       << "columns: " << m_columns << "\n"
131
0
       << "output width: " << m_output_width << "\n"
132
0
       << "output height: " << m_output_height << "\n";
133
134
0
  return sstr.str();
135
0
}
136
137
138
ImageItem_Grid::ImageItem_Grid(HeifContext* ctx)
139
0
    : ImageItem(ctx)
140
0
{
141
0
  m_tile_encoding_options = heif_encoding_options_alloc();
142
0
}
143
144
145
ImageItem_Grid::ImageItem_Grid(HeifContext* ctx, heif_item_id id)
146
3.19k
    : ImageItem(ctx, id)
147
3.19k
{
148
3.19k
  m_tile_encoding_options = heif_encoding_options_alloc();
149
3.19k
}
150
151
152
ImageItem_Grid::~ImageItem_Grid()
153
3.19k
{
154
3.19k
  heif_encoding_options_free(m_tile_encoding_options);
155
3.19k
}
156
157
158
Error ImageItem_Grid::initialize_decoder()
159
2.89k
{
160
2.89k
  Error err = read_grid_spec();
161
2.89k
  if (err) {
162
842
    return err;
163
842
  }
164
165
2.05k
  return Error::Ok;
166
2.89k
}
167
168
169
Error ImageItem_Grid::read_grid_spec()
170
2.89k
{
171
2.89k
  auto heif_file = get_context()->get_heif_file();
172
173
2.89k
  auto gridDataResult = heif_file->get_uncompressed_item_data(get_id());
174
2.89k
  if (!gridDataResult) {
175
495
    return gridDataResult.error();
176
495
  }
177
178
2.39k
  Error err = m_grid_spec.parse(*gridDataResult);
179
2.39k
  if (err) {
180
124
    return err;
181
124
  }
182
183
  //std::cout << grid.dump();
184
185
186
2.27k
  auto iref_box = heif_file->get_iref_box();
187
188
2.27k
  if (!iref_box) {
189
40
    return {heif_error_Invalid_input,
190
40
            heif_suberror_No_iref_box,
191
40
            "No iref box available, but needed for grid image"};
192
40
  }
193
194
2.23k
  m_grid_tile_ids = iref_box->get_references(get_id(), fourcc("dimg"));
195
196
2.23k
  if ((int) m_grid_tile_ids.size() != m_grid_spec.get_rows() * m_grid_spec.get_columns()) {
197
183
    std::stringstream sstr;
198
183
    sstr << "Tiled image with " << m_grid_spec.get_rows() << "x" << m_grid_spec.get_columns() << "="
199
183
         << (m_grid_spec.get_rows() * m_grid_spec.get_columns()) << " tiles, but only "
200
183
         << m_grid_tile_ids.size() << " tile images in file";
201
202
183
    return {heif_error_Invalid_input,
203
183
            heif_suberror_Missing_grid_images,
204
183
            sstr.str()};
205
183
  }
206
207
2.05k
  return Error::Ok;
208
2.23k
}
209
210
211
Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_compressed_image(const heif_decoding_options& options,
212
                                                                                bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
213
                                                                                DecodeTraversalState decode_state) const
214
1.67k
{
215
1.67k
  if (decode_state.processed_ids.contains(get_id())) {
216
0
    return Error{heif_error_Invalid_input,
217
0
                 heif_suberror_Unspecified,
218
0
                 "'iref' has cyclic references"};
219
0
  }
220
221
1.67k
  decode_state.processed_ids.insert(get_id());
222
223
224
1.67k
  if (decode_tile_only) {
225
0
    return decode_grid_tile(options, tile_x0, tile_y0, decode_state);
226
0
  }
227
1.67k
  else {
228
1.67k
    return decode_full_grid_image(options, decode_state);
229
1.67k
  }
230
1.67k
}
231
232
// Note: ImageItem_Grid does not override check_decoded_image_size(). The composed
233
// grid image is built to the grid-header size by construction (decode_and_paste_tile_image
234
// creates the canvas at get_grid_spec() size), so checking it against that same size
235
// would be tautological. The base default checks the composed image against 'ispe',
236
// which is the meaningful cross-check (grid-header size vs signaled size).
237
238
#if ENABLE_PARALLEL_TILE_DECODING
239
13
static void wait_for_jobs(std::deque<std::future<Error> >* jobs) {
240
13
  if (jobs->empty()) {
241
6
    return;
242
6
  }
243
244
19
  while (!jobs->empty()) {
245
12
    jobs->front().get();
246
12
    jobs->pop_front();
247
12
  }
248
7
}
249
#endif
250
251
Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(const heif_decoding_options& options, const DecodeTraversalState& decode_state) const
252
1.67k
{
253
1.67k
  std::shared_ptr<HeifPixelImage> img; // the decoded image
254
255
1.67k
  const ImageGrid& grid = get_grid_spec();
256
257
258
  // --- check that all image IDs are valid images
259
260
1.67k
  const std::vector<heif_item_id>& image_references = get_grid_tiles();
261
262
6.45k
  for (heif_item_id tile_id : image_references) {
263
6.45k
    if (!get_context()->is_image(tile_id)) {
264
75
      std::stringstream sstr;
265
75
      sstr << "Tile image ID=" << tile_id << " is not a proper image.";
266
267
75
      return Error(heif_error_Invalid_input,
268
75
                   heif_suberror_Missing_grid_images,
269
75
                   sstr.str());
270
75
    }
271
6.45k
  }
272
273
  //auto pixi = get_file()->get_property<Box_pixi>(get_id());
274
275
1.59k
  const uint32_t w = grid.get_width();
276
1.59k
  const uint32_t h = grid.get_height();
277
278
1.59k
  Error err = check_for_valid_image_size(get_context()->get_security_limits(), w, h);
279
1.59k
  if (err) {
280
15
    return err;
281
15
  }
282
283
1.58k
  uint32_t y0 = 0;
284
1.58k
  int reference_idx = 0;
285
286
1.58k
#if ENABLE_PARALLEL_TILE_DECODING
287
  // remember which tile to put where into the image
288
1.58k
  struct tile_data
289
1.58k
  {
290
1.58k
    heif_item_id tileID;
291
1.58k
    uint32_t x_origin, y_origin;
292
1.58k
  };
293
294
1.58k
  std::deque<tile_data> tiles;
295
1.58k
  if (get_context()->get_max_decoding_threads() > 0)
296
1.58k
    tiles.resize(static_cast<size_t>(grid.get_rows()) * static_cast<size_t>(grid.get_columns()));
297
298
1.58k
  std::deque<std::future<Error> > errs;
299
1.58k
#endif
300
301
1.58k
  uint32_t tile_width = 0;
302
1.58k
  uint32_t tile_height = 0;
303
304
1.58k
  if (options.start_progress) {
305
0
    options.start_progress(heif_progress_step_total, grid.get_rows() * grid.get_columns(), options.progress_user_data);
306
0
  }
307
1.58k
  if (options.on_progress) {
308
0
    options.on_progress(heif_progress_step_total, 0, options.progress_user_data);
309
0
  }
310
311
1.58k
  int progress_counter = 0;
312
1.58k
  bool cancelled = false;
313
1.58k
  std::shared_ptr<std::vector<Error> > warnings(new std::vector<Error>());
314
315
4.68k
  for (uint32_t y = 0; y < grid.get_rows() && !cancelled; y++) {
316
3.13k
    uint32_t x0 = 0;
317
318
9.24k
    for (uint32_t x = 0; x < grid.get_columns() && !cancelled; x++) {
319
320
6.14k
      heif_item_id tileID = image_references[reference_idx];
321
322
6.14k
      std::shared_ptr<const ImageItem> tileImg = get_context()->get_image(tileID, true);
323
6.14k
      if (!tileImg) {
324
0
        if (!options.strict_decoding && reference_idx != 0) {
325
          // Skip missing tiles (unless it's the first one).
326
0
          warnings->push_back(Error{
327
0
            heif_error_Invalid_input,
328
0
            heif_suberror_Missing_grid_images,
329
0
          });
330
0
          reference_idx++;
331
0
          x0 += tile_width;
332
0
          continue;
333
0
        }
334
335
0
        return Error{heif_error_Invalid_input,
336
0
                     heif_suberror_Missing_grid_images,
337
0
                     "Nonexistent grid image referenced"};
338
0
      }
339
6.14k
      if (auto error = tileImg->get_item_error()) {
340
972
        if (!options.strict_decoding && reference_idx != 0) {
341
          // Skip missing tiles (unless it's the first one).
342
972
          warnings->push_back(error);
343
972
          reference_idx++;
344
972
          x0 += tile_width;
345
972
          continue;
346
972
        }
347
348
0
        return error;
349
972
      }
350
351
5.17k
      uint32_t src_width = tileImg->get_width();
352
5.17k
      uint32_t src_height = tileImg->get_height();
353
5.17k
      err = check_for_valid_image_size(get_context()->get_security_limits(), src_width, src_height);
354
5.17k
      if (err) {
355
16
        return err;
356
16
      }
357
358
      // Integer division would let e.g. 9 tiles of 11px each "cover" a 107px canvas
359
      // (107/9 == 11), leaving an 8-pixel gap inside the visible image area.
360
5.16k
      if (static_cast<uint64_t>(src_width) * grid.get_columns() < grid.get_width() ||
361
5.15k
          static_cast<uint64_t>(src_height) * grid.get_rows() < grid.get_height()) {
362
13
        return Error{heif_error_Invalid_input,
363
13
                     heif_suberror_Invalid_grid_data,
364
13
                     "Grid tiles do not cover whole image"};
365
13
      }
366
367
5.14k
      if (x == 0 && y == 0) {
368
        // remember size of first tile and compare all other tiles against this
369
1.55k
        tile_width = src_width;
370
1.55k
        tile_height = src_height;
371
1.55k
      }
372
3.58k
      else if (src_width != tile_width || src_height != tile_height) {
373
9
        return Error{heif_error_Invalid_input,
374
9
                     heif_suberror_Invalid_grid_data,
375
9
                     "Grid tiles have different sizes"};
376
9
      }
377
378
5.13k
#if ENABLE_PARALLEL_TILE_DECODING
379
5.13k
      if (get_context()->get_max_decoding_threads() > 0)
380
5.13k
        tiles[x + y * grid.get_columns()] = tile_data{tileID, x0, y0};
381
0
      else
382
#else
383
        if (1)
384
#endif
385
0
      {
386
0
        if (options.cancel_decoding) {
387
0
          if (options.cancel_decoding(options.progress_user_data)) {
388
0
            cancelled = true;
389
0
          }
390
0
        }
391
392
0
        err = decode_and_paste_tile_image(tileID, x0, y0, img, options, progress_counter, warnings, decode_state);
393
0
        if (err) {
394
0
          return err;
395
0
        }
396
0
      }
397
398
5.13k
      x0 += src_width;
399
400
5.13k
      reference_idx++;
401
5.13k
    }
402
403
3.10k
    y0 += tile_height;
404
3.10k
  }
405
406
1.54k
#if ENABLE_PARALLEL_TILE_DECODING
407
1.54k
  if (get_context()->get_max_decoding_threads() > 0) {
408
    // Process all tiles in a set of background threads.
409
    // Do not start more than the maximum number of threads.
410
411
7.62k
    while (!tiles.empty() && !cancelled) {
412
413
      // If maximum number of threads running, wait until first thread finishes
414
415
6.08k
      if (errs.size() >= (size_t) get_context()->get_max_decoding_threads()) {
416
0
        Error e = errs.front().get();
417
0
        errs.pop_front();
418
0
        if (e) {
419
0
          wait_for_jobs(&errs);
420
0
          return e;
421
0
        }
422
0
      }
423
424
425
6.08k
      if (options.cancel_decoding) {
426
0
        if (options.cancel_decoding(options.progress_user_data)) {
427
0
          cancelled = true;
428
0
        }
429
0
      }
430
431
432
      // Start a new decoding thread
433
434
6.08k
      tile_data data = tiles.front();
435
6.08k
      tiles.pop_front();
436
437
6.08k
      errs.push_back(std::async(std::launch::async,
438
6.08k
                                &ImageItem_Grid::decode_and_paste_tile_image, this,
439
6.08k
                                data.tileID, data.x_origin, data.y_origin, std::ref(img), options,
440
6.08k
                                std::ref(progress_counter), warnings, decode_state));
441
6.08k
    }
442
443
    // check for decoding errors in remaining tiles
444
445
7.59k
    while (!errs.empty()) {
446
6.07k
      Error e = errs.front().get();
447
6.07k
      errs.pop_front();
448
6.07k
      if (e) {
449
13
        wait_for_jobs(&errs);
450
13
        return e;
451
13
      }
452
6.07k
    }
453
1.54k
  }
454
1.52k
#endif
455
456
1.52k
  if (options.end_progress) {
457
0
    options.end_progress(heif_progress_step_total, options.progress_user_data);
458
0
  }
459
460
1.52k
  if (cancelled) {
461
0
    return Error{heif_error_Canceled, heif_suberror_Unspecified, "Decoding the image was canceled"};
462
0
  }
463
464
1.52k
  if (!img) {
465
    // No tile could be decoded and the output canvas was never created.
466
    // Returning the null image would only produce a meaningless generic error
467
    // further up. Return the first per-tile error instead, which names the
468
    // actual cause, for example that no decoder plugin is installed (#1876).
469
626
    if (!warnings->empty()) {
470
626
      return warnings->front();
471
626
    }
472
473
0
    return Error{heif_error_Invalid_input,
474
0
                 heif_suberror_Invalid_grid_data,
475
0
                 "Grid image without tiles"};
476
626
  }
477
478
903
  img->add_warnings(*warnings.get());
479
480
903
  return img;
481
1.52k
}
482
483
6.06k
static Error progress_and_return_ok(const heif_decoding_options& options, int& progress_counter) {
484
6.06k
  if (options.on_progress) {
485
0
#if ENABLE_PARALLEL_TILE_DECODING
486
0
    static std::mutex progressMutex;
487
0
    std::lock_guard<std::mutex> lock(progressMutex);
488
0
#endif
489
490
0
    options.on_progress(heif_progress_step_total, ++progress_counter, options.progress_user_data);
491
0
  }
492
6.06k
  return Error::Ok;
493
6.06k
}
494
495
Error ImageItem_Grid::decode_and_paste_tile_image(heif_item_id tileID, uint32_t x0, uint32_t y0,
496
                                                  std::shared_ptr<HeifPixelImage>& inout_image,
497
                                                  const heif_decoding_options& options,
498
                                                  int& progress_counter,
499
                                                  const std::shared_ptr<std::vector<Error> >& warnings,
500
                                                  DecodeTraversalState decode_state) const
501
6.08k
{
502
6.08k
  std::shared_ptr<HeifPixelImage> tile_img;
503
6.08k
#if ENABLE_PARALLEL_TILE_DECODING
504
6.08k
  static std::mutex warningsMutex;
505
6.08k
#endif
506
507
6.08k
  auto tileItem = get_context()->get_image(tileID, true);
508
6.08k
  if (!tileItem && !options.strict_decoding) {
509
    // We ignore missing images. The un-pasted canvas region stays zero from calloc().
510
936
#if ENABLE_PARALLEL_TILE_DECODING
511
936
    std::lock_guard<std::mutex> lock(warningsMutex);
512
936
#endif
513
936
    warnings->emplace_back(
514
936
      heif_error_Invalid_input,
515
936
      heif_suberror_Missing_grid_images,
516
936
      "Missing grid image"
517
936
    );
518
936
    return progress_and_return_ok(options, progress_counter);
519
936
  }
520
521
6.08k
  assert(tileItem);
522
5.14k
  if (auto error = tileItem->get_item_error()) {
523
22
    return error;
524
22
  }
525
526
5.12k
  auto decodeResult = tileItem->decode_image(options, false, 0, 0, std::move(decode_state));
527
5.12k
  if (!decodeResult) {
528
3.80k
    if (!options.strict_decoding) {
529
      // We ignore broken tiles. The un-pasted canvas region stays zero from calloc().
530
3.80k
#if ENABLE_PARALLEL_TILE_DECODING
531
3.80k
      std::lock_guard<std::mutex> lock(warningsMutex);
532
3.80k
#endif
533
3.80k
      warnings->push_back(decodeResult.error());
534
3.80k
      return progress_and_return_ok(options, progress_counter);
535
3.80k
    }
536
537
18.4E
    return decodeResult.error();
538
3.80k
  }
539
540
1.31k
  tile_img = *decodeResult;
541
542
1.31k
  uint32_t w = get_grid_spec().get_width();
543
1.31k
  uint32_t h = get_grid_spec().get_height();
544
545
  // --- generate the image canvas for combining all the tiles
546
547
1.31k
  {
548
1.31k
#if ENABLE_PARALLEL_TILE_DECODING
549
    // All threads have to take this mutex, even those that will only read `inout_image`.
550
    // Otherwise, the image initialization would not be synchronized to them (they could,
551
    // for example, see the image pointer before the image content initialization is visible).
552
1.31k
    static std::mutex createImageMutex;
553
1.31k
    std::lock_guard<std::mutex> lock(createImageMutex);
554
1.31k
#endif
555
556
1.31k
    if (!inout_image) {
557
903
      auto grid_image = std::make_shared<HeifPixelImage>();
558
903
      auto err = grid_image->create_clone_image_at_new_size(tile_img, w, h, get_context()->get_security_limits());
559
903
      if (err) {
560
0
        return err;
561
0
      }
562
563
      // Fill alpha plane with opaque in case not all tiles have alpha planes
564
565
903
      if (grid_image->has_channel(heif_channel_Alpha)) {
566
0
        uint16_t alpha_bpp = grid_image->get_bits_per_pixel(heif_channel_Alpha);
567
0
        assert(alpha_bpp <= 16);
568
569
0
        auto alpha_default_value = static_cast<uint16_t>((1UL << alpha_bpp) - 1UL);
570
0
        grid_image->fill_channel(heif_channel_Alpha, alpha_default_value);
571
0
      }
572
573
903
      grid_image->copy_metadata_from(*tile_img);
574
575
903
      inout_image = grid_image;
576
903
    }
577
1.31k
  }
578
579
  // --- copy tile into output image
580
581
1.31k
  heif_chroma chroma = inout_image->get_chroma_format();
582
583
1.31k
  if (chroma != tile_img->get_chroma_format()) {
584
0
    return {heif_error_Invalid_input,
585
0
            heif_suberror_Wrong_tile_image_chroma_format,
586
0
            "Image tile has different chroma format than combined image"};
587
0
  }
588
589
590
1.31k
  inout_image->copy_image_to(tile_img, x0, y0);
591
592
1.31k
  return progress_and_return_ok(options, progress_counter);
593
1.31k
}
594
595
596
Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty,
597
                                                                         DecodeTraversalState decode_state) const
598
0
{
599
0
  uint32_t idx = ty * m_grid_spec.get_columns() + tx;
600
601
0
  if (idx >= m_grid_tile_ids.size()) {
602
0
    return Error{heif_error_Invalid_input,
603
0
                 heif_suberror_Missing_grid_images,
604
0
                 "Grid tile coordinate out of range"};
605
0
  }
606
607
0
  heif_item_id tile_id = m_grid_tile_ids[idx];
608
0
  std::shared_ptr<const ImageItem> tile_item = get_context()->get_image(tile_id, true);
609
0
  if (!tile_item) {
610
0
    return Error{heif_error_Invalid_input,
611
0
                 heif_suberror_Missing_grid_images,
612
0
                 "Grid tile references a non-existent item"};
613
0
  }
614
0
  if (auto error = tile_item->get_item_error()) {
615
0
    return error;
616
0
  }
617
618
0
  return tile_item->decode_compressed_image(options, false, 0, 0, std::move(decode_state));
619
0
}
620
621
622
void ImageItem_Grid::set_grid_tile_id(uint32_t tile_x, uint32_t tile_y, heif_item_id id)
623
0
{
624
0
  uint32_t idx = tile_y * m_grid_spec.get_columns() + tile_x;
625
0
  m_grid_tile_ids[idx] = id;
626
0
}
627
628
629
heif_image_tiling ImageItem_Grid::get_heif_image_tiling() const
630
0
{
631
0
  heif_image_tiling tiling{};
632
633
0
  const ImageGrid& gridspec = get_grid_spec();
634
0
  tiling.num_columns = gridspec.get_columns();
635
0
  tiling.num_rows = gridspec.get_rows();
636
637
0
  tiling.image_width = gridspec.get_width();
638
0
  tiling.image_height = gridspec.get_height();
639
0
  tiling.number_of_extra_dimensions = 0;
640
641
0
  auto tile_ids = get_grid_tiles();
642
0
  if (!tile_ids.empty() && tile_ids[0] != 0) {
643
0
    heif_item_id tile0_id = tile_ids[0];
644
0
    auto tile0 = get_context()->get_image(tile0_id, true);
645
0
    if (tile0 == nullptr || tile0->get_item_error()) {
646
0
      return tiling;
647
0
    }
648
649
0
    tiling.tile_width = tile0->get_width();
650
0
    tiling.tile_height = tile0->get_height();
651
0
  }
652
0
  else {
653
0
    tiling.tile_width = 0;
654
0
    tiling.tile_height = 0;
655
0
  }
656
657
0
  return tiling;
658
0
}
659
660
661
void ImageItem_Grid::get_tile_size(uint32_t& w, uint32_t& h) const
662
0
{
663
0
  const auto& tile_ids = get_grid_tiles();
664
0
  if (tile_ids.empty() || tile_ids[0] == 0) {
665
0
    w = h = 0;
666
0
    return;
667
0
  }
668
669
0
  auto tile = get_context()->get_image(tile_ids[0], true);
670
0
  if (tile == nullptr || tile->get_item_error()) {
671
0
    w = h = 0;
672
0
    return;
673
0
  }
674
675
0
  w = tile->get_width();
676
0
  h = tile->get_height();
677
0
}
678
679
680
681
int ImageItem_Grid::get_luma_bits_per_pixel() const
682
3.48k
{
683
3.48k
  auto child_result = get_context()->find_first_coded_image_id(get_id());
684
3.48k
  if (child_result.is_error()) {
685
129
    return -1;
686
129
  }
687
688
3.35k
  auto image = get_context()->get_image(*child_result, true);
689
3.35k
  if (!image) {
690
0
    return -1;
691
0
  }
692
693
3.35k
  return image->get_luma_bits_per_pixel();
694
3.35k
}
695
696
697
int ImageItem_Grid::get_chroma_bits_per_pixel() const
698
1.58k
{
699
1.58k
  auto child_result = get_context()->find_first_coded_image_id(get_id());
700
1.58k
  if (child_result.is_error()) {
701
0
    return -1;
702
0
  }
703
704
1.58k
  auto image = get_context()->get_image(*child_result, true);
705
1.58k
  return image->get_chroma_bits_per_pixel();
706
1.58k
}
707
708
Result<std::shared_ptr<Decoder>> ImageItem_Grid::get_decoder() const
709
8.77k
{
710
8.77k
  auto child_result = get_context()->find_first_coded_image_id(get_id());
711
8.77k
  if (child_result.is_error()) {
712
1.91k
    return child_result.error();
713
1.91k
  }
714
715
6.85k
  auto image = get_context()->get_image(*child_result, true);
716
6.85k
  if (!image) {
717
0
    return Error{heif_error_Invalid_input,
718
0
      heif_suberror_Nonexisting_item_referenced};
719
0
  }
720
6.85k
  else if (auto err = image->get_item_error()) {
721
132
    return err;
722
132
  }
723
724
6.72k
  return image->get_decoder();
725
6.85k
}
726
727
728
void ImageItem_Grid::populate_component_descriptions()
729
4.94k
{
730
4.94k
  if (!get_component_descriptions().empty()) {
731
1.56k
    return;
732
1.56k
  }
733
734
3.38k
  if (m_grid_tile_ids.empty()) {
735
2.89k
    ImageItem::populate_component_descriptions();
736
2.89k
    return;
737
2.89k
  }
738
739
490
  auto child = get_context()->get_image(m_grid_tile_ids[0], true);
740
490
  if (!child) {
741
418
    ImageItem::populate_component_descriptions();
742
418
    return;
743
418
  }
744
745
  // Try child-delegation first (correct for unci children with float/signed/
746
  // complex datatypes). If the child has no descriptions yet (e.g. it's a
747
  // visual codec without an initialized decoder), fall back to the base
748
  // populate which queries this item's own colorspace/bpp accessors (which
749
  // already delegate to the child).
750
72
  if (!populate_descriptions_from_child(*child, child->get_width(), child->get_height())) {
751
72
    ImageItem::populate_component_descriptions();
752
72
  }
753
72
}
754
755
756
Result<std::shared_ptr<ImageItem_Grid>> ImageItem_Grid::add_new_grid_item(HeifContext* ctx,
757
                                                                          uint32_t output_width,
758
                                                                          uint32_t output_height,
759
                                                                          uint16_t tile_rows,
760
                                                                          uint16_t tile_columns,
761
                                                                          const heif_encoding_options* encoding_options)
762
0
{
763
0
  std::shared_ptr<ImageItem_Grid> grid_image;
764
0
  if (tile_rows > 0xFFFF / tile_columns) {
765
0
    return Error{heif_error_Usage_error,
766
0
                 heif_suberror_Unspecified,
767
0
                 "Too many tiles (maximum: 65535)"};
768
0
  }
769
770
  // Create ImageGrid
771
772
0
  ImageGrid grid;
773
0
  grid.set_num_tiles(tile_columns, tile_rows);
774
0
  grid.set_output_size(output_width, output_height); // TODO: MIAF restricts the output size to be a multiple of the chroma subsampling (7.3.11.4.2)
775
0
  std::vector<uint8_t> grid_data = grid.write();
776
777
  // Create Grid Item
778
779
0
  std::shared_ptr<HeifFile> file = ctx->get_heif_file();
780
0
  auto grid_id_result = file->add_new_image(fourcc("grid"));
781
0
  if (!grid_id_result) {
782
0
    return grid_id_result.error();
783
0
  }
784
0
  heif_item_id grid_id = *grid_id_result;
785
0
  grid_image = std::make_shared<ImageItem_Grid>(ctx, grid_id);
786
0
  grid_image->set_tile_encoding_options(encoding_options);
787
0
  grid_image->set_grid_spec(grid);
788
0
  grid_image->set_resolution(output_width, output_height);
789
0
  grid_image->m_grid_orientation = encoding_options->image_orientation;
790
791
0
  ctx->insert_image_item(grid_id, grid_image);
792
0
  const int construction_method = 1; // 0=mdat 1=idat
793
0
  file->append_iloc_data(grid_id, grid_data, construction_method);
794
795
  // generate dummy grid item IDs (0)
796
0
  std::vector<heif_item_id> tile_ids;
797
0
  tile_ids.resize(static_cast<size_t>(tile_rows) * static_cast<size_t>(tile_columns));
798
799
  // Connect tiles to grid
800
0
  file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids);
801
802
  // Add ISPE property
803
0
  if (Error err = file->add_ispe_property(grid_id, output_width, output_height, false)) {
804
0
    return err;
805
0
  }
806
807
  // PIXI property will be added when the first tile is set
808
809
  // Set Brands
810
  //m_heif_file->set_brand(encoder->plugin->compression_format,
811
  //                       grid_image->is_miaf_compatible());
812
813
0
  return grid_image;
814
0
}
815
816
void ImageItem_Grid::set_tile_encoding_options(const heif_encoding_options* options)
817
0
{
818
0
  heif_encoding_options_copy(m_tile_encoding_options, options);
819
820
  // do not propagate image transformation to tiles
821
0
  m_tile_encoding_options->image_orientation = heif_orientation_normal;
822
0
}
823
824
825
Error ImageItem_Grid::add_image_tile(uint32_t tile_x, uint32_t tile_y,
826
                                     const std::shared_ptr<HeifPixelImage>& image,
827
                                     heif_encoder* encoder)
828
0
{
829
0
  auto encodingResult = get_context()->encode_image(image,
830
0
                                            encoder,
831
0
                                            *m_tile_encoding_options,
832
0
                                            heif_image_input_class_normal);
833
0
  if (!encodingResult) {
834
0
    return encodingResult.error();
835
0
  }
836
837
0
  std::shared_ptr<ImageItem> encoded_image = *encodingResult;
838
839
0
  auto file = get_file();
840
0
  file->get_infe_box(encoded_image->get_id())->set_hidden_item(true); // grid tiles are hidden items
841
842
  // Assign tile to grid
843
0
  heif_image_tiling tiling = get_heif_image_tiling();
844
0
  file->set_iref_reference(get_id(), fourcc("dimg"), tile_y * tiling.num_columns + tile_x, encoded_image->get_id());
845
846
0
  set_grid_tile_id(tile_x, tile_y, encoded_image->get_id());
847
848
  // Add PIXI property (copy from first tile)
849
0
  auto pixi = encoded_image->get_property<Box_pixi>();
850
0
  add_property(pixi, true);
851
852
  // copy over extra properties to grid item
853
854
0
  if (tile_x == 0 && tile_y == 0) {
855
0
    auto property_boxes = encoded_image->generate_property_boxes(false);
856
0
    for (auto& property : property_boxes) {
857
0
      add_property(property, is_property_essential(property));
858
0
    }
859
860
    // add color profile similar to first tile image
861
    // TODO: this shouldn't be necessary. The colr profiles should be in the ImageDescription above.
862
0
    auto colr_boxes = add_color_profile(image, *m_tile_encoding_options,
863
0
                                        heif_image_input_class_normal,
864
0
                                        m_tile_encoding_options->output_nclx_profile);
865
0
    for (auto& property : colr_boxes) {
866
0
      add_property(property, is_property_essential(property));
867
0
    }
868
869
    // Add transformative properties
870
871
0
    if (Error err = get_context()->get_heif_file()->add_orientation_properties(get_id(), m_grid_orientation)) {
872
0
      return err;
873
0
    }
874
0
  }
875
876
0
  return Error::Ok;
877
0
}
878
879
880
Result<std::shared_ptr<ImageItem_Grid>> ImageItem_Grid::add_and_encode_full_grid(HeifContext* ctx,
881
                                                                                 const std::vector<std::shared_ptr<HeifPixelImage>>& tiles,
882
                                                                                 uint16_t rows,
883
                                                                                 uint16_t columns,
884
                                                                                 heif_encoder* encoder,
885
                                                                                 const heif_encoding_options& options)
886
0
{
887
0
  std::shared_ptr<ImageItem_Grid> griditem;
888
889
  // Create ImageGrid
890
891
0
  ImageGrid grid;
892
0
  grid.set_num_tiles(columns, rows);
893
0
  uint32_t tile_width = tiles[0]->get_width();
894
0
  uint32_t tile_height = tiles[0]->get_height();
895
0
  grid.set_output_size(tile_width * columns, tile_height * rows);
896
0
  std::vector<uint8_t> grid_data = grid.write();
897
898
0
  auto file = ctx->get_heif_file();
899
900
  // Encode Tiles
901
902
0
  std::vector<heif_item_id> tile_ids;
903
904
0
  std::shared_ptr<Box_pixi> pixi_property;
905
906
0
  for (int i=0; i<rows*columns; i++) {
907
0
    std::shared_ptr<ImageItem> out_tile;
908
0
    auto encodingResult = ctx->encode_image(tiles[i],
909
0
                                            encoder,
910
0
                                            options,
911
0
                                            heif_image_input_class_normal);
912
0
    if (!encodingResult) {
913
0
      return encodingResult.error();
914
0
    }
915
0
    else {
916
0
      out_tile = *encodingResult;
917
0
    }
918
919
0
    heif_item_id tile_id = out_tile->get_id();
920
0
    file->get_infe_box(tile_id)->set_hidden_item(true); // only show the full grid
921
0
    tile_ids.push_back(out_tile->get_id());
922
923
0
    if (!pixi_property) {
924
0
      pixi_property = out_tile->get_property<Box_pixi>();
925
0
    }
926
0
  }
927
928
  // Create Grid Item
929
930
0
  auto grid_id_result = file->add_new_image(fourcc("grid"));
931
0
  if (!grid_id_result) {
932
0
    return grid_id_result.error();
933
0
  }
934
0
  heif_item_id grid_id = *grid_id_result;
935
0
  griditem = std::make_shared<ImageItem_Grid>(ctx, grid_id);
936
0
  ctx->insert_image_item(grid_id, griditem);
937
0
  const int construction_method = 1; // 0=mdat 1=idat
938
0
  file->append_iloc_data(grid_id, grid_data, construction_method);
939
940
  // Connect tiles to grid
941
942
0
  file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids);
943
944
  // Add ISPE property
945
946
0
  uint32_t image_width = tile_width * columns;
947
0
  uint32_t image_height = tile_height * rows;
948
949
0
  auto ispe = std::make_shared<Box_ispe>();
950
0
  ispe->set_size(image_width, image_height);
951
0
  griditem->add_property(ispe, false);
952
953
  // Add PIXI property (copy from first tile)
954
955
0
  griditem->add_property(pixi_property, true);
956
957
  // copy over extra properties to grid item
958
959
0
  auto property_boxes = tiles[0]->generate_property_boxes(true);
960
0
  for (auto& property : property_boxes) {
961
0
    griditem->add_property(property, griditem->is_property_essential(property));
962
0
  }
963
964
  // Set Brands
965
966
  //file->set_brand(encoder->plugin->compression_format,
967
  //                griditem->is_miaf_compatible());
968
969
0
  return griditem;
970
0
}
971
972
heif_brand2 ImageItem_Grid::get_compatible_brand() const
973
0
{
974
0
  if (m_grid_tile_ids.empty()) { return 0; }
975
976
0
  heif_item_id child_id = m_grid_tile_ids[0];
977
0
  auto child = get_context()->get_image(child_id, false);
978
0
  if (!child) { return 0; }
979
980
0
  return child->get_compatible_brand();
981
0
}