Coverage Report

Created: 2025-11-16 07:22

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