Coverage Report

Created: 2026-06-16 07:20

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