Coverage Report

Created: 2025-10-10 07:01

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
0
{
41
0
  const uint32_t high_bit = UINT32_C(0x80) << ((len - 1) * 8);
42
43
0
  uint32_t val = 0;
44
0
  while (len--) {
45
0
    val <<= 8;
46
0
    val |= data[ptr++];
47
0
  }
48
49
0
  bool negative = (val & high_bit) != 0;
50
51
0
  if (negative) {
52
0
    return -static_cast<int32_t>((~val) & 0x7fffffff) -1;
53
0
  }
54
0
  else {
55
0
    return static_cast<int32_t>(val);
56
0
  }
57
58
0
  return val;
59
0
}
60
61
62
static uint32_t readvec(const std::vector<uint8_t>& data, int& ptr, int len)
63
0
{
64
0
  uint32_t val = 0;
65
0
  while (len--) {
66
0
    val <<= 8;
67
0
    val |= data[ptr++];
68
0
  }
69
70
0
  return val;
71
0
}
72
73
74
Error ImageOverlay::parse(size_t num_images, const std::vector<uint8_t>& data)
75
0
{
76
0
  Error eofError(heif_error_Invalid_input,
77
0
                 heif_suberror_Invalid_overlay_data,
78
0
                 "Overlay image data incomplete");
79
80
0
  if (data.size() < 2 + 4 * 2) {
81
0
    return eofError;
82
0
  }
83
84
0
  m_version = data[0];
85
0
  if (m_version != 0) {
86
0
    std::stringstream sstr;
87
0
    sstr << "Overlay image data version " << ((int) m_version) << " is not implemented yet";
88
89
0
    return {heif_error_Unsupported_feature,
90
0
            heif_suberror_Unsupported_data_version,
91
0
            sstr.str()};
92
0
  }
93
94
0
  m_flags = data[1];
95
96
0
  int field_len = ((m_flags & 1) ? 4 : 2);
97
0
  int ptr = 2;
98
99
0
  if (ptr + 4 * 2 + 2 * field_len + num_images * 2 * field_len > data.size()) {
100
0
    return eofError;
101
0
  }
102
103
0
  for (int i = 0; i < 4; i++) {
104
0
    uint16_t color = static_cast<uint16_t>(readvec(data, ptr, 2));
105
0
    m_background_color[i] = color;
106
0
  }
107
108
0
  m_width = readvec(data, ptr, field_len);
109
0
  m_height = readvec(data, ptr, field_len);
110
111
0
  if (m_width == 0 || m_height == 0) {
112
0
    return {heif_error_Invalid_input,
113
0
            heif_suberror_Invalid_overlay_data,
114
0
            "Overlay image with zero width or height."};
115
0
  }
116
117
0
  m_offsets.resize(num_images);
118
119
0
  for (size_t i = 0; i < num_images; i++) {
120
0
    m_offsets[i].x = readvec_signed(data, ptr, field_len);
121
0
    m_offsets[i].y = readvec_signed(data, ptr, field_len);
122
0
  }
123
124
0
  return Error::Ok;
125
0
}
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
0
    : ImageItem(ctx, id)
215
0
{
216
0
}
217
218
219
Error ImageItem_Overlay::initialize_decoder()
220
0
{
221
0
  Error err = read_overlay_spec();
222
0
  if (err) {
223
0
    return err;
224
0
  }
225
226
0
  return Error::Ok;
227
0
}
228
229
230
Error ImageItem_Overlay::read_overlay_spec()
231
0
{
232
0
  auto heif_file = get_context()->get_heif_file();
233
234
0
  auto iref_box = heif_file->get_iref_box();
235
236
0
  if (!iref_box) {
237
0
    return {heif_error_Invalid_input,
238
0
            heif_suberror_No_iref_box,
239
0
            "No iref box available, but needed for iovl image"};
240
0
  }
241
242
243
0
  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
0
  auto overlayDataResult = heif_file->get_uncompressed_item_data(get_id());
256
0
  if (!overlayDataResult) {
257
0
    return overlayDataResult.error();
258
0
  }
259
260
0
  Error err = m_overlay_spec.parse(m_overlay_image_ids.size(), *overlayDataResult);
261
0
  if (err) {
262
0
    return err;
263
0
  }
264
265
0
  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
0
  return Error::Ok;
272
0
}
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) const
277
0
{
278
0
  return decode_overlay_image(options);
279
0
}
280
281
282
Result<std::shared_ptr<HeifPixelImage>> ImageItem_Overlay::decode_overlay_image(const heif_decoding_options& options) const
283
0
{
284
0
  std::shared_ptr<HeifPixelImage> img;
285
286
0
  uint32_t w = m_overlay_spec.get_canvas_width();
287
0
  uint32_t h = m_overlay_spec.get_canvas_height();
288
289
0
  Error err = check_for_valid_image_size(get_context()->get_security_limits(), w, h);
290
0
  if (err) {
291
0
    return err;
292
0
  }
293
294
  // TODO: seems we always have to compose this in RGB since the background color is an RGB value
295
0
  img = std::make_shared<HeifPixelImage>();
296
0
  img->create(w, h,
297
0
              heif_colorspace_RGB,
298
0
              heif_chroma_444);
299
0
  if (auto error = img->add_plane(heif_channel_R, w, h, 8, get_context()->get_security_limits())) { // TODO: other bit depths
300
0
    return error;
301
0
  }
302
0
  if (auto error = img->add_plane(heif_channel_G, w, h, 8, get_context()->get_security_limits())) { // TODO: other bit depths
303
0
    return error;
304
0
  }
305
0
  if (auto error = img->add_plane(heif_channel_B, w, h, 8, get_context()->get_security_limits())) { // TODO: other bit depths
306
0
    return error;
307
0
  }
308
309
0
  uint16_t bkg_color[4];
310
0
  m_overlay_spec.get_background_color(bkg_color);
311
312
0
  err = img->fill_RGB_16bit(bkg_color[0], bkg_color[1], bkg_color[2], bkg_color[3]);
313
0
  if (err) {
314
0
    return err;
315
0
  }
316
317
0
  for (size_t i = 0; i < m_overlay_image_ids.size(); i++) {
318
319
    // detect if 'iovl' is referencing itself
320
321
0
    if (m_overlay_image_ids[i] == get_id()) {
322
0
      return Error{heif_error_Invalid_input,
323
0
                   heif_suberror_Unspecified,
324
0
                   "Self-reference in 'iovl' image item."};
325
0
    }
326
327
0
    auto imgItem = get_context()->get_image(m_overlay_image_ids[i], true);
328
0
    if (!imgItem) {
329
0
      return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "'iovl' image references a non-existing item.");
330
0
    }
331
0
    if (auto error = imgItem->get_item_error()) {
332
0
      return error;
333
0
    }
334
335
0
    auto decodeResult = imgItem->decode_image(options, false, 0,0);
336
0
    if (!decodeResult) {
337
0
      return decodeResult.error();
338
0
    }
339
340
0
    std::shared_ptr<HeifPixelImage> overlay_img = *decodeResult;
341
342
343
    // process overlay in RGB space
344
345
0
    if (overlay_img->get_colorspace() != heif_colorspace_RGB ||
346
0
        overlay_img->get_chroma_format() != heif_chroma_444) {
347
0
      auto overlay_img_result = convert_colorspace(overlay_img, heif_colorspace_RGB, heif_chroma_444,
348
0
                                                   nclx_profile::undefined(),
349
0
                                                   0, options.color_conversion_options, options.color_conversion_options_ext,
350
0
                                                   get_context()->get_security_limits());
351
0
      if (!overlay_img_result) {
352
0
        return overlay_img_result.error();
353
0
      }
354
0
      else {
355
0
        overlay_img = *overlay_img_result;
356
0
      }
357
0
    }
358
359
0
    int32_t dx, dy;
360
0
    m_overlay_spec.get_offset(i, &dx, &dy);
361
362
0
    err = img->overlay(overlay_img, dx, dy);
363
0
    if (err) {
364
0
      if (err.error_code == heif_error_Invalid_input &&
365
0
          err.sub_error_code == heif_suberror_Overlay_image_outside_of_canvas) {
366
        // NOP, ignore this error
367
0
      }
368
0
      else {
369
0
        return err;
370
0
      }
371
0
    }
372
0
  }
373
374
0
  return img;
375
0
}
376
377
378
int ImageItem_Overlay::get_luma_bits_per_pixel() const
379
0
{
380
0
  heif_item_id child;
381
0
  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
382
0
  if (err) {
383
0
    return -1;
384
0
  }
385
386
0
  auto image = get_context()->get_image(child, true);
387
0
  return image->get_luma_bits_per_pixel();
388
0
}
389
390
391
int ImageItem_Overlay::get_chroma_bits_per_pixel() const
392
0
{
393
0
  heif_item_id child;
394
0
  Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child);
395
0
  if (err) {
396
0
    return -1;
397
0
  }
398
399
0
  auto image = get_context()->get_image(child, true);
400
0
  return image->get_chroma_bits_per_pixel();
401
0
}
402
403
404
Error ImageItem_Overlay::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const
405
0
{
406
0
  *out_colorspace = heif_colorspace_RGB;
407
0
  *out_chroma = heif_chroma_444;
408
409
0
  return Error::Ok;
410
0
}
411
412
413
Result<std::shared_ptr<ImageItem_Overlay>> ImageItem_Overlay::add_new_overlay_item(HeifContext* ctx, const ImageOverlay& overlayspec)
414
0
{
415
0
  if (overlayspec.get_num_offsets() > 0xFFFF) {
416
0
    return Error{heif_error_Usage_error,
417
0
                 heif_suberror_Unspecified,
418
0
                 "Too many overlay images (maximum: 65535)"};
419
0
  }
420
421
0
  std::vector<heif_item_id> ref_ids;
422
423
0
  auto file = ctx->get_heif_file();
424
425
0
  for (const auto& overlay : overlayspec.get_overlay_stack()) {
426
0
    file->get_infe_box(overlay.image_id)->set_hidden_item(true); // only show the full overlay
427
0
    ref_ids.push_back(overlay.image_id);
428
0
  }
429
430
431
  // Create ImageOverlay
432
433
0
  std::vector<uint8_t> iovl_data = overlayspec.write();
434
435
  // Create IOVL Item
436
437
0
  heif_item_id iovl_id = file->add_new_image(fourcc("iovl"));
438
0
  std::shared_ptr<ImageItem_Overlay> iovl_image = std::make_shared<ImageItem_Overlay>(ctx, iovl_id);
439
0
  ctx->insert_image_item(iovl_id, iovl_image);
440
0
  const int construction_method = 1; // 0=mdat 1=idat
441
0
  file->append_iloc_data(iovl_id, iovl_data, construction_method);
442
443
  // Connect images to overlay
444
0
  file->add_iref_reference(iovl_id, fourcc("dimg"), ref_ids);
445
446
  // Add ISPE property
447
0
  auto ispe = std::make_shared<Box_ispe>();
448
0
  ispe->set_size(overlayspec.get_canvas_width(), overlayspec.get_canvas_height());
449
0
  iovl_image->add_property(ispe, false);
450
451
  // Add PIXI property (copy from first image) - According to MIAF, all images shall have the same color information.
452
0
  auto pixi = file->get_property_for_item<Box_pixi>(ref_ids[0]);
453
0
  iovl_image->add_property(pixi, true);
454
455
  // Set Brands
456
  //m_heif_file->set_brand(encoder->plugin->compression_format,
457
  //                       out_grid_image->is_miaf_compatible());
458
459
0
  return iovl_image;
460
0
}
461
462
heif_brand2 ImageItem_Overlay::get_compatible_brand() const
463
0
{
464
0
  if (m_overlay_image_ids.empty()) { return 0; }
465
466
0
  heif_item_id child_id = m_overlay_image_ids[0];
467
0
  auto child = get_context()->get_image(child_id, false);
468
0
  if (!child) { return 0; }
469
470
0
  return child->get_compatible_brand();
471
0
}