Coverage Report

Created: 2025-12-03 07:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image-items/unc_image.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2023 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 <cstdint>
22
#include <cstring>
23
#include <algorithm>
24
#include <map>
25
#include <iostream>
26
#include <cassert>
27
#include <utility>
28
29
#include "common_utils.h"
30
#include "context.h"
31
#include "compression.h"
32
#include "error.h"
33
#include "libheif/heif.h"
34
#include "codecs/uncompressed/unc_types.h"
35
#include "codecs/uncompressed/unc_boxes.h"
36
#include "unc_image.h"
37
#include "codecs/uncompressed/unc_dec.h"
38
#include "codecs/uncompressed/unc_enc.h"
39
#include "codecs/uncompressed/unc_codec.h"
40
#include "image_item.h"
41
42
43
44
static void maybe_make_minimised_uncC(std::shared_ptr<Box_uncC>& uncC, const std::shared_ptr<const HeifPixelImage>& image)
45
0
{
46
0
  uncC->set_version(0);
47
0
  if (image->get_colorspace() != heif_colorspace_RGB) {
48
0
    return;
49
0
  }
50
0
  if (!((image->get_chroma_format() == heif_chroma_interleaved_RGB) || (image->get_chroma_format() == heif_chroma_interleaved_RGBA))) {
51
0
    return;
52
0
  }
53
0
  if (image->get_bits_per_pixel(heif_channel_interleaved) != 8) {
54
0
    return;
55
0
  }
56
0
  if (image->get_chroma_format() == heif_chroma_interleaved_RGBA) {
57
0
    uncC->set_profile(fourcc("rgba"));
58
0
  } else {
59
0
    uncC->set_profile(fourcc("rgb3"));
60
0
  }
61
0
  uncC->set_version(1);
62
0
}
63
64
65
ImageItem_uncompressed::ImageItem_uncompressed(HeifContext* ctx, heif_item_id id)
66
58
    : ImageItem(ctx, id)
67
58
{
68
58
  m_encoder = std::make_shared<Encoder_uncompressed>();
69
58
}
70
71
ImageItem_uncompressed::ImageItem_uncompressed(HeifContext* ctx)
72
0
    : ImageItem(ctx)
73
0
{
74
0
  m_encoder = std::make_shared<Encoder_uncompressed>();
75
0
}
76
77
78
Result<std::shared_ptr<HeifPixelImage>> ImageItem_uncompressed::decode_compressed_image(const heif_decoding_options& options,
79
                                                                                bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const
80
2
{
81
2
  std::shared_ptr<HeifPixelImage> img;
82
83
2
  std::vector<uint8_t> data;
84
85
2
  Error err;
86
87
2
  if (decode_tile_only) {
88
0
    err = UncompressedImageCodec::decode_uncompressed_image_tile(get_context(),
89
0
                                                                 get_id(),
90
0
                                                                 img,
91
0
                                                                 tile_x0, tile_y0);
92
0
  }
93
2
  else {
94
2
    err = UncompressedImageCodec::decode_uncompressed_image(get_context(),
95
2
                                                            get_id(),
96
2
                                                            img);
97
2
  }
98
99
2
  if (err) {
100
2
    return err;
101
2
  }
102
0
  else {
103
0
    return img;
104
0
  }
105
2
}
106
107
108
struct unciHeaders
109
{
110
  std::shared_ptr<Box_uncC> uncC;
111
  std::shared_ptr<Box_cmpd> cmpd;
112
};
113
114
115
static Result<unciHeaders> generate_headers(const std::shared_ptr<const HeifPixelImage>& src_image,
116
                                            const heif_unci_image_parameters* parameters,
117
                                            const heif_encoding_options* options)
118
0
{
119
0
  unciHeaders headers;
120
121
0
  bool uses_tiles = (parameters->tile_width != parameters->image_width ||
122
0
                     parameters->tile_height != parameters->image_height);
123
124
0
  std::shared_ptr<Box_uncC> uncC = std::make_shared<Box_uncC>();
125
0
  if (options && options->prefer_uncC_short_form && !uses_tiles) {
126
0
    maybe_make_minimised_uncC(uncC, src_image);
127
0
  }
128
129
0
  if (uncC->get_version() == 1) {
130
0
    headers.uncC = std::move(uncC);
131
0
  } else {
132
0
    std::shared_ptr<Box_cmpd> cmpd = std::make_shared<Box_cmpd>();
133
134
0
    Error error = fill_cmpd_and_uncC(cmpd, uncC, src_image, parameters);
135
0
    if (error) {
136
0
      return error;
137
0
    }
138
139
0
    headers.cmpd = std::move(cmpd);
140
0
    headers.uncC = std::move(uncC);
141
0
  }
142
143
0
  return headers;
144
0
}
145
146
147
Result<std::vector<uint8_t>> encode_image_tile(const std::shared_ptr<const HeifPixelImage>& src_image)
148
0
{
149
0
  std::vector<uint8_t> data;
150
151
0
  if (src_image->get_colorspace() == heif_colorspace_YCbCr)
152
0
  {
153
0
    uint64_t offset = 0;
154
0
    for (heif_channel channel : {heif_channel_Y, heif_channel_Cb, heif_channel_Cr})
155
0
    {
156
0
      size_t src_stride;
157
0
      uint32_t src_width = src_image->get_width(channel);
158
0
      uint32_t src_height = src_image->get_height(channel);
159
0
      const uint8_t* src_data = src_image->get_plane(channel, &src_stride);
160
0
      uint64_t out_size = src_width * uint64_t{src_height};
161
0
      data.resize(data.size() + out_size);
162
0
      for (uint32_t y = 0; y < src_height; y++) {
163
0
        memcpy(data.data() + offset + y * src_width, src_data + src_stride * y, src_width);
164
0
      }
165
0
      offset += out_size;
166
0
    }
167
168
0
    return data;
169
0
  }
170
0
  else if (src_image->get_colorspace() == heif_colorspace_RGB)
171
0
  {
172
0
    if (src_image->get_chroma_format() == heif_chroma_444)
173
0
    {
174
0
      uint64_t offset = 0;
175
0
      std::vector<heif_channel> channels = {heif_channel_R, heif_channel_G, heif_channel_B};
176
0
      if (src_image->has_channel(heif_channel_Alpha))
177
0
      {
178
0
        channels.push_back(heif_channel_Alpha);
179
0
      }
180
0
      for (heif_channel channel : channels)
181
0
      {
182
0
        size_t src_stride;
183
0
        const uint8_t* src_data = src_image->get_plane(channel, &src_stride);
184
0
        uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width();
185
186
0
        data.resize(data.size() + out_size);
187
0
        for (uint32_t y = 0; y < src_image->get_height(); y++) {
188
0
          memcpy(data.data() + offset + y * src_image->get_width(), src_data + y * src_stride, src_image->get_width());
189
0
        }
190
191
0
        offset += out_size;
192
0
      }
193
194
0
      return data;
195
0
    }
196
0
    else if ((src_image->get_chroma_format() == heif_chroma_interleaved_RGB) ||
197
0
             (src_image->get_chroma_format() == heif_chroma_interleaved_RGBA) ||
198
0
             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) ||
199
0
             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) ||
200
0
             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) ||
201
0
             (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE))
202
0
    {
203
0
      int bytes_per_pixel = 0;
204
0
      switch (src_image->get_chroma_format()) {
205
0
        case heif_chroma_interleaved_RGB:
206
0
          bytes_per_pixel=3;
207
0
          break;
208
0
        case heif_chroma_interleaved_RGBA:
209
0
          bytes_per_pixel=4;
210
0
          break;
211
0
        case heif_chroma_interleaved_RRGGBB_BE:
212
0
        case heif_chroma_interleaved_RRGGBB_LE:
213
0
          bytes_per_pixel=6;
214
0
          break;
215
0
        case heif_chroma_interleaved_RRGGBBAA_BE:
216
0
        case heif_chroma_interleaved_RRGGBBAA_LE:
217
0
          bytes_per_pixel=8;
218
0
          break;
219
0
        default:
220
0
          assert(false);
221
0
      }
222
223
0
      size_t src_stride;
224
0
      const uint8_t* src_data = src_image->get_plane(heif_channel_interleaved, &src_stride);
225
0
      uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_image->get_width() * bytes_per_pixel;
226
0
      data.resize(out_size);
227
0
      for (uint32_t y = 0; y < src_image->get_height(); y++) {
228
0
        memcpy(data.data() + y * src_image->get_width() * bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * bytes_per_pixel);
229
0
      }
230
231
0
      return data;
232
0
    }
233
0
    else
234
0
    {
235
0
      return Error(heif_error_Unsupported_feature,
236
0
                   heif_suberror_Unsupported_data_version,
237
0
                   "Unsupported RGB chroma");
238
0
    }
239
0
  }
240
0
  else if (src_image->get_colorspace() == heif_colorspace_monochrome)
241
0
  {
242
0
    uint64_t offset = 0;
243
0
    std::vector<heif_channel> channels;
244
0
    if (src_image->has_channel(heif_channel_Alpha))
245
0
    {
246
0
      channels = {heif_channel_Y, heif_channel_Alpha};
247
0
    }
248
0
    else
249
0
    {
250
0
      channels = {heif_channel_Y};
251
0
    }
252
0
    for (heif_channel channel : channels)
253
0
    {
254
0
      size_t src_stride;
255
0
      const uint8_t* src_data = src_image->get_plane(channel, &src_stride);
256
0
      uint64_t out_size = static_cast<uint64_t>(src_image->get_height()) * src_stride;
257
0
      data.resize(data.size() + out_size);
258
0
      memcpy(data.data() + offset, src_data, out_size);
259
0
      offset += out_size;
260
0
    }
261
262
0
    return data;
263
0
  }
264
0
  else
265
0
  {
266
0
    return Error(heif_error_Unsupported_feature,
267
0
                 heif_suberror_Unsupported_data_version,
268
0
                 "Unsupported colourspace");
269
0
  }
270
271
0
}
272
273
274
Result<Encoder::CodedImageData> ImageItem_uncompressed::encode(const std::shared_ptr<HeifPixelImage>& src_image,
275
                                                                 heif_encoder* encoder,
276
                                                                 const heif_encoding_options& options,
277
                                                                 heif_image_input_class input_class)
278
0
{
279
0
  return encode_static(src_image, options);
280
0
}
281
282
283
Result<Encoder::CodedImageData> ImageItem_uncompressed::encode_static(const std::shared_ptr<HeifPixelImage>& src_image,
284
                                                               const heif_encoding_options& options)
285
0
{
286
0
  auto parameters = std::unique_ptr<heif_unci_image_parameters,
287
0
                                    void (*)(heif_unci_image_parameters*)>(heif_unci_image_parameters_alloc(),
288
0
                                                                           heif_unci_image_parameters_release);
289
290
0
  parameters->image_width = src_image->get_width();
291
0
  parameters->image_height = src_image->get_height();
292
0
  parameters->tile_width = parameters->image_width;
293
0
  parameters->tile_height = parameters->image_height;
294
295
296
  // --- generate configuration property boxes
297
298
0
  Result<unciHeaders> genHeadersResult = generate_headers(src_image, parameters.get(), &options);
299
0
  if (!genHeadersResult) {
300
0
    return genHeadersResult.error();
301
0
  }
302
303
0
  const unciHeaders& headers = *genHeadersResult;
304
305
0
  Encoder::CodedImageData codedImageData;
306
0
  if (headers.uncC) {
307
0
    codedImageData.properties.push_back(headers.uncC);
308
0
  }
309
0
  if (headers.cmpd) {
310
0
    codedImageData.properties.push_back(headers.cmpd);
311
0
  }
312
313
314
  // --- encode image
315
316
0
  Result<std::vector<uint8_t>> codedBitstreamResult = encode_image_tile(src_image);
317
0
  if (!codedBitstreamResult) {
318
0
    return codedBitstreamResult.error();
319
0
  }
320
321
0
  codedImageData.bitstream = *codedBitstreamResult;
322
323
0
  return codedImageData;
324
0
}
325
326
327
Result<std::shared_ptr<ImageItem_uncompressed>> ImageItem_uncompressed::add_unci_item(HeifContext* ctx,
328
                                                                                      const heif_unci_image_parameters* parameters,
329
                                                                                      const heif_encoding_options* encoding_options,
330
                                                                                      const std::shared_ptr<const HeifPixelImage>& prototype)
331
0
{
332
  // Check input parameters
333
334
0
  if (parameters->image_width % parameters->tile_width != 0 ||
335
0
      parameters->image_height % parameters->tile_height != 0) {
336
0
    return Error{heif_error_Invalid_input,
337
0
                 heif_suberror_Invalid_parameter_value,
338
0
                 "ISO 23001-17 image size must be an integer multiple of the tile size."};
339
0
  }
340
341
  // Create 'unci' Item
342
343
0
  auto file = ctx->get_heif_file();
344
345
0
  heif_item_id unci_id = ctx->get_heif_file()->add_new_image(fourcc("unci"));
346
0
  auto unci_image = std::make_shared<ImageItem_uncompressed>(ctx, unci_id);
347
0
  unci_image->set_resolution(parameters->image_width, parameters->image_height);
348
0
  ctx->insert_image_item(unci_id, unci_image);
349
350
351
  // Generate headers
352
353
0
  Result<unciHeaders> genHeadersResult = generate_headers(prototype, parameters, encoding_options);
354
0
  if (!genHeadersResult) {
355
0
    return genHeadersResult.error();
356
0
  }
357
358
0
  const unciHeaders& headers = *genHeadersResult;
359
360
0
  assert(headers.uncC);
361
362
0
  if (headers.uncC) {
363
0
    unci_image->add_property(headers.uncC, true);
364
0
  }
365
366
0
  if (headers.cmpd) {
367
0
    unci_image->add_property(headers.cmpd, true);
368
0
  }
369
370
  // Add `ispe` property
371
372
0
  auto ispe = std::make_shared<Box_ispe>();
373
0
  ispe->set_size(static_cast<uint32_t>(parameters->image_width),
374
0
                 static_cast<uint32_t>(parameters->image_height));
375
0
  unci_image->add_property(ispe, true);
376
377
0
  if (parameters->compression != heif_unci_compression_off) {
378
0
    auto icef = std::make_shared<Box_icef>();
379
0
    auto cmpC = std::make_shared<Box_cmpC>();
380
0
    cmpC->set_compressed_unit_type(heif_cmpC_compressed_unit_type_image_tile);
381
382
0
    if (false) {
383
0
    }
384
0
#if HAVE_ZLIB
385
0
    else if (parameters->compression == heif_unci_compression_deflate) {
386
0
      cmpC->set_compression_type(fourcc("defl"));
387
0
    }
388
0
    else if (parameters->compression == heif_unci_compression_zlib) {
389
0
      cmpC->set_compression_type(fourcc("zlib"));
390
0
    }
391
0
#endif
392
0
#if HAVE_BROTLI
393
0
    else if (parameters->compression == heif_unci_compression_brotli) {
394
0
      cmpC->set_compression_type(fourcc("brot"));
395
0
    }
396
0
#endif
397
0
    else {
398
0
      assert(false);
399
0
    }
400
401
0
    unci_image->add_property(cmpC, true);
402
0
    unci_image->add_property_without_deduplication(icef, true); // icef is empty. A normal add_property() would lead to a wrong deduplication.
403
0
  }
404
405
  // Create empty image. If we use compression, we append the data piece by piece.
406
407
0
  if (parameters->compression == heif_unci_compression_off) {
408
0
    uint64_t tile_size = headers.uncC->compute_tile_data_size_bytes(parameters->image_width / headers.uncC->get_number_of_tile_columns(),
409
0
                                                                    parameters->image_height / headers.uncC->get_number_of_tile_rows());
410
411
0
    std::vector<uint8_t> dummydata;
412
0
    dummydata.resize(tile_size);
413
414
0
    uint32_t nTiles = (parameters->image_width / parameters->tile_width) * (parameters->image_height / parameters->tile_height);
415
416
0
    for (uint64_t i = 0; i < nTiles; i++) {
417
0
      const int construction_method = 0; // 0=mdat 1=idat
418
0
      file->append_iloc_data(unci_id, dummydata, construction_method);
419
0
    }
420
0
  }
421
422
  // Set Brands
423
  //ctx->get_heif_file()->set_brand(heif_compression_uncompressed, unci_image->is_miaf_compatible());
424
425
0
  return {unci_image};
426
0
}
427
428
429
Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr<const HeifPixelImage>& image)
430
0
{
431
0
  std::shared_ptr<Box_uncC> uncC = get_property<Box_uncC>();
432
0
  assert(uncC);
433
434
0
  uint32_t tile_width = image->get_width();
435
0
  uint32_t tile_height = image->get_height();
436
437
0
  uint32_t tile_idx = tile_y * uncC->get_number_of_tile_columns() + tile_x;
438
439
0
  Result<std::vector<uint8_t>> codedBitstreamResult = encode_image_tile(image);
440
0
  if (!codedBitstreamResult) {
441
0
    return codedBitstreamResult.error();
442
0
  }
443
444
0
  std::shared_ptr<Box_cmpC> cmpC = get_property<Box_cmpC>();
445
0
  std::shared_ptr<Box_icef> icef = get_property<Box_icef>();
446
447
0
  if (!icef || !cmpC) {
448
0
    assert(!icef);
449
0
    assert(!cmpC);
450
451
    // uncompressed
452
453
0
    uint64_t tile_data_size = uncC->compute_tile_data_size_bytes(tile_width, tile_height);
454
455
0
    get_file()->replace_iloc_data(get_id(), tile_idx * tile_data_size, *codedBitstreamResult, 0);
456
0
  }
457
0
  else {
458
0
    std::vector<uint8_t> compressed_data;
459
0
    const std::vector<uint8_t>& raw_data = std::move(*codedBitstreamResult);
460
0
    (void)raw_data;
461
462
0
    uint32_t compr = cmpC->get_compression_type();
463
0
    switch (compr) {
464
0
#if HAVE_ZLIB
465
0
      case fourcc("defl"):
466
0
        compressed_data = compress_deflate(raw_data.data(), raw_data.size());
467
0
        break;
468
0
      case fourcc("zlib"):
469
0
        compressed_data = compress_zlib(raw_data.data(), raw_data.size());
470
0
        break;
471
0
#endif
472
0
#if HAVE_BROTLI
473
0
      case fourcc("brot"):
474
0
        compressed_data = compress_brotli(raw_data.data(), raw_data.size());
475
0
        break;
476
0
#endif
477
0
      default:
478
0
        assert(false);
479
0
        break;
480
0
    }
481
482
0
    get_file()->append_iloc_data(get_id(), compressed_data, 0);
483
484
0
    Box_icef::CompressedUnitInfo unit_info;
485
0
    unit_info.unit_offset = m_next_tile_write_pos;
486
0
    unit_info.unit_size = compressed_data.size();
487
0
    icef->set_component(tile_idx, unit_info);
488
489
0
    m_next_tile_write_pos += compressed_data.size();
490
0
  }
491
492
0
  return Error::Ok;
493
0
}
494
495
496
void ImageItem_uncompressed::get_tile_size(uint32_t& w, uint32_t& h) const
497
0
{
498
0
  auto ispe = get_property<Box_ispe>();
499
0
  auto uncC = get_property<Box_uncC>();
500
501
0
  if (!ispe || !uncC) {
502
0
    w = h = 0;
503
0
  }
504
0
  else {
505
0
    w = ispe->get_width() / uncC->get_number_of_tile_columns();
506
0
    h = ispe->get_height() / uncC->get_number_of_tile_rows();
507
0
  }
508
0
}
509
510
511
heif_image_tiling ImageItem_uncompressed::get_heif_image_tiling() const
512
0
{
513
0
  heif_image_tiling tiling{};
514
515
0
  auto ispe = get_property<Box_ispe>();
516
0
  auto uncC = get_property<Box_uncC>();
517
0
  assert(ispe && uncC);
518
519
0
  tiling.num_columns = uncC->get_number_of_tile_columns();
520
0
  tiling.num_rows = uncC->get_number_of_tile_rows();
521
522
0
  tiling.tile_width = ispe->get_width() / tiling.num_columns;
523
0
  tiling.tile_height = ispe->get_height() / tiling.num_rows;
524
525
0
  tiling.image_width = ispe->get_width();
526
0
  tiling.image_height = ispe->get_height();
527
0
  tiling.number_of_extra_dimensions = 0;
528
529
0
  return tiling;
530
0
}
531
532
Result<std::shared_ptr<Decoder>> ImageItem_uncompressed::get_decoder() const
533
5
{
534
5
  return {m_decoder};
535
5
}
536
537
std::shared_ptr<Encoder> ImageItem_uncompressed::get_encoder() const
538
0
{
539
0
  return m_encoder;
540
0
}
541
542
Error ImageItem_uncompressed::initialize_decoder()
543
43
{
544
43
  std::shared_ptr<Box_cmpd> cmpd = get_property<Box_cmpd>();
545
43
  std::shared_ptr<Box_uncC> uncC = get_property<Box_uncC>();
546
43
  std::shared_ptr<Box_ispe> ispe = get_property<Box_ispe>();
547
548
43
  if (!uncC) {
549
40
    return Error{heif_error_Invalid_input,
550
40
                 heif_suberror_Unspecified,
551
40
                 "No 'uncC' box found."};
552
40
  }
553
554
3
  m_decoder = std::make_shared<Decoder_uncompressed>(uncC, cmpd, ispe);
555
556
3
  return Error::Ok;
557
43
}
558
559
void ImageItem_uncompressed::set_decoder_input_data()
560
3
{
561
3
  DataExtent extent;
562
3
  extent.set_from_image_item(get_context()->get_heif_file(), get_id());
563
564
3
  m_decoder->set_data_extent(std::move(extent));
565
3
}
566
567
568
bool ImageItem_uncompressed::has_coded_alpha_channel() const
569
3
{
570
3
  return m_decoder->has_alpha_component();
571
3
}
572
573
heif_brand2 ImageItem_uncompressed::get_compatible_brand() const
574
0
{
575
0
  return 0; // TODO: not clear to me what to use
576
0
}