Coverage Report

Created: 2026-03-08 06:41

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