Coverage Report

Created: 2026-09-01 06:57

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
1.58k
{
37
1.58k
  if (data.size() < 8) {
38
20
    return {heif_error_Invalid_input,
39
20
            heif_suberror_Invalid_grid_data,
40
20
            "Less than 8 bytes of data"};
41
20
  }
42
43
1.56k
  uint8_t version = data[0];
44
1.56k
  if (version != 0) {
45
74
    std::stringstream sstr;
46
74
    sstr << "Grid image version " << ((int) version) << " is not supported";
47
74
    return {heif_error_Unsupported_feature,
48
74
            heif_suberror_Unsupported_data_version,
49
74
            sstr.str()};
50
74
  }
51
52
1.49k
  uint8_t flags = data[1];
53
1.49k
  int field_size = ((flags & 1) ? 32 : 16);
54
55
1.49k
  m_rows = static_cast<uint16_t>(data[2] + 1);
56
1.49k
  m_columns = static_cast<uint16_t>(data[3] + 1);
57
58
1.49k
  if (field_size == 32) {
59
45
    if (data.size() < 12) {
60
23
      return {heif_error_Invalid_input,
61
23
              heif_suberror_Invalid_grid_data,
62
23
              "Grid image data incomplete"};
63
23
    }
64
65
22
    m_output_width = four_bytes_to_uint32(data[4], data[5], data[6], data[7]);
66
22
    m_output_height = four_bytes_to_uint32(data[8], data[9], data[10], data[11]);
67
22
  }
68
1.44k
  else {
69
1.44k
    m_output_width = two_bytes_to_uint16(data[4], data[5]);
70
1.44k
    m_output_height = two_bytes_to_uint16(data[6], data[7]);
71
1.44k
  }
72
73
1.46k
  return Error::Ok;
74
1.49k
}
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
2.42k
    : ImageItem(ctx, id)
147
2.42k
{
148
2.42k
  m_tile_encoding_options = heif_encoding_options_alloc();
149
2.42k
}
150
151
152
ImageItem_Grid::~ImageItem_Grid()
153
2.42k
{
154
2.42k
  heif_encoding_options_free(m_tile_encoding_options);
155
2.42k
}
156
157
158
Error ImageItem_Grid::initialize_decoder()
159
2.19k
{
160
2.19k
  Error err = read_grid_spec();
161
2.19k
  if (err) {
162
868
    return err;
163
868
  }
164
165
1.32k
  return Error::Ok;
166
2.19k
}
167
168
169
Error ImageItem_Grid::read_grid_spec()
170
2.19k
{
171
2.19k
  auto heif_file = get_context()->get_heif_file();
172
173
2.19k
  auto gridDataResult = heif_file->get_uncompressed_item_data(get_id());
174
2.19k
  if (!gridDataResult) {
175
606
    return gridDataResult.error();
176
606
  }
177
178
1.58k
  Error err = m_grid_spec.parse(*gridDataResult);
179
1.58k
  if (err) {
180
117
    return err;
181
117
  }
182
183
  //std::cout << grid.dump();
184
185
186
1.46k
  auto iref_box = heif_file->get_iref_box();
187
188
1.46k
  if (!iref_box) {
189
31
    return {heif_error_Invalid_input,
190
31
            heif_suberror_No_iref_box,
191
31
            "No iref box available, but needed for grid image"};
192
31
  }
193
194
1.43k
  m_grid_tile_ids = iref_box->get_references(get_id(), fourcc("dimg"));
195
196
1.43k
  if ((int) m_grid_tile_ids.size() != m_grid_spec.get_rows() * m_grid_spec.get_columns()) {
197
114
    std::stringstream sstr;
198
114
    sstr << "Tiled image with " << m_grid_spec.get_rows() << "x" << m_grid_spec.get_columns() << "="
199
114
         << (m_grid_spec.get_rows() * m_grid_spec.get_columns()) << " tiles, but only "
200
114
         << m_grid_tile_ids.size() << " tile images in file";
201
202
114
    return {heif_error_Invalid_input,
203
114
            heif_suberror_Missing_grid_images,
204
114
            sstr.str()};
205
114
  }
206
207
1.32k
  return Error::Ok;
208
1.43k
}
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
37.2k
{
215
37.2k
  if (decode_state.processed_ids.contains(get_id())) {
216
5
    return Error{heif_error_Invalid_input,
217
5
                 heif_suberror_Unspecified,
218
5
                 "'iref' has cyclic references"};
219
5
  }
220
221
37.2k
  decode_state.processed_ids.insert(get_id());
222
223
224
37.2k
  if (decode_tile_only) {
225
36.1k
    return decode_grid_tile(options, tile_x0, tile_y0, decode_state);
226
36.1k
  }
227
1.11k
  else {
228
1.11k
    return decode_full_grid_image(options, decode_state);
229
1.11k
  }
230
37.2k
}
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
62
static void wait_for_jobs(std::deque<std::future<Error> >* jobs) {
240
62
  if (jobs->empty()) {
241
0
    return;
242
0
  }
243
244
248
  while (!jobs->empty()) {
245
186
    jobs->front().get();
246
186
    jobs->pop_front();
247
186
  }
248
62
}
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.11k
{
253
1.11k
  std::shared_ptr<HeifPixelImage> img; // the decoded image
254
255
1.11k
  const ImageGrid& grid = get_grid_spec();
256
257
258
  // --- check that all image IDs are valid images
259
260
1.11k
  const std::vector<heif_item_id>& image_references = get_grid_tiles();
261
262
47.8k
  for (heif_item_id tile_id : image_references) {
263
47.8k
    if (!get_context()->is_image(tile_id)) {
264
85
      std::stringstream sstr;
265
85
      sstr << "Tile image ID=" << tile_id << " is not a proper image.";
266
267
85
      return Error(heif_error_Invalid_input,
268
85
                   heif_suberror_Missing_grid_images,
269
85
                   sstr.str());
270
85
    }
271
47.8k
  }
272
273
  //auto pixi = get_file()->get_property<Box_pixi>(get_id());
274
275
1.03k
  const uint32_t w = grid.get_width();
276
1.03k
  const uint32_t h = grid.get_height();
277
278
1.03k
  Error err = check_for_valid_image_size(get_context()->get_security_limits(), w, h);
279
1.03k
  if (err) {
280
12
    return err;
281
12
  }
282
283
1.02k
  uint32_t y0 = 0;
284
1.02k
  int reference_idx = 0;
285
286
1.02k
#if ENABLE_PARALLEL_TILE_DECODING
287
  // remember which tile to put where into the image
288
1.02k
  struct tile_data
289
1.02k
  {
290
1.02k
    heif_item_id tileID;
291
1.02k
    uint32_t x_origin, y_origin;
292
1.02k
  };
293
294
1.02k
  std::deque<tile_data> tiles;
295
1.02k
  if (get_context()->get_max_decoding_threads() > 0)
296
1.02k
    tiles.resize(static_cast<size_t>(grid.get_rows()) * static_cast<size_t>(grid.get_columns()));
297
298
1.02k
  std::deque<std::future<Error> > errs;
299
1.02k
#endif
300
301
1.02k
  uint32_t tile_width = 0;
302
1.02k
  uint32_t tile_height = 0;
303
304
1.02k
  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.02k
  if (options.on_progress) {
308
0
    options.on_progress(heif_progress_step_total, 0, options.progress_user_data);
309
0
  }
310
311
1.02k
  int progress_counter = 0;
312
1.02k
  bool cancelled = false;
313
1.02k
  std::shared_ptr<std::vector<Error> > warnings(new std::vector<Error>());
314
315
6.81k
  for (uint32_t y = 0; y < grid.get_rows() && !cancelled; y++) {
316
5.84k
    uint32_t x0 = 0;
317
318
52.0k
    for (uint32_t x = 0; x < grid.get_columns() && !cancelled; x++) {
319
320
46.2k
      heif_item_id tileID = image_references[reference_idx];
321
322
46.2k
      std::shared_ptr<const ImageItem> tileImg = get_context()->get_image(tileID, true);
323
46.2k
      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
46.2k
      if (auto error = tileImg->get_item_error()) {
340
18.8k
        if (!options.strict_decoding && reference_idx != 0) {
341
          // Skip missing tiles (unless it's the first one).
342
18.8k
          warnings->push_back(error);
343
18.8k
          reference_idx++;
344
18.8k
          x0 += tile_width;
345
18.8k
          continue;
346
18.8k
        }
347
348
18
        return error;
349
18.8k
      }
350
351
27.3k
      uint32_t src_width = tileImg->get_width();
352
27.3k
      uint32_t src_height = tileImg->get_height();
353
27.3k
      err = check_for_valid_image_size(get_context()->get_security_limits(), src_width, src_height);
354
27.3k
      if (err) {
355
21
        return err;
356
21
      }
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
27.3k
      if (static_cast<uint64_t>(src_width) * grid.get_columns() < grid.get_width() ||
361
27.3k
          static_cast<uint64_t>(src_height) * grid.get_rows() < grid.get_height()) {
362
9
        return Error{heif_error_Invalid_input,
363
9
                     heif_suberror_Invalid_grid_data,
364
9
                     "Grid tiles do not cover whole image"};
365
9
      }
366
367
27.3k
      if (x == 0 && y == 0) {
368
        // remember size of first tile and compare all other tiles against this
369
978
        tile_width = src_width;
370
978
        tile_height = src_height;
371
978
      }
372
26.3k
      else if (src_width != tile_width || src_height != tile_height) {
373
3
        return Error{heif_error_Invalid_input,
374
3
                     heif_suberror_Invalid_grid_data,
375
3
                     "Grid tiles have different sizes"};
376
3
      }
377
378
27.3k
#if ENABLE_PARALLEL_TILE_DECODING
379
27.3k
      if (get_context()->get_max_decoding_threads() > 0)
380
27.3k
        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
27.3k
      x0 += src_width;
399
400
27.3k
      reference_idx++;
401
27.3k
    }
402
403
5.79k
    y0 += tile_height;
404
5.79k
  }
405
406
969
#if ENABLE_PARALLEL_TILE_DECODING
407
969
  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
44.7k
    while (!tiles.empty() && !cancelled) {
412
413
      // If maximum number of threads running, wait until first thread finishes
414
415
43.8k
      if (errs.size() >= (size_t) get_context()->get_max_decoding_threads()) {
416
39.9k
        Error e = errs.front().get();
417
39.9k
        errs.pop_front();
418
39.9k
        if (e) {
419
62
          wait_for_jobs(&errs);
420
62
          return e;
421
62
        }
422
39.9k
      }
423
424
425
43.7k
      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
43.7k
      tile_data data = tiles.front();
435
43.7k
      tiles.pop_front();
436
437
43.7k
      errs.push_back(std::async(std::launch::async,
438
43.7k
                                &ImageItem_Grid::decode_and_paste_tile_image, this,
439
43.7k
                                data.tileID, data.x_origin, data.y_origin, std::ref(img), options,
440
43.7k
                                std::ref(progress_counter), warnings, decode_state));
441
43.7k
    }
442
443
    // check for decoding errors in remaining tiles
444
445
4.51k
    while (!errs.empty()) {
446
3.60k
      Error e = errs.front().get();
447
3.60k
      errs.pop_front();
448
3.60k
      if (e) {
449
0
        wait_for_jobs(&errs);
450
0
        return e;
451
0
      }
452
3.60k
    }
453
907
  }
454
907
#endif
455
456
907
  if (options.end_progress) {
457
0
    options.end_progress(heif_progress_step_total, options.progress_user_data);
458
0
  }
459
460
907
  if (cancelled) {
461
0
    return Error{heif_error_Canceled, heif_suberror_Unspecified, "Decoding the image was canceled"};
462
0
  }
463
464
907
  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
436
    if (!warnings->empty()) {
470
436
      return warnings->front();
471
436
    }
472
473
0
    return Error{heif_error_Invalid_input,
474
0
                 heif_suberror_Invalid_grid_data,
475
0
                 "Grid image without tiles"};
476
436
  }
477
478
471
  img->add_warnings(*warnings.get());
479
480
471
  return img;
481
907
}
482
483
43.6k
static Error progress_and_return_ok(const heif_decoding_options& options, int& progress_counter) {
484
43.6k
  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
43.6k
  return Error::Ok;
493
43.6k
}
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
43.7k
{
502
43.7k
  std::shared_ptr<HeifPixelImage> tile_img;
503
43.7k
#if ENABLE_PARALLEL_TILE_DECODING
504
43.7k
  static std::mutex warningsMutex;
505
43.7k
#endif
506
507
43.7k
  auto tileItem = get_context()->get_image(tileID, true);
508
43.7k
  if (!tileItem && !options.strict_decoding) {
509
    // We ignore missing images. The un-pasted canvas region stays zero from calloc().
510
17.2k
#if ENABLE_PARALLEL_TILE_DECODING
511
17.2k
    std::lock_guard<std::mutex> lock(warningsMutex);
512
17.2k
#endif
513
17.2k
    warnings->emplace_back(
514
17.2k
      heif_error_Invalid_input,
515
17.2k
      heif_suberror_Missing_grid_images,
516
17.2k
      "Missing grid image"
517
17.2k
    );
518
17.2k
    return progress_and_return_ok(options, progress_counter);
519
17.2k
  }
520
521
43.7k
  assert(tileItem);
522
26.5k
  if (auto error = tileItem->get_item_error()) {
523
118
    return error;
524
118
  }
525
526
26.4k
  auto decodeResult = tileItem->decode_image(options, false, 0, 0, std::move(decode_state));
527
26.4k
  if (!decodeResult) {
528
24.8k
    if (!options.strict_decoding) {
529
      // We ignore broken tiles. The un-pasted canvas region stays zero from calloc().
530
24.8k
#if ENABLE_PARALLEL_TILE_DECODING
531
24.8k
      std::lock_guard<std::mutex> lock(warningsMutex);
532
24.8k
#endif
533
24.8k
      warnings->push_back(decodeResult.error());
534
24.8k
      return progress_and_return_ok(options, progress_counter);
535
24.8k
    }
536
537
18.4E
    return decodeResult.error();
538
24.8k
  }
539
540
1.54k
  tile_img = *decodeResult;
541
542
1.54k
  uint32_t w = get_grid_spec().get_width();
543
1.54k
  uint32_t h = get_grid_spec().get_height();
544
545
  // --- generate the image canvas for combining all the tiles
546
547
1.54k
  {
548
1.54k
#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.54k
    static std::mutex createImageMutex;
553
1.54k
    std::lock_guard<std::mutex> lock(createImageMutex);
554
1.54k
#endif
555
556
1.54k
    if (!inout_image) {
557
493
      auto grid_image = std::make_shared<HeifPixelImage>();
558
493
      auto err = grid_image->create_clone_image_at_new_size(tile_img, w, h, get_context()->get_security_limits());
559
493
      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
493
      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
493
      grid_image->copy_metadata_from(*tile_img);
574
575
493
      inout_image = grid_image;
576
493
    }
577
1.54k
  }
578
579
  // --- copy tile into output image
580
581
1.54k
  heif_chroma chroma = inout_image->get_chroma_format();
582
583
1.54k
  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.54k
  inout_image->copy_image_to(tile_img, x0, y0);
591
592
1.54k
  return progress_and_return_ok(options, progress_counter);
593
1.54k
}
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
36.1k
{
599
36.1k
  uint32_t idx = ty * m_grid_spec.get_columns() + tx;
600
601
36.1k
  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
36.1k
  heif_item_id tile_id = m_grid_tile_ids[idx];
608
36.1k
  std::shared_ptr<const ImageItem> tile_item = get_context()->get_image(tile_id, true);
609
36.1k
  if (!tile_item) {
610
11.7k
    return Error{heif_error_Invalid_input,
611
11.7k
                 heif_suberror_Missing_grid_images,
612
11.7k
                 "Grid tile references a non-existent item"};
613
11.7k
  }
614
24.4k
  if (auto error = tile_item->get_item_error()) {
615
3.23k
    return error;
616
3.23k
  }
617
618
21.1k
  return tile_item->decode_compressed_image(options, false, 0, 0, std::move(decode_state));
619
24.4k
}
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
62.2k
{
631
62.2k
  heif_image_tiling tiling{};
632
633
62.2k
  const ImageGrid& gridspec = get_grid_spec();
634
62.2k
  tiling.num_columns = gridspec.get_columns();
635
62.2k
  tiling.num_rows = gridspec.get_rows();
636
637
62.2k
  tiling.image_width = gridspec.get_width();
638
62.2k
  tiling.image_height = gridspec.get_height();
639
62.2k
  tiling.number_of_extra_dimensions = 0;
640
641
62.2k
  auto tile_ids = get_grid_tiles();
642
62.2k
  if (!tile_ids.empty() && tile_ids[0] != 0) {
643
61.7k
    heif_item_id tile0_id = tile_ids[0];
644
61.7k
    auto tile0 = get_context()->get_image(tile0_id, true);
645
61.7k
    if (tile0 == nullptr || tile0->get_item_error()) {
646
46.5k
      return tiling;
647
46.5k
    }
648
649
15.1k
    tiling.tile_width = tile0->get_width();
650
15.1k
    tiling.tile_height = tile0->get_height();
651
15.1k
  }
652
497
  else {
653
497
    tiling.tile_width = 0;
654
497
    tiling.tile_height = 0;
655
497
  }
656
657
15.6k
  return tiling;
658
62.2k
}
659
660
661
void ImageItem_Grid::get_tile_size(uint32_t& w, uint32_t& h) const
662
70
{
663
70
  const auto& tile_ids = get_grid_tiles();
664
70
  if (tile_ids.empty() || tile_ids[0] == 0) {
665
13
    w = h = 0;
666
13
    return;
667
13
  }
668
669
57
  auto tile = get_context()->get_image(tile_ids[0], true);
670
57
  if (tile == nullptr || tile->get_item_error()) {
671
14
    w = h = 0;
672
14
    return;
673
14
  }
674
675
43
  w = tile->get_width();
676
43
  h = tile->get_height();
677
43
}
678
679
680
681
int ImageItem_Grid::get_luma_bits_per_pixel() const
682
786
{
683
786
  auto child_result = get_context()->find_first_coded_image_id(get_id());
684
786
  if (child_result.is_error()) {
685
9
    return -1;
686
9
  }
687
688
777
  auto image = get_context()->get_image(*child_result, true);
689
777
  if (!image) {
690
0
    return -1;
691
0
  }
692
693
777
  return image->get_luma_bits_per_pixel();
694
777
}
695
696
697
int ImageItem_Grid::get_chroma_bits_per_pixel() const
698
754
{
699
754
  auto child_result = get_context()->find_first_coded_image_id(get_id());
700
754
  if (child_result.is_error()) {
701
0
    return -1;
702
0
  }
703
704
754
  auto image = get_context()->get_image(*child_result, true);
705
754
  return image->get_chroma_bits_per_pixel();
706
754
}
707
708
Result<std::shared_ptr<Decoder>> ImageItem_Grid::get_decoder() const
709
3.71k
{
710
3.71k
  auto child_result = get_context()->find_first_coded_image_id(get_id());
711
3.71k
  if (child_result.is_error()) {
712
1.77k
    return child_result.error();
713
1.77k
  }
714
715
1.93k
  auto image = get_context()->get_image(*child_result, true);
716
1.93k
  if (!image) {
717
0
    return Error{heif_error_Invalid_input,
718
0
      heif_suberror_Nonexisting_item_referenced};
719
0
  }
720
1.93k
  else if (auto err = image->get_item_error()) {
721
98
    return err;
722
98
  }
723
724
1.84k
  return image->get_decoder();
725
1.93k
}
726
727
728
void ImageItem_Grid::populate_component_descriptions()
729
3.51k
{
730
3.51k
  if (!get_component_descriptions().empty()) {
731
711
    return;
732
711
  }
733
734
2.80k
  if (m_grid_tile_ids.empty()) {
735
2.19k
    ImageItem::populate_component_descriptions();
736
2.19k
    return;
737
2.19k
  }
738
739
613
  auto child = get_context()->get_image(m_grid_tile_ids[0], true);
740
613
  if (!child) {
741
496
    ImageItem::populate_component_descriptions();
742
496
    return;
743
496
  }
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
117
  if (!populate_descriptions_from_child(*child, child->get_width(), child->get_height())) {
751
117
    ImageItem::populate_component_descriptions();
752
117
  }
753
117
}
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
  file->add_ispe_property(grid_id, output_width, output_height, false);
804
805
  // PIXI property will be added when the first tile is set
806
807
  // Set Brands
808
  //m_heif_file->set_brand(encoder->plugin->compression_format,
809
  //                       grid_image->is_miaf_compatible());
810
811
0
  return grid_image;
812
0
}
813
814
void ImageItem_Grid::set_tile_encoding_options(const heif_encoding_options* options)
815
0
{
816
0
  heif_encoding_options_copy(m_tile_encoding_options, options);
817
818
  // do not propagate image transformation to tiles
819
0
  m_tile_encoding_options->image_orientation = heif_orientation_normal;
820
0
}
821
822
823
Error ImageItem_Grid::add_image_tile(uint32_t tile_x, uint32_t tile_y,
824
                                     const std::shared_ptr<HeifPixelImage>& image,
825
                                     heif_encoder* encoder)
826
0
{
827
0
  auto encodingResult = get_context()->encode_image(image,
828
0
                                            encoder,
829
0
                                            *m_tile_encoding_options,
830
0
                                            heif_image_input_class_normal);
831
0
  if (!encodingResult) {
832
0
    return encodingResult.error();
833
0
  }
834
835
0
  std::shared_ptr<ImageItem> encoded_image = *encodingResult;
836
837
0
  auto file = get_file();
838
0
  file->get_infe_box(encoded_image->get_id())->set_hidden_item(true); // grid tiles are hidden items
839
840
  // Assign tile to grid
841
0
  heif_image_tiling tiling = get_heif_image_tiling();
842
0
  file->set_iref_reference(get_id(), fourcc("dimg"), tile_y * tiling.num_columns + tile_x, encoded_image->get_id());
843
844
0
  set_grid_tile_id(tile_x, tile_y, encoded_image->get_id());
845
846
  // Add PIXI property (copy from first tile)
847
0
  auto pixi = encoded_image->get_property<Box_pixi>();
848
0
  add_property(pixi, true);
849
850
  // copy over extra properties to grid item
851
852
0
  if (tile_x == 0 && tile_y == 0) {
853
0
    auto property_boxes = encoded_image->generate_property_boxes(false);
854
0
    for (auto& property : property_boxes) {
855
0
      add_property(property, is_property_essential(property));
856
0
    }
857
858
    // add color profile similar to first tile image
859
    // TODO: this shouldn't be necessary. The colr profiles should be in the ImageDescription above.
860
0
    auto colr_boxes = add_color_profile(image, *m_tile_encoding_options,
861
0
                                        heif_image_input_class_normal,
862
0
                                        m_tile_encoding_options->output_nclx_profile);
863
0
    for (auto& property : colr_boxes) {
864
0
      add_property(property, is_property_essential(property));
865
0
    }
866
867
    // Add transformative properties
868
869
0
    get_context()->get_heif_file()->add_orientation_properties(get_id(), m_grid_orientation);
870
0
  }
871
872
0
  return Error::Ok;
873
0
}
874
875
876
Result<std::shared_ptr<ImageItem_Grid>> ImageItem_Grid::add_and_encode_full_grid(HeifContext* ctx,
877
                                                                                 const std::vector<std::shared_ptr<HeifPixelImage>>& tiles,
878
                                                                                 uint16_t rows,
879
                                                                                 uint16_t columns,
880
                                                                                 heif_encoder* encoder,
881
                                                                                 const heif_encoding_options& options)
882
0
{
883
0
  std::shared_ptr<ImageItem_Grid> griditem;
884
885
  // Create ImageGrid
886
887
0
  ImageGrid grid;
888
0
  grid.set_num_tiles(columns, rows);
889
0
  uint32_t tile_width = tiles[0]->get_width();
890
0
  uint32_t tile_height = tiles[0]->get_height();
891
0
  grid.set_output_size(tile_width * columns, tile_height * rows);
892
0
  std::vector<uint8_t> grid_data = grid.write();
893
894
0
  auto file = ctx->get_heif_file();
895
896
  // Encode Tiles
897
898
0
  std::vector<heif_item_id> tile_ids;
899
900
0
  std::shared_ptr<Box_pixi> pixi_property;
901
902
0
  for (int i=0; i<rows*columns; i++) {
903
0
    std::shared_ptr<ImageItem> out_tile;
904
0
    auto encodingResult = ctx->encode_image(tiles[i],
905
0
                                            encoder,
906
0
                                            options,
907
0
                                            heif_image_input_class_normal);
908
0
    if (!encodingResult) {
909
0
      return encodingResult.error();
910
0
    }
911
0
    else {
912
0
      out_tile = *encodingResult;
913
0
    }
914
915
0
    heif_item_id tile_id = out_tile->get_id();
916
0
    file->get_infe_box(tile_id)->set_hidden_item(true); // only show the full grid
917
0
    tile_ids.push_back(out_tile->get_id());
918
919
0
    if (!pixi_property) {
920
0
      pixi_property = out_tile->get_property<Box_pixi>();
921
0
    }
922
0
  }
923
924
  // Create Grid Item
925
926
0
  auto grid_id_result = file->add_new_image(fourcc("grid"));
927
0
  if (!grid_id_result) {
928
0
    return grid_id_result.error();
929
0
  }
930
0
  heif_item_id grid_id = *grid_id_result;
931
0
  griditem = std::make_shared<ImageItem_Grid>(ctx, grid_id);
932
0
  ctx->insert_image_item(grid_id, griditem);
933
0
  const int construction_method = 1; // 0=mdat 1=idat
934
0
  file->append_iloc_data(grid_id, grid_data, construction_method);
935
936
  // Connect tiles to grid
937
938
0
  file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids);
939
940
  // Add ISPE property
941
942
0
  uint32_t image_width = tile_width * columns;
943
0
  uint32_t image_height = tile_height * rows;
944
945
0
  auto ispe = std::make_shared<Box_ispe>();
946
0
  ispe->set_size(image_width, image_height);
947
0
  griditem->add_property(ispe, false);
948
949
  // Add PIXI property (copy from first tile)
950
951
0
  griditem->add_property(pixi_property, true);
952
953
  // copy over extra properties to grid item
954
955
0
  auto property_boxes = tiles[0]->generate_property_boxes(true);
956
0
  for (auto& property : property_boxes) {
957
0
    griditem->add_property(property, griditem->is_property_essential(property));
958
0
  }
959
960
  // Set Brands
961
962
  //file->set_brand(encoder->plugin->compression_format,
963
  //                griditem->is_miaf_compatible());
964
965
0
  return griditem;
966
0
}
967
968
heif_brand2 ImageItem_Grid::get_compatible_brand() const
969
0
{
970
0
  if (m_grid_tile_ids.empty()) { return 0; }
971
972
0
  heif_item_id child_id = m_grid_tile_ids[0];
973
0
  auto child = get_context()->get_image(child_id, false);
974
0
  if (!child) { return 0; }
975
976
0
  return child->get_compatible_brand();
977
0
}