Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image-items/overlay.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 "overlay.h"
22
#include "context.h"
23
#include "file.h"
24
#include "color-conversion/colorconversion.h"
25
#include "security_limits.h"
26
27
28
template<typename I>
29
void writevec(uint8_t* data, size_t& idx, I value, int len)
30
0
{
31
0
  for (int i = 0; i < len; i++) {
32
0
    data[idx + i] = static_cast<uint8_t>((value >> (len - 1 - i) * 8) & 0xFF);
33
0
  }
34
35
0
  idx += len;
36
0
}
Unexecuted instantiation: void writevec<unsigned short>(unsigned char*, unsigned long&, unsigned short, int)
Unexecuted instantiation: void writevec<unsigned int>(unsigned char*, unsigned long&, unsigned int, int)
Unexecuted instantiation: void writevec<int>(unsigned char*, unsigned long&, int, int)
37
38
39
static int32_t readvec_signed(const std::vector<uint8_t>& data, int& ptr, int len)
40
220
{
41
220
  const uint32_t high_bit = UINT32_C(0x80) << ((len - 1) * 8);
42
43
220
  uint32_t val = 0;
44
804
  while (len--) {
45
584
    val <<= 8;
46
584
    val |= data[ptr++];
47
584
  }
48
49
220
  bool negative = (val & high_bit) != 0;
50
51
220
  if (negative) {
52
133
    return -static_cast<int32_t>((~val) & 0x7fffffff) -1;
53
133
  }
54
87
  else {
55
87
    return static_cast<int32_t>(val);
56
87
  }
57
58
0
  return val;
59
220
}
60
61
62
static uint32_t readvec(const std::vector<uint8_t>& data, int& ptr, int len)
63
102
{
64
102
  uint32_t val = 0;
65
322
  while (len--) {
66
220
    val <<= 8;
67
220
    val |= data[ptr++];
68
220
  }
69
70
102
  return val;
71
102
}
72
73
74
Error ImageOverlay::parse(size_t num_images, const std::vector<uint8_t>& data)
75
20
{
76
20
  Error eofError(heif_error_Invalid_input,
77
20
                 heif_suberror_Invalid_overlay_data,
78
20
                 "Overlay image data incomplete");
79
80
20
  if (data.size() < 2 + 4 * 2) {
81
1
    return eofError;
82
1
  }
83
84
19
  m_version = data[0];
85
19
  if (m_version != 0) {
86
1
    std::stringstream sstr;
87
1
    sstr << "Overlay image data version " << ((int) m_version) << " is not implemented yet";
88
89
1
    return {heif_error_Unsupported_feature,
90
1
            heif_suberror_Unsupported_data_version,
91
1
            sstr.str()};
92
1
  }
93
94
18
  m_flags = data[1];
95
96
18
  int field_len = ((m_flags & 1) ? 4 : 2);
97
18
  int ptr = 2;
98
99
18
  if (ptr + 4 * 2 + 2 * field_len + num_images * 2 * field_len > data.size()) {
100
1
    return eofError;
101
1
  }
102
103
85
  for (int i = 0; i < 4; i++) {
104
68
    uint16_t color = static_cast<uint16_t>(readvec(data, ptr, 2));
105
68
    m_background_color[i] = color;
106
68
  }
107
108
17
  m_width = readvec(data, ptr, field_len);
109
17
  m_height = readvec(data, ptr, field_len);
110
111
17
  if (m_width == 0 || m_height == 0) {
112
2
    return {heif_error_Invalid_input,
113
2
            heif_suberror_Invalid_overlay_data,
114
2
            "Overlay image with zero width or height."};
115
2
  }
116
117
15
  m_offsets.resize(num_images);
118
119
125
  for (size_t i = 0; i < num_images; i++) {
120
110
    m_offsets[i].x = readvec_signed(data, ptr, field_len);
121
110
    m_offsets[i].y = readvec_signed(data, ptr, field_len);
122
110
  }
123
124
15
  return Error::Ok;
125
17
}
126
127
128
std::vector<uint8_t> ImageOverlay::write() const
129
0
{
130
0
  assert(m_version == 0);
131
132
0
  bool longFields = (m_width > 0xFFFF) || (m_height > 0xFFFF);
133
0
  for (const auto& img : m_offsets) {
134
0
    if (img.x > 0x7FFF || img.y > 0x7FFF || img.x < -32768 || img.y < -32768) {
135
0
      longFields = true;
136
0
      break;
137
0
    }
138
0
  }
139
140
0
  std::vector<uint8_t> data;
141
142
0
  data.resize(2 + 4 * 2 + (longFields ? 4 : 2) * (2 + m_offsets.size() * 2));
143
144
0
  size_t idx = 0;
145
0
  data[idx++] = m_version;
146
0
  data[idx++] = (longFields ? 1 : 0); // flags
147
148
0
  for (uint16_t color : m_background_color) {
149
0
    writevec(data.data(), idx, color, 2);
150
0
  }
151
152
0
  writevec(data.data(), idx, m_width, longFields ? 4 : 2);
153
0
  writevec(data.data(), idx, m_height, longFields ? 4 : 2);
154
155
0
  for (const auto& img : m_offsets) {
156
0
    writevec(data.data(), idx, img.x, longFields ? 4 : 2);
157
0
    writevec(data.data(), idx, img.y, longFields ? 4 : 2);
158
0
  }
159
160
0
  assert(idx == data.size());
161
162
0
  return data;
163
0
}
164
165
166
std::string ImageOverlay::dump() const
167
0
{
168
0
  std::stringstream sstr;
169
170
0
  sstr << "version: " << ((int) m_version) << "\n"
171
0
       << "flags: " << ((int) m_flags) << "\n"
172
0
       << "background color: " << m_background_color[0]
173
0
       << ";" << m_background_color[1]
174
0
       << ";" << m_background_color[2]
175
0
       << ";" << m_background_color[3] << "\n"
176
0
       << "canvas size: " << m_width << "x" << m_height << "\n"
177
0
       << "offsets: ";
178
179
0
  for (const ImageWithOffset& offset : m_offsets) {
180
0
    sstr << offset.x << ";" << offset.y << " ";
181
0
  }
182
0
  sstr << "\n";
183
184
0
  return sstr.str();
185
0
}
186
187
188
void ImageOverlay::get_background_color(uint16_t col[4]) const
189
0
{
190
0
  for (int i = 0; i < 4; i++) {
191
0
    col[i] = m_background_color[i];
192
0
  }
193
0
}
194
195
196
void ImageOverlay::get_offset(size_t image_index, int32_t* x, int32_t* y) const
197
0
{
198
0
  assert(image_index < m_offsets.size());
199
0
  assert(x && y);
200
201
0
  *x = m_offsets[image_index].x;
202
0
  *y = m_offsets[image_index].y;
203
0
}
204
205
206
207
ImageItem_Overlay::ImageItem_Overlay(HeifContext* ctx)
208
0
    : ImageItem(ctx)
209
0
{
210
0
}
211
212
213
ImageItem_Overlay::ImageItem_Overlay(HeifContext* ctx, heif_item_id id)
214
54
    : ImageItem(ctx, id)
215
54
{
216
54
}
217
218
219
Error ImageItem_Overlay::initialize_decoder()
220
39
{
221
39
  Error err = read_overlay_spec();
222
39
  if (err) {
223
24
    return err;
224
24
  }
225
226
15
  return Error::Ok;
227
39
}
228
229
230
Error ImageItem_Overlay::read_overlay_spec()
231
39
{
232
39
  auto heif_file = get_context()->get_heif_file();
233
234
39
  auto iref_box = heif_file->get_iref_box();
235
236
39
  if (!iref_box) {
237
4
    return {heif_error_Invalid_input,
238
4
            heif_suberror_No_iref_box,
239
4
            "No iref box available, but needed for iovl image"};
240
4
  }
241
242
243
35
  m_overlay_image_ids = iref_box->get_references(get_id(), fourcc("dimg"));
244
245
  /* TODO: probably, it is valid that an iovl image has no references ?
246
247
  if (image_references.empty()) {
248
    return Error(heif_error_Invalid_input,
249
                 heif_suberror_Missing_grid_images,
250
                 "'iovl' image with more than one reference image");
251
  }
252
  */
253
254
255
35
  auto overlayDataResult = heif_file->get_uncompressed_item_data(get_id());
256
35
  if (!overlayDataResult) {
257
15
    return overlayDataResult.error();
258
15
  }
259
260
20
  Error err = m_overlay_spec.parse(m_overlay_image_ids.size(), *overlayDataResult);
261
20
  if (err) {
262
5
    return err;
263
5
  }
264
265
15
  if (m_overlay_image_ids.size() != m_overlay_spec.get_num_offsets()) {
266
0
    return Error(heif_error_Invalid_input,
267
0
                 heif_suberror_Invalid_overlay_data,
268
0
                 "Number of image offsets does not match the number of image references");
269
0
  }
270
271
15
  return Error::Ok;
272
15
}
273
274
275
Result<std::shared_ptr<HeifPixelImage>> ImageItem_Overlay::decode_compressed_image(const heif_decoding_options& options,
276
                                                                                   bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0,
277
                                                                                   std::set<heif_item_id> processed_ids) const
278
0
{
279
0
  return decode_overlay_image(options, processed_ids);
280
0
}
281
282
283
Result<std::shared_ptr<HeifPixelImage>> ImageItem_Overlay::decode_overlay_image(const heif_decoding_options& options,
284
                                                                                std::set<heif_item_id> processed_ids) const
285
0
{
286
0
  if (processed_ids.contains(get_id())) {
287
0
    return Error{heif_error_Invalid_input,
288
0
                 heif_suberror_Unspecified,
289
0
                 "'iref' has cyclic references"};
290
0
  }
291
292
0
  processed_ids.insert(get_id());
293
294
295
0
  std::shared_ptr<HeifPixelImage> img;
296
297
0
  uint32_t w = m_overlay_spec.get_canvas_width();
298
0
  uint32_t h = m_overlay_spec.get_canvas_height();
299
300
0
  Error err = check_for_valid_image_size(get_context()->get_security_limits(), w, h);
301
0
  if (err) {
302
0
    return err;
303
0
  }
304
305
  // TODO: seems we always have to compose this in RGB since the background color is an RGB value
306
0
  img = std::make_shared<HeifPixelImage>();
307
0
  img->create(w, h,
308
0
              heif_colorspace_RGB,
309
0
              heif_chroma_444);
310
0
  if (auto error = img->add_plane(heif_channel_R, w, h, 8, get_context()->get_security_limits())) { // TODO: other bit depths
311
0
    return error;
312
0
  }
313
0
  if (auto error = img->add_plane(heif_channel_G, w, h, 8, get_context()->get_security_limits())) { // TODO: other bit depths
314
0
    return error;
315
0
  }
316
0
  if (auto error = img->add_plane(heif_channel_B, w, h, 8, get_context()->get_security_limits())) { // TODO: other bit depths
317
0
    return error;
318
0
  }
319
320
0
  uint16_t bkg_color[4];
321
0
  m_overlay_spec.get_background_color(bkg_color);
322
323
0
  err = img->fill_RGB_16bit(bkg_color[0], bkg_color[1], bkg_color[2], bkg_color[3]);
324
0
  if (err) {
325
0
    return err;
326
0
  }
327
328
0
  for (size_t i = 0; i < m_overlay_image_ids.size(); i++) {
329
330
    // detect if 'iovl' is referencing itself
331
332
0
    if (m_overlay_image_ids[i] == get_id()) {
333
0
      return Error{heif_error_Invalid_input,
334
0
                   heif_suberror_Unspecified,
335
0
                   "Self-reference in 'iovl' image item."};
336
0
    }
337
338
0
    auto imgItem = get_context()->get_image(m_overlay_image_ids[i], true);
339
0
    if (!imgItem) {
340
0
      return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "'iovl' image references a non-existing item.");
341
0
    }
342
0
    if (auto error = imgItem->get_item_error()) {
343
0
      return error;
344
0
    }
345
346
0
    auto decodeResult = imgItem->decode_image(options, false, 0,0, processed_ids);
347
0
    if (!decodeResult) {
348
0
      return decodeResult.error();
349
0
    }
350
351
0
    std::shared_ptr<HeifPixelImage> overlay_img = *decodeResult;
352
353
354
    // process overlay in RGB space
355
356
0
    if (overlay_img->get_colorspace() != heif_colorspace_RGB ||
357
0
        overlay_img->get_chroma_format() != heif_chroma_444) {
358
0
      auto overlay_img_result = convert_colorspace(overlay_img, heif_colorspace_RGB, heif_chroma_444,
359
0
                                                   nclx_profile::undefined(),
360
0
                                                   0, options.color_conversion_options, options.color_conversion_options_ext,
361
0
                                                   get_context()->get_security_limits());
362
0
      if (!overlay_img_result) {
363
0
        return overlay_img_result.error();
364
0
      }
365
0
      else {
366
0
        overlay_img = *overlay_img_result;
367
0
      }
368
0
    }
369
370
0
    int32_t dx, dy;
371
0
    m_overlay_spec.get_offset(i, &dx, &dy);
372
373
0
    err = img->overlay(overlay_img, dx, dy);
374
0
    if (err) {
375
0
      if (err.error_code == heif_error_Invalid_input &&
376
0
          err.sub_error_code == heif_suberror_Overlay_image_outside_of_canvas) {
377
        // NOP, ignore this error
378
0
      }
379
0
      else {
380
0
        return err;
381
0
      }
382
0
    }
383
0
  }
384
385
0
  return img;
386
0
}
387
388
389
int ImageItem_Overlay::get_luma_bits_per_pixel() const
390
1
{
391
1
  heif_item_id child;
392
1
  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
393
1
  if (err) {
394
1
    return -1;
395
1
  }
396
397
0
  auto image = get_context()->get_image(child, true);
398
0
  return image->get_luma_bits_per_pixel();
399
1
}
400
401
402
int ImageItem_Overlay::get_chroma_bits_per_pixel() const
403
0
{
404
0
  heif_item_id child;
405
0
  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
406
0
  if (err) {
407
0
    return -1;
408
0
  }
409
410
0
  auto image = get_context()->get_image(child, true);
411
0
  return image->get_chroma_bits_per_pixel();
412
0
}
413
414
415
Error ImageItem_Overlay::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
416
0
{
417
0
  *out_colorspace = heif_colorspace_RGB;
418
0
  *out_chroma = heif_chroma_444;
419
420
0
  return Error::Ok;
421
0
}
422
423
424
Result<std::shared_ptr<ImageItem_Overlay>> ImageItem_Overlay::add_new_overlay_item(HeifContext* ctx, const ImageOverlay& overlayspec)
425
0
{
426
0
  if (overlayspec.get_num_offsets() > 0xFFFF) {
427
0
    return Error{heif_error_Usage_error,
428
0
                 heif_suberror_Unspecified,
429
0
                 "Too many overlay images (maximum: 65535)"};
430
0
  }
431
432
0
  std::vector<heif_item_id> ref_ids;
433
434
0
  auto file = ctx->get_heif_file();
435
436
0
  for (const auto& overlay : overlayspec.get_overlay_stack()) {
437
0
    file->get_infe_box(overlay.image_id)->set_hidden_item(true); // only show the full overlay
438
0
    ref_ids.push_back(overlay.image_id);
439
0
  }
440
441
442
  // Create ImageOverlay
443
444
0
  std::vector<uint8_t> iovl_data = overlayspec.write();
445
446
  // Create IOVL Item
447
448
0
  heif_item_id iovl_id = file->add_new_image(fourcc("iovl"));
449
0
  std::shared_ptr<ImageItem_Overlay> iovl_image = std::make_shared<ImageItem_Overlay>(ctx, iovl_id);
450
0
  ctx->insert_image_item(iovl_id, iovl_image);
451
0
  const int construction_method = 1; // 0=mdat 1=idat
452
0
  file->append_iloc_data(iovl_id, iovl_data, construction_method);
453
454
  // Connect images to overlay
455
0
  file->add_iref_reference(iovl_id, fourcc("dimg"), ref_ids);
456
457
  // Add ISPE property
458
0
  auto ispe = std::make_shared<Box_ispe>();
459
0
  ispe->set_size(overlayspec.get_canvas_width(), overlayspec.get_canvas_height());
460
0
  iovl_image->add_property(ispe, false);
461
462
  // Add PIXI property (copy from first image) - According to MIAF, all images shall have the same color information.
463
0
  auto pixi = file->get_property_for_item<Box_pixi>(ref_ids[0]);
464
0
  iovl_image->add_property(pixi, true);
465
466
  // Set Brands
467
  //m_heif_file->set_brand(encoder->plugin->compression_format,
468
  //                       out_grid_image->is_miaf_compatible());
469
470
0
  return iovl_image;
471
0
}
472
473
heif_brand2 ImageItem_Overlay::get_compatible_brand() const
474
0
{
475
0
  if (m_overlay_image_ids.empty()) { return 0; }
476
477
0
  heif_item_id child_id = m_overlay_image_ids[0];
478
0
  auto child = get_context()->get_image(child_id, false);
479
0
  if (!child) { return 0; }
480
481
0
  return child->get_compatible_brand();
482
0
}