Coverage Report

Created: 2026-05-24 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image-items/tiled.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 "tiled.h"
22
#include "context.h"
23
#include "file.h"
24
#include <algorithm>
25
#include <limits>
26
#include "security_limits.h"
27
#include "codecs/hevc_dec.h"
28
#if WITH_UNCOMPRESSED_CODEC
29
#include "codecs/uncompressed/unc_boxes.h"
30
#endif
31
#include "api_structs.h"
32
33
34
static uint64_t readvec(const std::vector<uint8_t>& data, size_t& ptr, int len)
35
0
{
36
0
  uint64_t val = 0;
37
0
  while (len--) {
38
0
    val <<= 8;
39
0
    val |= data[ptr++];
40
0
  }
41
42
0
  return val;
43
0
}
44
45
46
Result<uint64_t> number_of_tiles(const heif_tiled_image_parameters& params, const heif_security_limits* limits)
47
0
{
48
0
  uint64_t nTiles = nTiles_h(params) * static_cast<uint64_t>(nTiles_v(params));
49
50
  // Enforce the limit before the extra-dimensions loop so it is checked
51
  // even when number_of_extra_dimensions == 0.
52
0
  if (limits && limits->max_number_of_tiles && nTiles > limits->max_number_of_tiles) {
53
0
    return Error{
54
0
      heif_error_Unsupported_filetype,
55
0
      heif_suberror_Security_limit_exceeded,
56
0
      "Number of tiles exceeds security limit"
57
0
    };
58
0
  }
59
60
0
  for (int i = 0; i < params.number_of_extra_dimensions; i++) {
61
    // We only support up to 8 extra dimensions
62
0
    if (i == 8) {
63
0
      break;
64
0
    }
65
66
0
    if (params.extra_dimensions[i] == 0) {
67
0
      return Error{
68
0
        heif_error_Unsupported_filetype,
69
0
        heif_suberror_Unspecified,
70
0
        "Zero extra dimension size."
71
0
      };
72
0
    }
73
74
0
    if (nTiles > UINT64_MAX / params.extra_dimensions[i]) {
75
0
      return Error{
76
0
        heif_error_Unsupported_filetype,
77
0
        heif_suberror_Unspecified,
78
0
        "Number of tiles exceeds uint64 maximum."
79
0
      };
80
0
    }
81
82
0
    nTiles *= params.extra_dimensions[i];
83
84
0
    if (limits && limits->max_number_of_tiles && nTiles > limits->max_number_of_tiles) {
85
0
      return Error{
86
0
        heif_error_Unsupported_filetype,
87
0
        heif_suberror_Security_limit_exceeded,
88
0
        "Number of tiles exceeds security limit"
89
0
      };
90
0
    }
91
0
  }
92
93
0
  return nTiles;
94
0
}
95
96
97
uint32_t nTiles_h(const heif_tiled_image_parameters& params)
98
0
{
99
  // 64-bit arithmetic prevents wrap-around when image_width + tile_width - 1
100
  // exceeds UINT32_MAX. The quotient is bounded by image_width, so the
101
  // narrowing cast is safe. Callers are responsible for ensuring tile_width > 0.
102
0
  return static_cast<uint32_t>(
103
0
      (static_cast<uint64_t>(params.image_width) + params.tile_width - 1) / params.tile_width);
104
0
}
105
106
107
uint32_t nTiles_v(const heif_tiled_image_parameters& params)
108
0
{
109
0
  return static_cast<uint32_t>(
110
0
      (static_cast<uint64_t>(params.image_height) + params.tile_height - 1) / params.tile_height);
111
0
}
112
113
114
void Box_tilC::init_heif_tiled_image_parameters(heif_tiled_image_parameters& params)
115
0
{
116
0
  params.version = 1;
117
118
0
  params.image_width = 0;
119
0
  params.image_height = 0;
120
0
  params.tile_width = 0;
121
0
  params.tile_height = 0;
122
0
  params.compression_format_fourcc = 0;
123
0
  params.offset_field_length = 40;
124
0
  params.size_field_length = 24;
125
0
  params.number_of_extra_dimensions = 0;
126
127
0
  for (uint32_t& dim : params.extra_dimensions) {
128
0
    dim = 0;
129
0
  }
130
131
0
  params.tiles_are_sequential = false;
132
0
}
133
134
135
void Box_tilC::derive_box_version()
136
0
{
137
0
  set_version(0);
138
139
0
  uint8_t flags = 0;
140
141
0
  switch (m_parameters.offset_field_length) {
142
0
    case 32:
143
0
      flags |= 0;
144
0
      break;
145
0
    case 40:
146
0
      flags |= 0x01;
147
0
      break;
148
0
    case 48:
149
0
      flags |= 0x02;
150
0
      break;
151
0
    case 64:
152
0
      flags |= 0x03;
153
0
      break;
154
0
    default:
155
0
      assert(false); // TODO: return error
156
0
  }
157
158
0
  switch (m_parameters.size_field_length) {
159
0
    case 0:
160
0
      flags |= 0;
161
0
      break;
162
0
    case 24:
163
0
      flags |= 0x04;
164
0
      break;
165
0
    case 32:
166
0
      flags |= 0x08;
167
0
      break;
168
0
    case 64:
169
0
      flags |= 0x0c;
170
0
      break;
171
0
    default:
172
0
      assert(false); // TODO: return error
173
0
  }
174
175
0
  if (m_parameters.tiles_are_sequential) {
176
0
    flags |= 0x10;
177
0
  }
178
179
0
  set_flags(flags);
180
0
}
181
182
183
Error Box_tilC::write(StreamWriter& writer) const
184
0
{
185
0
  assert(m_parameters.version == 1);
186
187
0
  size_t box_start = reserve_box_header_space(writer);
188
189
0
  if (m_parameters.number_of_extra_dimensions > 8) {
190
0
    assert(false); // currently not supported
191
0
  }
192
193
0
  writer.write32(m_parameters.tile_width);
194
0
  writer.write32(m_parameters.tile_height);
195
0
  writer.write32(m_parameters.compression_format_fourcc);
196
197
0
  writer.write8(m_parameters.number_of_extra_dimensions);
198
199
0
  for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) {
200
0
    writer.write32(m_parameters.extra_dimensions[i]);
201
0
  }
202
203
0
  auto& tile_properties = m_children;
204
0
  if (tile_properties.size() > 255) {
205
0
    return {heif_error_Encoding_error,
206
0
            heif_suberror_Unspecified,
207
0
            "Cannot write more than 255 tile properties in tilC header"};
208
0
  }
209
210
0
  writer.write8(static_cast<uint8_t>(tile_properties.size()));
211
0
  for (const auto& property : tile_properties) {
212
0
    property->write(writer);
213
0
  }
214
215
0
  prepend_header(writer, box_start);
216
217
0
  return Error::Ok;
218
0
}
219
220
221
std::string Box_tilC::dump(Indent& indent) const
222
0
{
223
0
  std::ostringstream sstr;
224
225
0
  sstr << BoxHeader::dump(indent);
226
227
0
  sstr << indent << "version: " << ((int) get_version()) << "\n"
228
       //<< indent << "image size: " << m_parameters.image_width << "x" << m_parameters.image_height << "\n"
229
0
       << indent << "tile size: " << m_parameters.tile_width << "x" << m_parameters.tile_height << "\n"
230
0
       << indent << "compression: " << fourcc_to_string(m_parameters.compression_format_fourcc) << "\n"
231
0
       << indent << "tiles are sequential: " << (m_parameters.tiles_are_sequential ? "yes" : "no") << "\n"
232
0
       << indent << "offset field length: " << ((int) m_parameters.offset_field_length) << " bits\n"
233
0
       << indent << "size field length: " << ((int) m_parameters.size_field_length) << " bits\n"
234
0
       << indent << "number of extra dimensions: " << ((int) m_parameters.number_of_extra_dimensions) << "\n";
235
236
0
  sstr << indent << "tile properties:\n"
237
0
       << dump_children(indent, true);
238
239
0
  return sstr.str();
240
241
0
}
242
243
244
Error Box_tilC::parse(BitstreamRange& range, const heif_security_limits* limits)
245
0
{
246
0
  parse_full_box_header(range);
247
248
  // Note: actually, we should allow 0 only, but there are a few images around that use version 1.
249
0
  if (get_version() > 1) {
250
0
    std::stringstream sstr;
251
0
    sstr << "'tili' image version " << ((int) get_version()) << " is not implemented yet";
252
253
0
    return {heif_error_Unsupported_feature,
254
0
            heif_suberror_Unsupported_data_version,
255
0
            sstr.str()};
256
0
  }
257
258
0
  m_parameters.version = get_version();
259
260
0
  uint32_t flags = get_flags();
261
262
0
  switch (flags & 0x03) {
263
0
    case 0:
264
0
      m_parameters.offset_field_length = 32;
265
0
      break;
266
0
    case 1:
267
0
      m_parameters.offset_field_length = 40;
268
0
      break;
269
0
    case 2:
270
0
      m_parameters.offset_field_length = 48;
271
0
      break;
272
0
    case 3:
273
0
      m_parameters.offset_field_length = 64;
274
0
      break;
275
0
  }
276
277
0
  switch (flags & 0x0c) {
278
0
    case 0x00:
279
0
      m_parameters.size_field_length = 0;
280
0
      break;
281
0
    case 0x04:
282
0
      m_parameters.size_field_length = 24;
283
0
      break;
284
0
    case 0x08:
285
0
      m_parameters.size_field_length = 32;
286
0
      break;
287
0
    case 0x0c:
288
0
      m_parameters.size_field_length = 64;
289
0
      break;
290
0
  }
291
292
0
  m_parameters.tiles_are_sequential = !!(flags & 0x10);
293
294
295
0
  m_parameters.tile_width = range.read32();
296
0
  m_parameters.tile_height = range.read32();
297
0
  m_parameters.compression_format_fourcc = range.read32();
298
299
0
  if (m_parameters.tile_width == 0 || m_parameters.tile_height == 0) {
300
0
    return {heif_error_Invalid_input,
301
0
            heif_suberror_Unspecified,
302
0
            "Tile with zero width or height."};
303
0
  }
304
305
306
  // --- extra dimensions
307
308
0
  m_parameters.number_of_extra_dimensions = range.read8();
309
310
0
  for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) {
311
0
    uint32_t size = range.read32();
312
313
0
    if (size == 0) {
314
0
      return {heif_error_Invalid_input,
315
0
              heif_suberror_Unspecified,
316
0
              "'tili' extra dimension may not be zero."};
317
0
    }
318
319
0
    if (i < 8) {
320
0
      m_parameters.extra_dimensions[i] = size;
321
0
    }
322
0
    else {
323
      // TODO: error: too many dimensions (not supported)
324
0
    }
325
0
  }
326
327
  // --- read tile properties
328
329
  // Check version for backwards compatibility with old format.
330
  // TODO: remove when spec is final and old test images have been converted
331
0
  if (get_version() == 0) {
332
0
    uint8_t num_properties = range.read8();
333
334
0
    Error error = read_children(range, num_properties, limits);
335
0
    if (error) {
336
0
      return error;
337
0
    }
338
0
  }
339
340
0
  return range.get_error();
341
0
}
342
343
344
Error TiledHeader::set_parameters(const heif_tiled_image_parameters& params)
345
0
{
346
0
  m_parameters = params;
347
348
0
  Result<uint64_t> num_tiles_result = number_of_tiles(params, heif_get_global_security_limits());
349
0
  if (auto err = num_tiles_result.error()) {
350
0
    return err;
351
0
  }
352
353
0
  m_offsets.resize(*num_tiles_result);
354
355
0
  for (auto& tile: m_offsets) {
356
0
    tile.offset = TILD_OFFSET_NOT_LOADED;
357
0
  }
358
359
0
  return Error::Ok;
360
0
}
361
362
363
Error TiledHeader::read_full_offset_table(const std::shared_ptr<HeifFile>& file, heif_item_id tild_id, const heif_security_limits* limits)
364
0
{
365
0
  Result<uint64_t> nTiles_result = number_of_tiles(m_parameters, limits);
366
0
  if (auto err = nTiles_result.error()) {
367
0
    return err;
368
0
  }
369
370
0
  return read_offset_table_range(file, tild_id, 0, *nTiles_result);
371
0
}
372
373
374
Error TiledHeader::read_offset_table_range(const std::shared_ptr<HeifFile>& file, heif_item_id tild_id,
375
                                           uint64_t start, uint64_t end)
376
0
{
377
0
  const Error eofError(heif_error_Invalid_input,
378
0
                       heif_suberror_Unspecified,
379
0
                       "Tili header data incomplete");
380
381
0
  std::vector<uint8_t> data;
382
383
384
385
  // --- load offsets
386
387
0
  size_t size_to_read = (end - start) * (m_parameters.offset_field_length + m_parameters.size_field_length) / 8;
388
0
  size_t start_offset = start * (m_parameters.offset_field_length + m_parameters.size_field_length) / 8;
389
390
  // TODO: when we request a file range from the stream reader, it may return a larger range.
391
  //       We should then also use this larger range to read more table entries.
392
  //       But this is not easy since our data may span several iloc extents and we have to map this back to item addresses.
393
  //       Maybe it is easier to just ignore the extra data and rely on the stream read to cache this data.
394
395
0
  Error err = file->append_data_from_iloc(tild_id, data, start_offset, size_to_read);
396
0
  if (err) {
397
0
    return err;
398
0
  }
399
400
0
  size_t idx = 0;
401
0
  for (uint64_t i = start; i < end; i++) {
402
0
    m_offsets[i].offset = readvec(data, idx, m_parameters.offset_field_length / 8);
403
404
0
    if (m_parameters.size_field_length) {
405
0
      assert(m_parameters.size_field_length <= 32);
406
0
      m_offsets[i].size = static_cast<uint32_t>(readvec(data, idx, m_parameters.size_field_length / 8));
407
0
    }
408
409
    // printf("[%zu] : offset/size: %zu %d\n", i, m_offsets[i].offset, m_offsets[i].size);
410
0
  }
411
412
0
  return Error::Ok;
413
0
}
414
415
416
size_t TiledHeader::get_header_size() const
417
0
{
418
0
  assert(m_header_size);
419
0
  return m_header_size;
420
0
}
421
422
423
uint32_t TiledHeader::get_offset_table_entry_size() const
424
0
{
425
0
  return (m_parameters.offset_field_length + m_parameters.size_field_length) / 8;
426
0
}
427
428
429
std::pair<uint32_t, uint32_t> TiledHeader::get_tile_offset_table_range_to_read(uint32_t idx, uint32_t nEntries) const
430
0
{
431
0
  uint32_t start = idx;
432
0
  uint32_t end = idx+1;
433
434
0
  while (end < m_offsets.size() && end - idx < nEntries && m_offsets[end].offset == TILD_OFFSET_NOT_LOADED) {
435
0
    end++;
436
0
  }
437
438
0
  while (start > 0 && idx - start < nEntries && m_offsets[start-1].offset == TILD_OFFSET_NOT_LOADED) {
439
0
    start--;
440
0
  }
441
442
  // try to fill the smaller hole
443
444
0
  if (end - start > nEntries) {
445
0
    if (idx - start < end - idx) {
446
0
      end = start + nEntries;
447
0
    }
448
0
    else {
449
0
      start = end - nEntries;
450
0
    }
451
0
  }
452
453
0
  return {start, end};
454
0
}
455
456
457
Error TiledHeader::set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size)
458
0
{
459
  // Offset and size are written into bit-fields of the configured widths;
460
  // silently truncating here produces files where the offset table points to
461
  // garbage. Reject the value so the caller knows to widen the fields.
462
0
  uint8_t off_bits = m_parameters.offset_field_length;
463
0
  uint8_t sz_bits  = m_parameters.size_field_length;
464
465
0
  if (off_bits < 64 && offset >> off_bits) {
466
0
    std::stringstream sstr;
467
0
    sstr << "Tile offset " << offset << " does not fit in the configured "
468
0
         << static_cast<int>(off_bits) << "-bit offset field. Use a wider "
469
0
            "offset_field_length (40/48/64) when encoding the tili image.";
470
0
    return {heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
471
0
  }
472
473
0
  if (sz_bits != 0 && sz_bits < 32 && size >> sz_bits) {
474
0
    std::stringstream sstr;
475
0
    sstr << "Tile size " << size << " does not fit in the configured "
476
0
         << static_cast<int>(sz_bits) << "-bit size field.";
477
0
    return {heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
478
0
  }
479
480
0
  uint64_t idx = uint64_t{tile_y} * nTiles_h(m_parameters) + tile_x;
481
0
  m_offsets[idx].offset = offset;
482
0
  m_offsets[idx].size = size;
483
0
  return Error::Ok;
484
0
}
485
486
487
template<typename I>
488
void writevec(uint8_t* data, size_t& idx, I value, int len)
489
0
{
490
0
  for (int i = 0; i < len; i++) {
491
0
    data[idx + i] = static_cast<uint8_t>((value >> (len - 1 - i) * 8) & 0xFF);
492
0
  }
493
494
0
  idx += len;
495
0
}
496
497
498
Result<std::vector<uint8_t>> TiledHeader::write_offset_table()
499
0
{
500
0
  Result<uint64_t> nTiles_result = number_of_tiles(m_parameters, nullptr);
501
0
  if (auto err = nTiles_result.error()) {
502
0
    return err;
503
0
  }
504
505
506
0
  int offset_entry_size = (m_parameters.offset_field_length + m_parameters.size_field_length) / 8;
507
0
  uint64_t size = *nTiles_result * offset_entry_size;
508
509
0
  std::vector<uint8_t> data;
510
0
  data.resize(size);
511
512
0
  size_t idx = 0;
513
514
0
  uint8_t off_bits = m_parameters.offset_field_length;
515
0
  uint8_t sz_bits  = m_parameters.size_field_length;
516
517
0
  for (const auto& offset: m_offsets) {
518
0
    if (off_bits < 64 && offset.offset >> off_bits) {
519
0
      std::stringstream sstr;
520
0
      sstr << "Tile offset " << offset.offset << " does not fit in the "
521
0
              "configured " << static_cast<int>(off_bits) << "-bit offset field.";
522
0
      return Error{heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
523
0
    }
524
0
    if (sz_bits != 0 && sz_bits < 32 && offset.size >> sz_bits) {
525
0
      std::stringstream sstr;
526
0
      sstr << "Tile size " << offset.size << " does not fit in the "
527
0
              "configured " << static_cast<int>(sz_bits) << "-bit size field.";
528
0
      return Error{heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
529
0
    }
530
531
0
    writevec(data.data(), idx, offset.offset, m_parameters.offset_field_length / 8);
532
533
0
    if (m_parameters.size_field_length != 0) {
534
0
      writevec(data.data(), idx, offset.size, m_parameters.size_field_length / 8);
535
0
    }
536
0
  }
537
538
0
  assert(idx == data.size());
539
540
0
  m_header_size = data.size();
541
542
0
  return data;
543
0
}
544
545
546
std::string TiledHeader::dump() const
547
0
{
548
0
  std::stringstream sstr;
549
550
0
  sstr << "offsets: ";
551
552
  // TODO
553
554
0
  for (const auto& offset: m_offsets) {
555
0
    sstr << offset.offset << ", size: " << offset.size << "\n";
556
0
  }
557
558
0
  return sstr.str();
559
0
}
560
561
562
ImageItem_Tiled::ImageItem_Tiled(HeifContext* ctx)
563
0
        : ImageItem(ctx)
564
0
{
565
0
  m_tile_encoding_options = heif_encoding_options_alloc();
566
0
}
567
568
569
ImageItem_Tiled::ImageItem_Tiled(HeifContext* ctx, heif_item_id id)
570
0
        : ImageItem(ctx, id)
571
0
{
572
0
  m_tile_encoding_options = heif_encoding_options_alloc();
573
0
}
574
575
576
ImageItem_Tiled::~ImageItem_Tiled()
577
0
{
578
0
  heif_encoding_options_free(m_tile_encoding_options);
579
0
}
580
581
582
heif_compression_format ImageItem_Tiled::get_compression_format() const
583
0
{
584
0
  return compression_format_from_fourcc_infe_type(m_tild_header.get_parameters().compression_format_fourcc);
585
0
}
586
587
588
Error ImageItem_Tiled::initialize_decoder()
589
0
{
590
0
  auto heif_file = get_context()->get_heif_file();
591
592
0
  auto tilC_box = get_property<Box_tilC>();
593
0
  if (!tilC_box) {
594
0
    return {heif_error_Invalid_input,
595
0
            heif_suberror_Unspecified,
596
0
            "Tiled image without 'tilC' property box."};
597
0
  }
598
599
0
  auto ispe_box = get_property<Box_ispe>();
600
0
  if (!ispe_box) {
601
0
    return {heif_error_Invalid_input,
602
0
            heif_suberror_Unspecified,
603
0
            "Tiled image without 'ispe' property box."};
604
0
  }
605
606
0
  heif_tiled_image_parameters parameters = tilC_box->get_parameters();
607
0
  parameters.image_width = ispe_box->get_width();
608
0
  parameters.image_height = ispe_box->get_height();
609
610
0
  if (parameters.image_width == 0 || parameters.image_height == 0) {
611
0
    return {heif_error_Invalid_input,
612
0
            heif_suberror_Unspecified,
613
0
            "'tili' image with zero width or height."};
614
0
  }
615
616
0
  if (Error err = m_tild_header.set_parameters(parameters)) {
617
0
    return err;
618
0
  }
619
620
621
  // --- create a dummy image item for decoding tiles
622
623
0
  heif_compression_format format = compression_format_from_fourcc_infe_type(m_tild_header.get_parameters().compression_format_fourcc);
624
0
  m_tile_item = ImageItem::alloc_for_compression_format(get_context(), format);
625
626
  // For backwards compatibility: copy over properties from `tili` item.
627
  // TODO: remove when spec is final and old test images have been converted
628
0
  if (tilC_box->get_version() == 1) {
629
0
    auto propertiesResult = get_properties();
630
0
    if (!propertiesResult) {
631
0
      return propertiesResult.error();
632
0
    }
633
634
    // Filter out per-tile boxes incompatible with tili's shared template
635
0
    auto props = *propertiesResult;
636
0
#if WITH_UNCOMPRESSED_CODEC
637
0
    for (const auto& box : props) {
638
0
      if (box->get_short_type() == fourcc("icef")) {
639
0
        auto icef = std::dynamic_pointer_cast<Box_icef>(box);
640
0
        if (icef && icef->get_units().size() > 1) {
641
0
          return {heif_error_Invalid_input,
642
0
                  heif_suberror_Unspecified,
643
0
                  "icef box with multiple units is incompatible with tili shared tile template."};
644
0
        }
645
0
      }
646
0
    }
647
0
    std::erase_if(props, [](const std::shared_ptr<Box>& box) {
648
0
      uint32_t type = box->get_short_type();
649
0
      return type == fourcc("icef") || type == fourcc("sbpm") || type == fourcc("snuc");
650
0
    });
651
0
#endif
652
653
0
    m_tile_item->set_properties(props);
654
0
  }
655
0
  else {
656
    // --- This is the new method
657
658
    // Synthesize an ispe box if there was none in the file
659
660
0
    auto tile_properties = tilC_box->get_all_child_boxes();
661
662
    // Filter out per-tile boxes incompatible with tili's shared template
663
0
#if WITH_UNCOMPRESSED_CODEC
664
0
    for (const auto& box : tile_properties) {
665
0
      if (box->get_short_type() == fourcc("icef")) {
666
0
        auto icef = std::dynamic_pointer_cast<Box_icef>(box);
667
0
        if (icef && icef->get_units().size() > 1) {
668
0
          return {heif_error_Invalid_input,
669
0
                  heif_suberror_Unspecified,
670
0
                  "icef box with multiple units is incompatible with tili shared tile template."};
671
0
        }
672
0
      }
673
0
    }
674
0
    std::erase_if(tile_properties, [](const std::shared_ptr<Box>& box) {
675
0
      uint32_t type = box->get_short_type();
676
0
      return type == fourcc("icef") || type == fourcc("sbpm") || type == fourcc("snuc");
677
0
    });
678
0
#endif
679
680
0
    bool have_ispe = false;
681
0
    for (const auto& property : tile_properties) {
682
0
      if (property->get_short_type() == fourcc("ispe")) {
683
0
        have_ispe = true;
684
0
        break;
685
0
      }
686
0
    }
687
688
0
    if (!have_ispe) {
689
0
      auto ispe = std::make_shared<Box_ispe>();
690
0
      ispe->set_size(parameters.tile_width, parameters.tile_height);
691
0
      tile_properties.emplace_back(std::move(ispe));
692
0
    }
693
694
0
    m_tile_item->set_properties(tile_properties);
695
0
  }
696
697
0
  m_tile_decoder = Decoder::alloc_for_infe_type(m_tile_item.get());
698
0
  if (!m_tile_decoder) {
699
0
    return {heif_error_Unsupported_feature,
700
0
            heif_suberror_Unsupported_codec,
701
0
            "'tili' image with unsupported compression format."};
702
0
  }
703
704
0
  if (m_preload_offset_table) {
705
0
    if (Error err = m_tild_header.read_full_offset_table(heif_file, get_id(), get_context()->get_security_limits())) {
706
0
      return err;
707
0
    }
708
0
  }
709
710
711
0
  return Error::Ok;
712
0
}
713
714
715
void ImageItem_Tiled::populate_component_descriptions()
716
0
{
717
  // Idempotent: skip if already populated.
718
0
  if (!get_component_descriptions().empty()) {
719
0
    return;
720
0
  }
721
722
  // initialize_decoder() must have run; m_tile_item carries the per-tile
723
  // properties (cmpd/uncC for unci tiles, codec config for visual tiles)
724
  // and its own populate has filled tile-sized component descriptions.
725
0
  if (!m_tile_item) {
726
0
    return;
727
0
  }
728
729
0
  uint32_t tile_w = m_tild_header.get_parameters().tile_width;
730
0
  uint32_t tile_h = m_tild_header.get_parameters().tile_height;
731
0
  populate_descriptions_from_child(*m_tile_item, tile_w, tile_h);
732
0
}
733
734
735
Result<std::shared_ptr<ImageItem_Tiled>>
736
ImageItem_Tiled::add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_parameters* parameters,
737
                                    const heif_encoder* encoder,
738
                                    const heif_encoding_options* encoding_options)
739
0
{
740
0
  Result<uint64_t> num_tiles_result = number_of_tiles(*parameters, ctx->get_security_limits());
741
0
  if (auto err = num_tiles_result.error()) {
742
0
    return err;
743
0
  }
744
745
  // Create 'tili' Item
746
747
0
  auto file = ctx->get_heif_file();
748
749
0
  auto tild_id_result = ctx->get_heif_file()->add_new_image(fourcc("tili"));
750
0
  if (!tild_id_result) {
751
0
    return tild_id_result.error();
752
0
  }
753
0
  heif_item_id tild_id = *tild_id_result;
754
0
  auto tild_image = std::make_shared<ImageItem_Tiled>(ctx, tild_id);
755
0
  tild_image->set_resolution(parameters->image_width, parameters->image_height);
756
0
  ctx->insert_image_item(tild_id, tild_image);
757
758
0
  if (encoding_options) {
759
    // encoding options for the tiles, but do not apply transformative properties
760
0
    heif_encoding_options_copy(tild_image->m_tile_encoding_options, encoding_options);
761
0
    tild_image->m_tile_encoding_options->image_orientation = heif_orientation_normal;
762
763
    // orientation of the main image
764
0
    tild_image->m_image_orientation = encoding_options->image_orientation;
765
0
  }
766
767
  // Create tilC box
768
769
0
  auto tilC_box = std::make_shared<Box_tilC>();
770
0
  tilC_box->set_parameters(*parameters);
771
0
  tilC_box->set_compression_format(encoder->plugin->compression_format);
772
0
  tild_image->add_property(tilC_box, true);
773
774
  // Create header + offset table
775
776
0
  TiledHeader tild_header;
777
0
  tild_header.set_parameters(*parameters);
778
0
  tild_header.set_compression_format(encoder->plugin->compression_format);
779
780
0
  Result<std::vector<uint8_t>> header_data_result = tild_header.write_offset_table();
781
0
  if (auto err = header_data_result.error()) {
782
0
    return err;
783
0
  }
784
785
0
  const int construction_method = 0; // 0=mdat 1=idat
786
0
  file->append_iloc_data(tild_id, *header_data_result, construction_method);
787
788
789
0
  if (parameters->image_width > 0xFFFFFFFF || parameters->image_height > 0xFFFFFFFF) {
790
0
    return {Error(heif_error_Usage_error, heif_suberror_Invalid_image_size,
791
0
                  "'ispe' only supports image sized up to 4294967295 pixels per dimension")};
792
0
  }
793
794
  // Add ISPE property
795
0
  auto ispe = std::make_shared<Box_ispe>();
796
0
  ispe->set_size(static_cast<uint32_t>(parameters->image_width),
797
0
                 static_cast<uint32_t>(parameters->image_height));
798
0
  tild_image->add_property(ispe, true);
799
800
#if 0
801
  // TODO
802
803
  // Add PIXI property (copy from first tile)
804
  auto pixi = m_heif_file->get_property<Box_pixi>(tile_ids[0]);
805
  m_heif_file->add_property(grid_id, pixi, true);
806
#endif
807
808
0
  tild_image->set_tild_header(tild_header);
809
0
  tild_image->set_next_tild_position(header_data_result->size());
810
811
  // Set Brands
812
  //m_heif_file->set_brand(encoder->plugin->compression_format,
813
  //                       out_grid_image->is_miaf_compatible());
814
815
0
  return {tild_image};
816
0
}
817
818
819
Error ImageItem_Tiled::add_image_tile(uint32_t tile_x, uint32_t tile_y,
820
                                     const std::shared_ptr<HeifPixelImage>& image,
821
                                     heif_encoder* encoder)
822
0
{
823
0
  auto item = ImageItem::alloc_for_compression_format(get_context(), encoder->plugin->compression_format);
824
825
0
  Result<std::shared_ptr<HeifPixelImage>> colorConversionResult;
826
0
  colorConversionResult = item->get_encoder()->convert_colorspace_for_encoding(image, encoder,
827
0
                                                                               m_tile_encoding_options->output_nclx_profile,
828
0
                                                                               &m_tile_encoding_options->color_conversion_options,
829
0
                                                                               get_context()->get_security_limits());
830
0
  if (!colorConversionResult) {
831
0
    return colorConversionResult.error();
832
0
  }
833
834
0
  std::shared_ptr<HeifPixelImage> colorConvertedImage = *colorConversionResult;
835
836
0
  Result<Encoder::CodedImageData> encodeResult = item->encode_to_bitstream_and_boxes(colorConvertedImage, encoder, *m_tile_encoding_options, heif_image_input_class_normal);
837
838
0
  if (!encodeResult) {
839
0
    return encodeResult.error();
840
0
  }
841
842
0
  const int construction_method = 0; // 0=mdat 1=idat
843
0
  get_file()->append_iloc_data(get_id(), encodeResult->bitstream, construction_method);
844
845
0
  auto& header = m_tild_header;
846
847
0
  if (image->get_width() != header.get_parameters().tile_width ||
848
0
      image->get_height() != header.get_parameters().tile_height) {
849
0
    return {heif_error_Usage_error,
850
0
            heif_suberror_Unspecified,
851
0
            "Tile image size does not match the specified tile size."};
852
0
  }
853
854
0
  uint64_t offset = get_next_tild_position();
855
0
  size_t dataSize = encodeResult->bitstream.size();
856
0
  if (dataSize > 0xFFFFFFFF) {
857
0
    return {heif_error_Encoding_error, heif_suberror_Unspecified, "Compressed tile size exceeds maximum tile size."};
858
0
  }
859
0
  if (Error err = header.set_tild_tile_range(tile_x, tile_y, offset, static_cast<uint32_t>(dataSize))) {
860
0
    return err;
861
0
  }
862
0
  set_next_tild_position(offset + encodeResult->bitstream.size());
863
864
0
  auto tilC = get_property<Box_tilC>();
865
0
  assert(tilC);
866
867
0
  std::vector<std::shared_ptr<Box>>& tile_properties = tilC->get_tile_properties();
868
869
0
  for (auto& propertyBox : encodeResult->properties) {
870
871
    // we do not have to save ispe boxes in the tile properties as this is automatically synthesized
872
873
0
    if (propertyBox->get_short_type() == fourcc("ispe")) {
874
0
      continue;
875
0
    }
876
877
0
#if WITH_UNCOMPRESSED_CODEC
878
    // icef/sbpm/snuc contain per-tile data incompatible with tili's shared tile template
879
0
    uint32_t ptype = propertyBox->get_short_type();
880
881
0
    if (ptype == fourcc("icef")) {
882
0
      auto icef = std::dynamic_pointer_cast<Box_icef>(propertyBox);
883
0
      if (icef && icef->get_units().size() > 1) {
884
0
        return {heif_error_Usage_error,
885
0
                heif_suberror_Unspecified,
886
0
                "icef box with multiple units is incompatible with tili shared tile template."};
887
0
      }
888
      // Single-unit icef can be safely skipped
889
0
      continue;
890
0
    }
891
892
0
    if (ptype == fourcc("sbpm") || ptype == fourcc("snuc")) {
893
0
      return {heif_error_Usage_error,
894
0
              heif_suberror_Unspecified,
895
0
              "Cannot store per-tile property (" + fourcc_to_string(ptype) + ") in tili shared tile template."};
896
0
    }
897
0
#endif
898
899
    // skip properties that exist already
900
901
0
    bool exists = std::any_of(tile_properties.begin(),
902
0
                              tile_properties.end(),
903
0
                              [&propertyBox](const std::shared_ptr<Box>& p) { return p->get_short_type() == propertyBox->get_short_type();});
904
0
    if (exists) {
905
0
      continue;
906
0
    }
907
908
0
    tile_properties.emplace_back(propertyBox);
909
910
    // some tile properties are also added to the tili image
911
912
0
    switch (propertyBox->get_short_type()) {
913
0
      case fourcc("pixi"):
914
0
        get_file()->add_property(get_id(), propertyBox, propertyBox->is_essential());
915
0
        break;
916
0
    }
917
918
0
    get_file()->add_orientation_properties(get_id(), m_image_orientation);
919
0
  }
920
921
  //get_file()->set_brand(encoder->plugin->compression_format,
922
  //                      true); // TODO: out_grid_image->is_miaf_compatible());
923
924
0
  return Error::Ok;
925
0
}
926
927
928
Error ImageItem_Tiled::process_before_write()
929
0
{
930
  // overwrite offsets
931
932
0
  const int construction_method = 0; // 0=mdat 1=idat
933
934
0
  Result<std::vector<uint8_t>> header_data_result = m_tild_header.write_offset_table();
935
0
  if (auto err = header_data_result.error()) {
936
0
    return err;
937
0
  }
938
939
0
  get_file()->replace_iloc_data(get_id(), 0, *header_data_result, construction_method);
940
0
  return {};
941
0
}
942
943
944
Result<std::shared_ptr<HeifPixelImage>>
945
ImageItem_Tiled::decode_compressed_image(const heif_decoding_options& options,
946
                                         bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
947
                                         std::set<heif_item_id> processed_ids) const
948
0
{
949
0
  if (decode_tile_only) {
950
0
    return decode_grid_tile(options, tile_x0, tile_y0);
951
0
  }
952
0
  else {
953
0
    return Error{heif_error_Unsupported_feature, heif_suberror_Unspecified,
954
0
                 "'tili' images can only be accessed per tile"};
955
0
  }
956
0
}
957
958
959
Error ImageItem_Tiled::append_compressed_tile_data(std::vector<uint8_t>& data, uint32_t tx, uint32_t ty) const
960
0
{
961
0
  uint64_t idx64 = static_cast<uint64_t>(ty) * nTiles_h(m_tild_header.get_parameters()) + tx;
962
0
  if (idx64 > std::numeric_limits<uint32_t>::max()) {
963
0
    return Error{heif_error_Invalid_input,
964
0
                 heif_suberror_Unspecified,
965
0
                 "Tile index out of range."};
966
0
  }
967
0
  auto idx = static_cast<uint32_t>(idx64);
968
969
0
  if (!m_tild_header.is_tile_offset_known(idx)) {
970
0
    Error err = const_cast<ImageItem_Tiled*>(this)->load_tile_offset_entry(idx);
971
0
    if (err) {
972
0
      return err;
973
0
    }
974
0
  }
975
976
0
  uint64_t offset = m_tild_header.get_tile_offset(idx);
977
0
  uint64_t size = m_tild_header.get_tile_size(idx);
978
979
0
  Error err = get_file()->append_data_from_iloc(get_id(), data, offset, size);
980
0
  if (err.error_code) {
981
0
    return err;
982
0
  }
983
984
0
  return Error::Ok;
985
0
}
986
987
988
Result<DataExtent>
989
ImageItem_Tiled::get_compressed_data_for_tile(uint32_t tx, uint32_t ty) const
990
0
{
991
  // --- get compressed data
992
993
0
  Error err = m_tile_item->initialize_decoder();
994
0
  if (err) {
995
0
    return err;
996
0
  }
997
998
0
  Result<std::vector<uint8_t>> dataResult = m_tile_item->read_bitstream_configuration_data();
999
0
  if (!dataResult) {
1000
0
    return dataResult.error();
1001
0
  }
1002
1003
0
  std::vector<uint8_t> data = std::move(*dataResult);
1004
0
  err = append_compressed_tile_data(data, tx, ty);
1005
0
  if (err) {
1006
0
    return err;
1007
0
  }
1008
1009
  // --- decode
1010
1011
0
  DataExtent extent;
1012
0
  extent.m_raw = std::move(data);
1013
1014
0
  return std::move(extent);
1015
0
}
1016
1017
1018
Result<std::shared_ptr<HeifPixelImage>>
1019
ImageItem_Tiled::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const
1020
0
{
1021
0
  Result<DataExtent> extentResult = get_compressed_data_for_tile(tx, ty);
1022
0
  if (!extentResult) {
1023
0
    return extentResult.error();
1024
0
  }
1025
1026
0
  m_tile_decoder->set_data_extent(std::move(*extentResult));
1027
1028
0
  uint32_t tw = 0, th = 0;
1029
0
  get_tile_size(tw, th);
1030
0
  heif_security_limits tightened = tighten_image_size_limit_for_ispe(
1031
0
      get_context()->get_security_limits(), tw, th,
1032
0
      max_coding_unit_size_for_codec(m_tile_decoder->get_compression_format()));
1033
1034
0
  return m_tile_decoder->decode_single_frame_from_compressed_data(options, &tightened);
1035
0
}
1036
1037
1038
Error ImageItem_Tiled::load_tile_offset_entry(uint32_t idx)
1039
0
{
1040
0
  uint32_t nEntries = mReadChunkSize_bytes / m_tild_header.get_offset_table_entry_size();
1041
0
  std::pair<uint32_t, uint32_t> range = m_tild_header.get_tile_offset_table_range_to_read(idx, nEntries);
1042
1043
0
  return m_tild_header.read_offset_table_range(get_file(), get_id(), range.first, range.second);
1044
0
}
1045
1046
1047
heif_image_tiling ImageItem_Tiled::get_heif_image_tiling() const
1048
0
{
1049
0
  heif_image_tiling tiling{};
1050
1051
0
  tiling.num_columns = nTiles_h(m_tild_header.get_parameters());
1052
0
  tiling.num_rows = nTiles_v(m_tild_header.get_parameters());
1053
1054
0
  tiling.tile_width = m_tild_header.get_parameters().tile_width;
1055
0
  tiling.tile_height = m_tild_header.get_parameters().tile_height;
1056
1057
0
  tiling.image_width = m_tild_header.get_parameters().image_width;
1058
0
  tiling.image_height = m_tild_header.get_parameters().image_height;
1059
0
  tiling.number_of_extra_dimensions = m_tild_header.get_parameters().number_of_extra_dimensions;
1060
0
  for (int i = 0; i < std::min(tiling.number_of_extra_dimensions, uint8_t(8)); i++) {
1061
0
    tiling.extra_dimension_size[i] = m_tild_header.get_parameters().extra_dimensions[i];
1062
0
  }
1063
1064
0
  return tiling;
1065
0
}
1066
1067
1068
void ImageItem_Tiled::get_tile_size(uint32_t& w, uint32_t& h) const
1069
0
{
1070
0
  w = m_tild_header.get_parameters().tile_width;
1071
0
  h = m_tild_header.get_parameters().tile_height;
1072
0
}
1073
1074
1075
Error ImageItem_Tiled::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
1076
0
{
1077
0
  uint32_t tx=0, ty=0; // TODO: find a tile that is defined.
1078
1079
0
  Result<DataExtent> extentResult = get_compressed_data_for_tile(tx, ty);
1080
0
  if (!extentResult) {
1081
0
    return extentResult.error();
1082
0
  }
1083
1084
0
  m_tile_decoder->set_data_extent(std::move(*extentResult));
1085
1086
0
  Error err = m_tile_decoder->get_coded_image_colorspace(out_colorspace, out_chroma);
1087
0
  if (err) {
1088
0
    return err;
1089
0
  }
1090
1091
0
  postprocess_coded_image_colorspace(out_colorspace, out_chroma);
1092
1093
0
  return Error::Ok;
1094
0
}
1095
1096
1097
int ImageItem_Tiled::get_luma_bits_per_pixel() const
1098
0
{
1099
0
  DataExtent any_tile_extent;
1100
0
  append_compressed_tile_data(any_tile_extent.m_raw, 0,0); // TODO: use tile that is already loaded
1101
0
  m_tile_decoder->set_data_extent(std::move(any_tile_extent));
1102
1103
0
  return m_tile_decoder->get_luma_bits_per_pixel();
1104
0
}
1105
1106
int ImageItem_Tiled::get_chroma_bits_per_pixel() const
1107
0
{
1108
0
  DataExtent any_tile_extent;
1109
0
  append_compressed_tile_data(any_tile_extent.m_raw, 0,0); // TODO: use tile that is already loaded
1110
0
  m_tile_decoder->set_data_extent(std::move(any_tile_extent));
1111
1112
0
  return m_tile_decoder->get_chroma_bits_per_pixel();
1113
0
}
1114
1115
heif_brand2 ImageItem_Tiled::get_compatible_brand() const
1116
0
{
1117
0
  return 0;
1118
1119
  // TODO: it is not clear to me what brand to use here.
1120
1121
  /*
1122
  switch (m_tild_header.get_parameters().compression_format_fourcc) {
1123
    case heif_compression_HEVC:
1124
      return heif_brand2_heic;
1125
  }
1126
   */
1127
0
}