Coverage Report

Created: 2026-06-07 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/image/pixelimage.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2017 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
22
#include "pixelimage.h"
23
#include "common_utils.h"
24
#include "security_limits.h"
25
26
#include <cassert>
27
#include <cstdlib>
28
#include <cstring>
29
#include <utility>
30
#include <limits>
31
#include <algorithm>
32
#include <map>
33
#include <color-conversion/colorconversion.h>
34
35
#include "codecs/uncompressed/unc_types.h"
36
37
38
heif_chroma chroma_from_subsampling(int h, int v)
39
0
{
40
0
  if (h == 2 && v == 2) {
41
0
    return heif_chroma_420;
42
0
  }
43
0
  else if (h == 2 && v == 1) {
44
0
    return heif_chroma_422;
45
0
  }
46
0
  else if (h == 1 && v == 1) {
47
0
    return heif_chroma_444;
48
0
  }
49
0
  else {
50
0
    assert(false);
51
0
    return heif_chroma_undefined;
52
0
  }
53
0
}
54
55
56
uint32_t chroma_width(uint32_t w, heif_chroma chroma)
57
7.29k
{
58
7.29k
  switch (chroma) {
59
5.60k
    case heif_chroma_420:
60
5.74k
    case heif_chroma_422:
61
5.74k
      return w/2 + (w & 1); // note: prevents integer overflow
62
1.54k
    default:
63
1.54k
      return w;
64
7.29k
  }
65
7.29k
}
66
67
uint32_t chroma_height(uint32_t h, heif_chroma chroma)
68
7.29k
{
69
7.29k
  switch (chroma) {
70
5.60k
    case heif_chroma_420:
71
5.60k
      return h/2 + (h & 1); // note: prevents integer overflow
72
1.69k
    default:
73
1.69k
      return h;
74
7.29k
  }
75
7.29k
}
76
77
uint32_t channel_width(uint32_t w, heif_chroma chroma, heif_channel channel)
78
7.29k
{
79
7.29k
  if (channel == heif_channel_Cb || channel == heif_channel_Cr) {
80
7.29k
    return chroma_width(w, chroma);
81
7.29k
  }
82
0
  else {
83
0
    return w;
84
0
  }
85
7.29k
}
86
87
uint32_t channel_height(uint32_t h, heif_chroma chroma, heif_channel channel)
88
7.29k
{
89
7.29k
  if (channel == heif_channel_Cb || channel == heif_channel_Cr) {
90
7.29k
    return chroma_height(h, chroma);
91
7.29k
  }
92
0
  else {
93
0
    return h;
94
0
  }
95
7.29k
}
96
97
98
99
static std::vector<uint16_t> map_channel_to_component_type(heif_channel channel, heif_chroma chroma)
100
10.4k
{
101
10.4k
  switch (channel) {
102
4.04k
    case heif_channel_Y:
103
4.04k
      return {heif_cmpd_component_type_Y};
104
1.47k
    case heif_channel_Cb:
105
1.47k
      return {heif_cmpd_component_type_Cb};
106
1.47k
    case heif_channel_Cr:
107
1.47k
      return {heif_cmpd_component_type_Cr};
108
683
    case heif_channel_R:
109
683
      return {heif_cmpd_component_type_red};
110
683
    case heif_channel_G:
111
683
      return {heif_cmpd_component_type_green};
112
683
    case heif_channel_B:
113
683
      return {heif_cmpd_component_type_blue};
114
0
    case heif_channel_Alpha:
115
0
      return {heif_cmpd_component_type_alpha};
116
0
    case heif_channel_filter_array:
117
0
      return {heif_cmpd_component_type_filter_array};
118
0
    case heif_channel_depth:
119
0
      return {heif_cmpd_component_type_depth};
120
0
    case heif_channel_disparity:
121
0
      return {heif_cmpd_component_type_disparity};
122
1.41k
    case heif_channel_interleaved:
123
1.41k
      switch (chroma) {
124
574
        case heif_chroma_interleaved_RGB:
125
859
        case heif_chroma_interleaved_RRGGBB_BE:
126
1.41k
        case heif_chroma_interleaved_RRGGBB_LE:
127
1.41k
          return {
128
1.41k
            heif_cmpd_component_type_red,
129
1.41k
            heif_cmpd_component_type_green,
130
1.41k
            heif_cmpd_component_type_blue
131
1.41k
          };
132
0
        case heif_chroma_interleaved_RGBA:
133
0
        case heif_chroma_interleaved_RRGGBBAA_BE:
134
0
        case heif_chroma_interleaved_RRGGBBAA_LE:
135
0
          return {
136
0
            heif_cmpd_component_type_red,
137
0
            heif_cmpd_component_type_green,
138
0
            heif_cmpd_component_type_blue,
139
0
            heif_cmpd_component_type_alpha
140
0
          };
141
0
        default:
142
0
          assert(false);
143
0
          return {static_cast<uint16_t>(1000 + channel)};
144
0
          break;
145
1.41k
      }
146
0
    default:
147
      // For other channels without a direct match,
148
      // use an internal custom value.
149
0
      return {static_cast<uint16_t>(1000 + channel)};
150
10.4k
  }
151
10.4k
}
152
153
154
155
156
157
HeifPixelImage::~HeifPixelImage()
158
6.63k
{
159
10.9k
  for (auto& component : m_storage) {
160
10.9k
    std::free(component.allocated_mem);
161
10.9k
  }
162
6.63k
}
163
164
165
HeifPixelImage::ComponentStorage* HeifPixelImage::find_storage_for_channel(heif_channel channel)
166
17.6k
{
167
29.5k
  for (auto& component : m_storage) {
168
29.5k
    if (component.m_channel == channel) {
169
17.6k
      return &component;
170
17.6k
    }
171
29.5k
  }
172
9
  return nullptr;
173
17.6k
}
174
175
const HeifPixelImage::ComponentStorage* HeifPixelImage::find_storage_for_channel(heif_channel channel) const
176
25.5k
{
177
49.0k
  for (const auto& component : m_storage) {
178
49.0k
    if (component.m_channel == channel) {
179
20.6k
      return &component;
180
20.6k
    }
181
49.0k
  }
182
4.93k
  return nullptr;
183
25.5k
}
184
185
186
int num_interleaved_components_per_plane(heif_chroma chroma)
187
11.5k
{
188
11.5k
  switch (chroma) {
189
0
    case heif_chroma_undefined:
190
247
    case heif_chroma_monochrome:
191
3.60k
    case heif_chroma_420:
192
5.62k
    case heif_chroma_422:
193
9.04k
    case heif_chroma_444:
194
9.04k
      return 1;
195
196
1.14k
    case heif_chroma_interleaved_RGB:
197
1.43k
    case heif_chroma_interleaved_RRGGBB_BE:
198
2.54k
    case heif_chroma_interleaved_RRGGBB_LE:
199
2.54k
      return 3;
200
201
0
    case heif_chroma_interleaved_RGBA:
202
0
    case heif_chroma_interleaved_RRGGBBAA_BE:
203
0
    case heif_chroma_interleaved_RRGGBBAA_LE:
204
0
      return 4;
205
11.5k
  }
206
207
11.5k
  assert(false);
208
0
  return 0;
209
11.5k
}
210
211
212
bool is_integer_multiple_of_chroma_size(uint32_t width,
213
                                        uint32_t height,
214
                                        heif_chroma chroma)
215
0
{
216
0
  switch (chroma) {
217
0
    case heif_chroma_444:
218
0
    case heif_chroma_monochrome:
219
0
      return true;
220
0
    case heif_chroma_422:
221
0
      return (width & 1) == 0;
222
0
    case heif_chroma_420:
223
0
      return (width & 1) == 0 && (height & 1) == 0;
224
0
    default:
225
0
      assert(false);
226
0
      return false;
227
0
  }
228
0
}
229
230
231
std::vector<heif_chroma> get_valid_chroma_values_for_colorspace(heif_colorspace colorspace)
232
3.52k
{
233
3.52k
  switch (colorspace) {
234
3.52k
    case heif_colorspace_YCbCr:
235
3.52k
      return {heif_chroma_420, heif_chroma_422, heif_chroma_444};
236
237
0
    case heif_colorspace_RGB:
238
      // heif_chroma_planar and heif_chroma_444 are synonyms here.
239
      // HeifPixelImage::create() canonicalizes the stored value to
240
      // heif_chroma_444, so internal state and read-back always see 444;
241
      // listing both here signals to callers that either is accepted, and
242
      // prepares for a possible future switch of the canonical name.
243
0
      return {heif_chroma_444,
244
0
              heif_chroma_planar,
245
0
              heif_chroma_interleaved_RGB,
246
0
              heif_chroma_interleaved_RGBA,
247
0
              heif_chroma_interleaved_RRGGBB_BE,
248
0
              heif_chroma_interleaved_RRGGBBAA_BE,
249
0
              heif_chroma_interleaved_RRGGBB_LE,
250
0
              heif_chroma_interleaved_RRGGBBAA_LE};
251
252
0
    case heif_colorspace_monochrome:
253
0
      return {heif_chroma_planar};
254
255
0
    case heif_colorspace_custom:
256
      // Custom-colorspace images may have any number of planar components
257
      // with arbitrary semantics. heif_chroma_planar describes the layout
258
      // (planar, no subsampling); the per-component semantics live in the
259
      // component descriptions.
260
0
      return {heif_chroma_planar};
261
262
0
    case heif_colorspace_filter_array:
263
      // Filter-array (CFA / Bayer) images are a single mosaicked plane.
264
      // The spatial pattern encodes color, so the legacy "monochrome" label
265
      // is misleading; heif_chroma_planar describes only the layout.
266
0
      return {heif_chroma_planar};
267
268
0
    default:
269
0
      return {};
270
3.52k
  }
271
3.52k
}
272
273
274
void HeifPixelImage::create(uint32_t width, uint32_t height, heif_colorspace colorspace, heif_chroma chroma)
275
6.63k
{
276
  // Canonicalize (RGB, planar) to (RGB, 444). heif_chroma_planar is accepted
277
  // as a synonym at this layer too (not just at heif_image_create), so any
278
  // internal caller passing it gets the canonical form stored.
279
6.63k
  if (colorspace == heif_colorspace_RGB && chroma == heif_chroma_planar) {
280
0
    chroma = heif_chroma_444;
281
0
  }
282
283
6.63k
  m_width = width;
284
6.63k
  m_height = height;
285
6.63k
  m_colorspace = colorspace;
286
6.63k
  m_chroma = chroma;
287
6.63k
}
288
289
static uint32_t rounded_size(uint32_t s)
290
21.8k
{
291
21.8k
  s = (s + 1U) & ~1U;
292
293
21.8k
  if (s < 64) {
294
4.59k
    s = 64;
295
4.59k
  }
296
297
21.8k
  return s;
298
21.8k
}
299
300
void HeifPixelImage::register_component_descriptions(ComponentStorage& plane,
301
                                                 const std::vector<uint16_t>& component_types)
302
10.4k
{
303
13.2k
  for (uint16_t type : component_types) {
304
13.2k
    uint32_t id = mint_component_id();
305
13.2k
    plane.m_component_ids.push_back(id);
306
307
13.2k
    ComponentDescription desc;
308
13.2k
    desc.component_id = id;
309
13.2k
    desc.channel = plane.m_channel;
310
13.2k
    desc.component_type = type;
311
13.2k
    desc.datatype = plane.m_datatype;
312
13.2k
    desc.bit_depth = plane.m_bit_depth;
313
13.2k
    desc.width = plane.m_width;
314
13.2k
    desc.height = plane.m_height;
315
13.2k
    desc.has_data_plane = true;
316
13.2k
    add_component_description(std::move(desc));
317
13.2k
  }
318
10.4k
}
319
320
321
void HeifPixelImage::register_component_descriptions(ComponentStorage& plane,
322
                                                 const std::vector<const ComponentDescription*>& source_descriptions)
323
488
{
324
488
  for (const ComponentDescription* src : source_descriptions) {
325
488
    uint32_t id = mint_component_id();
326
488
    plane.m_component_ids.push_back(id);
327
328
    // Start from the source description so per-component metadata
329
    // (component_type, gimi_content_id, has_data_plane, ...) is preserved.
330
    // If the lookup failed (shouldn't happen on a well-formed source),
331
    // fall back to a default-initialized description.
332
488
    ComponentDescription desc;
333
488
    if (src) {
334
488
      desc = *src;
335
488
    }
336
488
    desc.component_id = id;
337
488
    desc.channel = plane.m_channel;
338
488
    desc.datatype = plane.m_datatype;
339
488
    desc.bit_depth = plane.m_bit_depth;
340
488
    desc.width = plane.m_width;
341
488
    desc.height = plane.m_height;
342
488
    add_component_description(std::move(desc));
343
488
  }
344
488
}
345
346
347
Error HeifPixelImage::add_channel(heif_channel channel, uint32_t width, uint32_t height, int bit_depth,
348
                                const heif_security_limits* limits,
349
                                heif_component_datatype datatype)
350
10.4k
{
351
  // for backwards compatibility, allow for 24/32 bits for RGB/RGBA interleaved chromas
352
353
10.4k
  if (m_chroma == heif_chroma_interleaved_RGB && bit_depth == 24) {
354
0
    bit_depth = 8;
355
0
  }
356
357
10.4k
  if (m_chroma == heif_chroma_interleaved_RGBA && bit_depth == 32) {
358
0
    bit_depth = 8;
359
0
  }
360
361
10.4k
  int num_interleaved_pixels = num_interleaved_components_per_plane(m_chroma);
362
363
10.4k
  ComponentStorage plane;
364
10.4k
  plane.m_channel = channel;
365
366
10.4k
  if (auto err = plane.alloc(width, height, datatype, bit_depth, num_interleaved_pixels, limits, m_memory_handle)) {
367
0
    return err;
368
0
  }
369
370
10.4k
  register_component_descriptions(plane, map_channel_to_component_type(channel, m_chroma));
371
10.4k
  m_storage.push_back(std::move(plane));
372
10.4k
  return Error::Ok;
373
10.4k
}
374
375
376
Error HeifPixelImage::ComponentStorage::alloc(uint32_t width, uint32_t height, heif_component_datatype datatype, int bit_depth,
377
                                        int num_interleaved_components,
378
                                        const heif_security_limits* limits,
379
                                        MemoryHandle& memory_handle)
380
10.9k
{
381
10.9k
  assert(bit_depth >= 1);
382
10.9k
  assert(bit_depth <= 128);
383
384
10.9k
  if (width == 0 || height == 0) {
385
0
    return {heif_error_Usage_error,
386
0
            heif_suberror_Unspecified,
387
0
            "Invalid image size"};
388
0
  }
389
390
10.9k
  if (width == std::numeric_limits<uint32_t>::max() || height == std::numeric_limits<uint32_t>::max()) {
391
0
    return {heif_error_Memory_allocation_error,
392
0
            heif_suberror_Security_limit_exceeded,
393
0
            "Image size too large for memory alignment"};
394
0
  }
395
396
  // use 16 byte alignment (enough for 128 bit data-types). Every row is an integer number of data-elements.
397
10.9k
  uint16_t alignment = 16; // must be power of two
398
399
10.9k
  m_width = width;
400
10.9k
  m_height = height;
401
402
10.9k
  m_mem_width = rounded_size(width);
403
10.9k
  m_mem_height = rounded_size(height);
404
405
10.9k
  assert(num_interleaved_components > 0 && num_interleaved_components <= 255);
406
407
10.9k
  m_bit_depth = static_cast<uint8_t>(bit_depth);
408
10.9k
  m_num_interleaved_components = static_cast<uint8_t>(num_interleaved_components);
409
10.9k
  m_datatype = datatype;
410
411
  // Cache bytes-per-pixel for the inner-loop get_bytes_per_pixel().
412
10.9k
  int bytes_per_component;
413
10.9k
  if (bit_depth <= 8)        bytes_per_component = 1;
414
5.15k
  else if (bit_depth <= 16)  bytes_per_component = 2;
415
0
  else if (bit_depth <= 32)  bytes_per_component = 4;
416
0
  else if (bit_depth <= 64)  bytes_per_component = 8;
417
0
  else { assert(bit_depth <= 128); bytes_per_component = 16; }
418
10.9k
  m_bytes_per_pixel = static_cast<uint8_t>(bytes_per_component * num_interleaved_components);
419
420
10.9k
  int bytes_per_pixel = m_bytes_per_pixel;
421
422
10.9k
  uint64_t stride_64 = static_cast<uint64_t>(m_mem_width) * bytes_per_pixel;
423
10.9k
  stride_64 = (stride_64 + alignment - 1U) & ~static_cast<uint64_t>(alignment - 1U);
424
10.9k
  if (stride_64 > std::numeric_limits<size_t>::max()) {
425
0
    return {heif_error_Memory_allocation_error,
426
0
            heif_suberror_Security_limit_exceeded,
427
0
            "Image stride overflow"};
428
0
  }
429
10.9k
  stride = static_cast<size_t>(stride_64);
430
431
10.9k
  assert(alignment>=1);
432
433
10.9k
  if (limits &&
434
10.9k
      limits->max_image_size_pixels &&
435
10.9k
      limits->max_image_size_pixels / height < width) {
436
437
0
    std::stringstream sstr;
438
0
    sstr << "Allocating an image of size " << width << "x" << height << " exceeds the security limit of "
439
0
         << limits->max_image_size_pixels << " pixels";
440
441
0
    return {heif_error_Memory_allocation_error,
442
0
            heif_suberror_Security_limit_exceeded,
443
0
            sstr.str()};
444
0
  }
445
446
  // Check for allocation size overflow using 64-bit arithmetic
447
  // Test case was an overlay image with size 1x134217727.
448
  // Width 1 gets aligned to 64 and then width * height overflows 32 bit systems.
449
10.9k
  uint64_t alloc_64 = static_cast<uint64_t>(m_mem_height) * stride + alignment - 1;
450
10.9k
  if (alloc_64 > std::numeric_limits<size_t>::max()) {
451
0
    return {heif_error_Memory_allocation_error,
452
0
            heif_suberror_Security_limit_exceeded,
453
0
            "Image allocation size overflow"};
454
0
  }
455
10.9k
  allocation_size = static_cast<size_t>(alloc_64);
456
457
10.9k
  if (auto err = memory_handle.alloc(allocation_size, limits, "image data")) {
458
0
    return err;
459
0
  }
460
461
    // --- allocate memory
462
463
  // Must zero-initialize: padding regions (stride, rounded_size(), alignment slack) are not
464
  // written by decoders, so uninitialized contents would leak across decoded images.
465
10.9k
  allocated_mem = static_cast<uint8_t*>(std::calloc(1, allocation_size));
466
10.9k
  if (allocated_mem == nullptr) {
467
0
    std::stringstream sstr;
468
0
    sstr << "Allocating " << allocation_size << " bytes failed";
469
470
0
    return {heif_error_Memory_allocation_error,
471
0
            heif_suberror_Unspecified,
472
0
            sstr.str()};
473
0
  }
474
475
10.9k
  uint8_t* mem_8 = allocated_mem;
476
477
  // shift beginning of image data to aligned memory position
478
479
10.9k
  auto mem_start_addr = (uint64_t) mem_8;
480
10.9k
  auto mem_start_offset = (mem_start_addr & (alignment - 1U));
481
10.9k
  if (mem_start_offset != 0) {
482
0
    mem_8 += alignment - mem_start_offset;
483
0
  }
484
485
10.9k
  mem = mem_8;
486
487
10.9k
  return Error::Ok;
488
10.9k
}
489
490
491
Error HeifPixelImage::extend_padding_to_size(uint32_t width, uint32_t height, bool adjust_size,
492
                                             const heif_security_limits* limits)
493
0
{
494
0
  for (auto& component : m_storage) {
495
    // get_subsampled_size() only adjusts the size for Cb/Cr; for every other
496
    // channel it assumes the component has the full logical image size. We
497
    // cannot compute a correct padded size for a non-Cb/Cr component that does
498
    // not follow that assumption (e.g. multi-component ISO 23001-17 images).
499
0
    if ((component.m_width != m_width ||
500
0
         component.m_height != m_height) &&
501
0
        (component.m_channel != heif_channel_Cb &&
502
0
         component.m_channel != heif_channel_Cr)) {
503
0
      return Error{heif_error_Unsupported_feature,
504
0
                   heif_suberror_Unspecified,
505
0
                   "Cannot extend padding for an image with non-uniform component sizes."};
506
0
    }
507
508
0
    uint32_t subsampled_width, subsampled_height;
509
0
    get_subsampled_size(width, height, component.m_channel, m_chroma,
510
0
                        &subsampled_width, &subsampled_height);
511
512
0
    uint32_t old_width = component.m_width;
513
0
    uint32_t old_height = component.m_height;
514
515
0
    int bytes_per_pixel = component.get_bytes_per_pixel();
516
517
0
    if (component.m_mem_width < subsampled_width ||
518
0
        component.m_mem_height < subsampled_height) {
519
520
0
      ComponentStorage newPlane;
521
0
      newPlane.m_channel = component.m_channel;
522
0
      newPlane.m_component_ids = component.m_component_ids;
523
0
      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, component.m_datatype, component.m_bit_depth,
524
0
                                    num_interleaved_components_per_plane(m_chroma),
525
0
                                    limits, m_memory_handle))
526
0
      {
527
0
        return err;
528
0
      }
529
530
      // This is not needed, but we have to silence the clang-tidy false positive.
531
0
      if (!newPlane.mem) {
532
0
        return Error::InternalError;
533
0
      }
534
535
      // copy the visible part of the old plane into the new plane
536
537
0
      for (uint32_t y = 0; y < component.m_height; y++) {
538
0
        memcpy(static_cast<uint8_t*>(newPlane.mem) + y * newPlane.stride,
539
0
               static_cast<uint8_t*>(component.mem) + y * component.stride,
540
0
               component.m_width * bytes_per_pixel);
541
0
      }
542
543
      // --- release the old plane before replacing it with the reallocated plane
544
545
0
      m_memory_handle.free(component.allocation_size);
546
0
      std::free(component.allocated_mem);
547
548
0
      component = newPlane;
549
0
    }
550
551
    // extend plane size
552
553
0
    if (old_width != subsampled_width) {
554
0
      for (uint32_t y = 0; y < old_height; y++) {
555
0
        for (uint32_t x = old_width; x < subsampled_width; x++) {
556
0
          memcpy(static_cast<uint8_t*>(component.mem) + y * component.stride + x * bytes_per_pixel,
557
0
                 static_cast<uint8_t*>(component.mem) + y * component.stride + (old_width - 1) * bytes_per_pixel,
558
0
                 bytes_per_pixel);
559
0
        }
560
0
      }
561
0
    }
562
563
0
    for (uint32_t y = old_height; y < subsampled_height; y++) {
564
0
      memcpy(static_cast<uint8_t*>(component.mem) + y * component.stride,
565
0
             static_cast<uint8_t*>(component.mem) + (old_height - 1) * component.stride,
566
0
             subsampled_width * bytes_per_pixel);
567
0
    }
568
569
570
0
    if (adjust_size) {
571
0
      component.m_width = subsampled_width;
572
0
      component.m_height = subsampled_height;
573
0
    }
574
0
  }
575
576
  // modify logical image size, if requested
577
578
0
  if (adjust_size) {
579
0
    m_width = width;
580
0
    m_height = height;
581
0
  }
582
583
0
  return Error::Ok;
584
0
}
585
586
587
Error HeifPixelImage::extend_to_size_with_zero(uint32_t width, uint32_t height, const heif_security_limits* limits)
588
0
{
589
0
  for (auto& component : m_storage) {
590
    // See extend_padding_to_size(): get_subsampled_size() assumes a non-Cb/Cr
591
    // component has the full logical image size, so we cannot compute a correct
592
    // target size for a component that does not follow that assumption.
593
0
    if ((component.m_width != m_width ||
594
0
         component.m_height != m_height) &&
595
0
        (component.m_channel != heif_channel_Cb &&
596
0
         component.m_channel != heif_channel_Cr)) {
597
0
      return Error{heif_error_Unsupported_feature,
598
0
                   heif_suberror_Unspecified,
599
0
                   "Cannot extend an image with non-uniform component sizes."};
600
0
    }
601
602
0
    uint32_t subsampled_width, subsampled_height;
603
0
    get_subsampled_size(width, height, component.m_channel, m_chroma,
604
0
                        &subsampled_width, &subsampled_height);
605
606
0
    uint32_t old_width = component.m_width;
607
0
    uint32_t old_height = component.m_height;
608
609
0
    int bytes_per_pixel = component.get_bytes_per_pixel();
610
611
0
    if (component.m_mem_width < subsampled_width ||
612
0
        component.m_mem_height < subsampled_height) {
613
614
0
      ComponentStorage newPlane;
615
0
      newPlane.m_channel = component.m_channel;
616
0
      newPlane.m_component_ids = component.m_component_ids;
617
0
      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, component.m_datatype, component.m_bit_depth, num_interleaved_components_per_plane(m_chroma), limits, m_memory_handle)) {
618
0
        return err;
619
0
      }
620
621
      // This is not needed, but we have to silence the clang-tidy false positive.
622
0
      if (!newPlane.mem) {
623
0
        return Error::InternalError;
624
0
      }
625
626
      // copy the visible part of the old plane into the new plane
627
628
0
      for (uint32_t y = 0; y < component.m_height; y++) {
629
0
        memcpy(static_cast<uint8_t*>(newPlane.mem) + y * newPlane.stride,
630
0
               static_cast<uint8_t*>(component.mem) + y * component.stride,
631
0
               component.m_width * bytes_per_pixel);
632
0
      }
633
634
      // --- release the old plane before replacing it with the reallocated plane
635
636
0
      m_memory_handle.free(component.allocation_size);
637
0
      std::free(component.allocated_mem);
638
639
0
      component = newPlane;
640
0
    }
641
642
    // extend plane size
643
644
0
    uint8_t fill = 0;
645
0
    if (bytes_per_pixel == 1 && (component.m_channel == heif_channel_Cb || component.m_channel == heif_channel_Cr)) {
646
0
      fill = 128;
647
0
    }
648
649
0
    if (old_width != subsampled_width) {
650
0
      for (uint32_t y = 0; y < old_height; y++) {
651
0
        memset(static_cast<uint8_t*>(component.mem) + y * component.stride + old_width * bytes_per_pixel,
652
0
               fill,
653
0
               bytes_per_pixel * (subsampled_width - old_width));
654
0
      }
655
0
    }
656
657
0
    for (uint32_t y = old_height; y < subsampled_height; y++) {
658
0
      memset(static_cast<uint8_t*>(component.mem) + y * component.stride,
659
0
             fill,
660
0
             subsampled_width * bytes_per_pixel);
661
0
    }
662
663
664
0
    component.m_width = subsampled_width;
665
0
    component.m_height = subsampled_height;
666
667
    // Keep ComponentDescriptions in sync with the resized plane so that
668
    // get_component_width/height stays consistent with get_width(channel).
669
0
    for (uint32_t id : component.m_component_ids) {
670
0
      if (auto* desc = find_component_description(id)) {
671
0
        desc->width = subsampled_width;
672
0
        desc->height = subsampled_height;
673
0
      }
674
0
    }
675
0
  }
676
677
  // modify the logical image size
678
679
0
  m_width = width;
680
0
  m_height = height;
681
682
0
  return Error::Ok;
683
0
}
684
685
bool HeifPixelImage::has_channel(heif_channel channel) const
686
5.74k
{
687
5.74k
  return find_storage_for_channel(channel) != nullptr;
688
5.74k
}
689
690
691
bool HeifPixelImage::has_alpha() const
692
0
{
693
0
  return has_channel(heif_channel_Alpha) ||
694
0
         get_chroma_format() == heif_chroma_interleaved_RGBA ||
695
0
         get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE ||
696
0
         get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE;
697
0
}
698
699
700
uint32_t HeifPixelImage::get_width(heif_channel channel) const
701
5.16k
{
702
5.16k
  auto* comp = find_storage_for_channel(channel);
703
5.16k
  if (!comp) {
704
0
    return 0;
705
0
  }
706
707
5.16k
  return comp->m_width;
708
5.16k
}
709
710
711
uint32_t HeifPixelImage::get_height(heif_channel channel) const
712
4.86k
{
713
4.86k
  auto* comp = find_storage_for_channel(channel);
714
4.86k
  if (!comp) {
715
0
    return 0;
716
0
  }
717
718
4.86k
  return comp->m_height;
719
4.86k
}
720
721
722
uint32_t HeifPixelImage::get_width(uint32_t component_id) const
723
0
{
724
0
  auto* comp = find_storage_for_component(component_id);
725
0
  if (!comp) {
726
0
    return 0;
727
0
  }
728
729
0
  return comp->m_width;
730
0
}
731
732
733
uint32_t HeifPixelImage::get_height(uint32_t component_id) const
734
0
{
735
0
  auto* comp = find_storage_for_component(component_id);
736
0
  if (!comp) {
737
0
    return 0;
738
0
  }
739
740
0
  return comp->m_height;
741
0
}
742
743
744
bool HeifPixelImage::primary_planes_have_size(uint32_t width, uint32_t height) const
745
1.45k
{
746
3.21k
  auto channel_has_size = [&](heif_channel channel, uint32_t w, uint32_t h) {
747
    // get_width()/get_height() return 0 for an absent channel -> mismatch.
748
3.21k
    return get_width(channel) == w && get_height(channel) == h;
749
3.21k
  };
750
751
1.45k
  switch (m_colorspace) {
752
247
    case heif_colorspace_monochrome:
753
247
      return channel_has_size(heif_channel_Y, width, height);
754
755
1.20k
    case heif_colorspace_YCbCr: {
756
      // Y has the full size; Cb/Cr are subsampled according to the chroma format.
757
      // Downstream color conversion derives chroma plane indices from m_chroma, so
758
      // Cb/Cr must actually match those subsampled dimensions (issue #1796).
759
1.20k
      if (!channel_has_size(heif_channel_Y, width, height)) {
760
321
        return false;
761
321
      }
762
883
      if (m_chroma == heif_chroma_monochrome) {
763
0
        return true;
764
0
      }
765
883
      uint32_t chroma_w, chroma_h;
766
883
      get_subsampled_size(width, height, heif_channel_Cb, m_chroma, &chroma_w, &chroma_h);
767
883
      return channel_has_size(heif_channel_Cb, chroma_w, chroma_h) &&
768
883
             channel_has_size(heif_channel_Cr, chroma_w, chroma_h);
769
883
    }
770
771
0
    case heif_colorspace_RGB:
772
0
      if (m_chroma == heif_chroma_444) {
773
        // planar RGB: all three planes must be present and have the full size
774
0
        return channel_has_size(heif_channel_R, width, height) &&
775
0
               channel_has_size(heif_channel_G, width, height) &&
776
0
               channel_has_size(heif_channel_B, width, height);
777
0
      }
778
0
      else {
779
0
        return channel_has_size(heif_channel_interleaved, width, height);
780
0
      }
781
782
0
    case heif_colorspace_filter_array:
783
0
      return channel_has_size(heif_channel_filter_array, width, height);
784
785
0
    case heif_colorspace_undefined:
786
0
    default:
787
      // Multi-component / custom-colorspace images (CMYK, bayer configs, ...)
788
      // cannot be checked generically; codec-specific overrides handle these.
789
0
      return true;
790
1.45k
  }
791
1.45k
}
792
793
794
std::set<heif_channel> HeifPixelImage::get_channel_set() const
795
1.13k
{
796
1.13k
  std::set<heif_channel> channels;
797
798
2.89k
  for (const auto& component : m_storage) {
799
2.89k
    channels.insert(component.m_channel);
800
2.89k
  }
801
802
1.13k
  return channels;
803
1.13k
}
804
805
806
uint16_t HeifPixelImage::get_storage_bits_per_pixel(enum heif_channel channel) const
807
0
{
808
0
  auto* comp = find_storage_for_channel(channel);
809
0
  if (!comp) {
810
    // Channel not present. The return type is unsigned, so the historical
811
    // `return -1` actually yielded 65535; use 0 as an unambiguous
812
    // "not present" value (no real channel has 0 bits per pixel).
813
0
    return 0;
814
0
  }
815
816
0
  uint32_t bpp = comp->get_bytes_per_pixel() * 8;
817
0
  assert(bpp <= 256);
818
0
  return static_cast<uint8_t>(bpp);
819
0
}
820
821
822
uint16_t HeifPixelImage::get_bits_per_pixel(enum heif_channel channel) const
823
9.78k
{
824
9.78k
  auto* comp = find_storage_for_channel(channel);
825
9.78k
  if (!comp) {
826
    // Channel not present -- see get_storage_bits_per_pixel().
827
0
    return 0;
828
0
  }
829
830
9.78k
  return comp->m_bit_depth;
831
9.78k
}
832
833
834
uint16_t HeifPixelImage::get_visual_image_bits_per_pixel() const
835
1.13k
{
836
1.13k
  switch (m_colorspace) {
837
247
    case heif_colorspace_monochrome:
838
247
      return get_bits_per_pixel(heif_channel_Y);
839
0
      break;
840
883
    case heif_colorspace_YCbCr:
841
883
      return std::max(get_bits_per_pixel(heif_channel_Y),
842
883
                      std::max(get_bits_per_pixel(heif_channel_Cb),
843
883
                               get_bits_per_pixel(heif_channel_Cr)));
844
0
      break;
845
0
    case heif_colorspace_RGB:
846
0
      if (m_chroma == heif_chroma_444) {
847
0
        return std::max(get_bits_per_pixel(heif_channel_R),
848
0
             std::max(get_bits_per_pixel(heif_channel_G),
849
0
                        get_bits_per_pixel(heif_channel_B)));
850
0
      }
851
0
      else {
852
0
        assert(has_channel(heif_channel_interleaved));
853
0
        return get_bits_per_pixel(heif_channel_interleaved);
854
0
      }
855
0
      break;
856
0
    case heif_colorspace_custom:
857
0
      return 0;
858
0
      break;
859
0
    case heif_colorspace_filter_array:
860
0
      assert(has_channel(heif_channel_filter_array));
861
0
      return get_bits_per_pixel(heif_channel_filter_array);
862
0
    default:
863
0
      assert(false);
864
0
      return 0;
865
0
      break;
866
1.13k
  }
867
1.13k
}
868
869
870
heif_component_datatype HeifPixelImage::get_datatype(heif_channel channel) const
871
0
{
872
0
  auto* comp = find_storage_for_channel(channel);
873
0
  if (!comp) {
874
0
    return heif_component_datatype_undefined;
875
0
  }
876
877
0
  return comp->m_datatype;
878
0
}
879
880
881
int HeifPixelImage::get_number_of_interleaved_components(heif_channel channel) const
882
0
{
883
0
  auto* comp = find_storage_for_channel(channel);
884
0
  if (!comp) {
885
0
    return 0;
886
0
  }
887
888
0
  return comp->m_num_interleaved_components;
889
0
}
890
891
892
Error HeifPixelImage::copy_new_channel_from(const std::shared_ptr<const HeifPixelImage>& src_image,
893
                                          heif_channel src_channel,
894
                                          heif_channel dst_channel,
895
                                          const heif_security_limits* limits)
896
0
{
897
0
  assert(src_image->has_channel(src_channel));
898
0
  assert(!has_channel(dst_channel));
899
900
0
  uint32_t width = src_image->get_width(src_channel);
901
0
  uint32_t height = src_image->get_height(src_channel);
902
903
0
  const auto* src_plane_ptr = src_image->find_storage_for_channel(src_channel);
904
0
  assert(src_plane_ptr != nullptr);
905
0
  const auto& src_plane = *src_plane_ptr;
906
907
0
  auto err = add_channel(dst_channel, width, height,
908
0
                       src_image->get_bits_per_pixel(src_channel), limits,
909
0
                       src_plane.m_datatype);
910
0
  if (err) {
911
0
    return err;
912
0
  }
913
914
0
  uint8_t* dst;
915
0
  size_t dst_stride = 0;
916
917
0
  const uint8_t* src;
918
0
  size_t src_stride = 0;
919
920
0
  src = src_image->get_channel_memory(src_channel, &src_stride);
921
0
  dst = get_channel_memory(dst_channel, &dst_stride);
922
923
0
  uint32_t bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8);
924
925
0
  for (uint32_t y = 0; y < height; y++) {
926
0
    memcpy(dst + y * dst_stride, src + y * src_stride, bpl);
927
0
  }
928
929
0
  return Error::Ok;
930
0
}
931
932
933
Error HeifPixelImage::extract_alpha_from_RGBA(const std::shared_ptr<const HeifPixelImage>& src_image,
934
                                              const heif_security_limits* limits)
935
0
{
936
0
  uint32_t width = src_image->get_width();
937
0
  uint32_t height = src_image->get_height();
938
939
  // The copy loop below assumes 8-bit interleaved RGBA (4 bytes per pixel,
940
  // alpha at byte offset 3). 16-bit interleaved formats (RRGGBBAA_*) have a
941
  // different layout and would be read/written incorrectly.
942
0
  if (src_image->get_bits_per_pixel(heif_channel_interleaved) != 8) {
943
0
    return {heif_error_Unsupported_feature,
944
0
            heif_suberror_Unspecified,
945
0
            "extract_alpha_from_RGBA only supports 8-bit interleaved RGBA"};
946
0
  }
947
948
0
  if (Error err = add_channel(heif_channel_Y, width, height, src_image->get_bits_per_pixel(heif_channel_interleaved), limits)) {
949
0
    return err;
950
0
  }
951
952
0
  uint8_t* dst;
953
0
  size_t dst_stride = 0;
954
955
0
  const uint8_t* src;
956
0
  size_t src_stride = 0;
957
958
0
  src = src_image->get_channel_memory(heif_channel_interleaved, &src_stride);
959
0
  dst = get_channel_memory(heif_channel_Y, &dst_stride);
960
961
  //int bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8);
962
963
0
  for (uint32_t y = 0; y < height; y++) {
964
0
    for (uint32_t x = 0; x < width; x++) {
965
0
      dst[y * dst_stride + x] = src[y * src_stride + 4 * x + 3];
966
0
    }
967
0
  }
968
969
0
  return Error::Ok;
970
0
}
971
972
973
Error HeifPixelImage::fill_new_channel(heif_channel dst_channel, uint16_t value, int width, int height, int bpp,
974
                                     const heif_security_limits* limits)
975
0
{
976
0
  if (Error err = add_channel(dst_channel, width, height, bpp, limits)) {
977
0
    return err;
978
0
  }
979
980
0
  fill_channel(dst_channel, value);
981
982
0
  return Error::Ok;
983
0
}
984
985
986
void HeifPixelImage::fill_channel(heif_channel dst_channel, uint16_t value)
987
0
{
988
0
  int num_interleaved = num_interleaved_components_per_plane(m_chroma);
989
990
0
  int bpp = get_bits_per_pixel(dst_channel);
991
0
  uint32_t width = get_width(dst_channel);
992
0
  uint32_t height = get_height(dst_channel);
993
994
0
  if (bpp <= 8) {
995
0
    uint8_t* dst;
996
0
    size_t dst_stride = 0;
997
0
    dst = get_channel_memory(dst_channel, &dst_stride);
998
0
    size_t width_bytes = static_cast<size_t>(width) * num_interleaved;
999
1000
0
    for (uint32_t y = 0; y < height; y++) {
1001
0
      memset(dst + y * dst_stride, value, width_bytes);
1002
0
    }
1003
0
  }
1004
0
  else {
1005
0
    uint16_t* dst;
1006
0
    size_t dst_stride = 0;
1007
0
    dst = get_channel_memory<uint16_t>(dst_channel, &dst_stride);
1008
0
    dst_stride /= sizeof(uint16_t);
1009
1010
0
    size_t row_size = static_cast<size_t>(width) * num_interleaved;
1011
0
    for (uint32_t y = 0; y < height; y++) {
1012
0
      for (size_t x = 0; x < row_size; x++) {
1013
0
        dst[y * dst_stride + x] = value;
1014
0
      }
1015
0
    }
1016
0
  }
1017
0
}
1018
1019
1020
void HeifPixelImage::transfer_channel_from_image_as(const std::shared_ptr<HeifPixelImage>& source,
1021
                                                  heif_channel src_channel,
1022
                                                  heif_channel dst_channel)
1023
0
{
1024
  // TODO: check that dst_channel does not exist yet
1025
1026
  // Find and remove the component from source
1027
0
  ComponentStorage plane;
1028
0
  for (auto it = source->m_storage.begin(); it != source->m_storage.end(); ++it) {
1029
0
    if (it->m_channel == src_channel) {
1030
0
      plane = *it;
1031
0
      source->m_storage.erase(it);
1032
0
      break;
1033
0
    }
1034
0
  }
1035
0
  source->m_memory_handle.free(plane.allocation_size);
1036
1037
  // Move the matching ComponentDescription(s) from source to destination.
1038
  // The plane's old ids belong to source's m_next_component_id space and may
1039
  // collide with destination's ids, so we mint fresh ids on destination and
1040
  // rewrite plane.m_component_ids accordingly. Source's descriptions for the
1041
  // moved ids are dropped (the buffer is gone).
1042
0
  std::vector<uint32_t> new_ids;
1043
0
  new_ids.reserve(plane.m_component_ids.size());
1044
0
  for (uint32_t old_id : plane.m_component_ids) {
1045
    // Take the source description (snapshot; the source entry is removed below).
1046
0
    ComponentDescription desc;
1047
0
    if (auto* src_desc = source->find_component_description(old_id)) {
1048
0
      desc = *src_desc;
1049
0
    }
1050
    // Mint a destination id and reset description fields that change on transfer.
1051
0
    desc.component_id = mint_component_id();
1052
0
    desc.channel = dst_channel;
1053
    // Re-derive the cmpd component_type from the new channel so the
1054
    // transferred plane is described as e.g. "alpha" rather than carrying
1055
    // over the source's "monochrome"/"Y" type. (The most common case is
1056
    // moving an alpha aux item's Y plane onto a main image as Alpha.)
1057
0
    auto types = map_channel_to_component_type(dst_channel, heif_chroma_undefined);
1058
0
    if (!types.empty()) {
1059
0
      desc.component_type = types[0];
1060
0
    }
1061
0
    new_ids.push_back(desc.component_id);
1062
0
    add_component_description(std::move(desc));
1063
1064
    // Drop the source's description entry for old_id.
1065
0
    source->remove_component_description(old_id);
1066
0
  }
1067
0
  plane.m_component_ids = std::move(new_ids);
1068
0
  plane.m_channel = dst_channel;
1069
0
  m_storage.push_back(plane);
1070
1071
  // Note: we assume that image planes are never transferred between heif_contexts
1072
0
  m_memory_handle.alloc(plane.allocation_size,
1073
0
                        source->m_memory_handle.get_security_limits(),
1074
0
                        "transferred image data");
1075
0
}
1076
1077
1078
bool is_interleaved_with_alpha(heif_chroma chroma)
1079
2.26k
{
1080
2.26k
  switch (chroma) {
1081
0
    case heif_chroma_undefined:
1082
247
    case heif_chroma_monochrome:
1083
712
    case heif_chroma_420:
1084
918
    case heif_chroma_422:
1085
1.13k
    case heif_chroma_444:
1086
1.70k
    case heif_chroma_interleaved_RGB:
1087
1.70k
    case heif_chroma_interleaved_RRGGBB_BE:
1088
2.26k
    case heif_chroma_interleaved_RRGGBB_LE:
1089
2.26k
      return false;
1090
1091
0
    case heif_chroma_interleaved_RGBA:
1092
0
    case heif_chroma_interleaved_RRGGBBAA_BE:
1093
0
    case heif_chroma_interleaved_RRGGBBAA_LE:
1094
0
      return true;
1095
2.26k
  }
1096
1097
2.26k
  assert(false);
1098
0
  return false;
1099
2.26k
}
1100
1101
1102
Error HeifPixelImage::copy_image_to(const std::shared_ptr<const HeifPixelImage>& source, uint32_t x0, uint32_t y0)
1103
0
{
1104
0
  std::set<enum heif_channel> channels = source->get_channel_set();
1105
1106
0
  uint32_t w = get_width();
1107
0
  uint32_t h = get_height();
1108
0
  heif_chroma chroma = get_chroma_format();
1109
1110
1111
0
  for (heif_channel channel : channels) {
1112
1113
    // The source channel set may contain channels that this image does not
1114
    // have. get_channel_memory() would return nullptr for those, so skip them
1115
    // instead of dereferencing a null pointer.
1116
0
    if (!has_channel(channel)) {
1117
0
      continue;
1118
0
    }
1119
1120
0
    size_t tile_stride;
1121
0
    const uint8_t* tile_data = source->get_channel_memory(channel, &tile_stride);
1122
1123
0
    size_t out_stride;
1124
0
    uint8_t* out_data = get_channel_memory(channel, &out_stride);
1125
1126
0
    if (w <= x0 || h <= y0) {
1127
0
      return {heif_error_Invalid_input,
1128
0
              heif_suberror_Invalid_grid_data};
1129
0
    }
1130
1131
0
    if (source->get_bits_per_pixel(channel) != get_bits_per_pixel(channel)) {
1132
0
      return {heif_error_Invalid_input,
1133
0
              heif_suberror_Wrong_tile_image_pixel_depth};
1134
0
    }
1135
1136
0
    uint32_t src_width = source->get_width(channel);
1137
0
    uint32_t src_height = source->get_height(channel);
1138
1139
0
    uint32_t xs = channel_width(x0, chroma, channel);
1140
0
    uint32_t ys = channel_height(y0, chroma, channel);
1141
1142
    // Compute copy size from actual plane bounds to avoid chroma rounding mismatch.
1143
    // channel_height(y0) + channel_height(h - y0) can exceed channel_height(h) with 4:2:0
1144
    // due to ceiling division, so we use (plane_size - offset) instead.
1145
0
    uint32_t copy_width = std::min(src_width, channel_width(w, chroma, channel) - xs);
1146
0
    uint32_t copy_height = std::min(src_height, channel_height(h, chroma, channel) - ys);
1147
1148
0
    copy_width *= source->get_storage_bits_per_pixel(channel) / 8;
1149
0
    xs *= source->get_storage_bits_per_pixel(channel) / 8;
1150
1151
0
    for (uint32_t py = 0; py < copy_height; py++) {
1152
0
      memcpy(out_data + xs + (ys + py) * out_stride,
1153
0
             tile_data + py * tile_stride,
1154
0
             copy_width);
1155
0
    }
1156
0
  }
1157
1158
0
  return Error::Ok;
1159
0
}
1160
1161
1162
Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::rotate_ccw(int angle_degrees, const heif_security_limits* limits)
1163
522
{
1164
  // TODO: Bayer pattern, polarization patterns and sensor maps reference
1165
  //   image geometry and are currently copied verbatim by
1166
  //   forward_all_metadata_from(). For 90/270° rotations the layout is
1167
  //   transposed and for 180° it is flipped, so the copied metadata is no
1168
  //   longer semantically valid. Either rotate these structures along with
1169
  //   the pixels, or return an error when such metadata is present and
1170
  //   rotation would invalidate it.
1171
1172
  // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation
1173
1174
522
  bool need_conversion = false;
1175
1176
522
  if (get_chroma_format() == heif_chroma_422) {
1177
0
    if (angle_degrees == 90 || angle_degrees == 270) {
1178
0
      need_conversion = true;
1179
0
    }
1180
0
    else if (angle_degrees == 180 && has_odd_height()) {
1181
0
      need_conversion = true;
1182
0
    }
1183
0
  }
1184
522
  else if (get_chroma_format() == heif_chroma_420) {
1185
0
    if (angle_degrees == 90 && has_odd_width()) {
1186
0
      need_conversion = true;
1187
0
    }
1188
0
    else if (angle_degrees == 180 && (has_odd_width() || has_odd_height())) {
1189
0
      need_conversion = true;
1190
0
    }
1191
0
    else if (angle_degrees == 270 && has_odd_height()) {
1192
0
      need_conversion = true;
1193
0
    }
1194
0
  }
1195
1196
522
  if (need_conversion) {
1197
0
    heif_color_conversion_options options{};
1198
0
    heif_color_conversion_options_set_defaults(&options);
1199
1200
0
    auto converted_image_result = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444,
1201
0
                                                     nclx_profile(), // default, undefined
1202
0
                                                     get_bits_per_pixel(heif_channel_Y), options, nullptr, limits);
1203
0
    if (!converted_image_result) {
1204
0
      return converted_image_result.error();
1205
0
    }
1206
1207
0
    return (*converted_image_result)->rotate_ccw(angle_degrees, limits);
1208
0
  }
1209
1210
1211
  // --- create output image
1212
1213
522
  if (angle_degrees == 0) {
1214
34
    return shared_from_this();
1215
34
  }
1216
1217
488
  uint32_t out_width = m_width;
1218
488
  uint32_t out_height = m_height;
1219
1220
488
  if (angle_degrees == 90 || angle_degrees == 270) {
1221
266
    std::swap(out_width, out_height);
1222
266
  }
1223
1224
488
  std::shared_ptr<HeifPixelImage> out_img = std::make_shared<HeifPixelImage>();
1225
488
  out_img->create(out_width, out_height, m_colorspace, m_chroma);
1226
488
  out_img->copy_metadata_from(*this);
1227
1228
1229
  // --- rotate all channels
1230
1231
488
  for (const auto &component: m_storage) {
1232
488
    uint32_t out_plane_width = component.m_width;
1233
488
    uint32_t out_plane_height = component.m_height;
1234
1235
488
    if (angle_degrees == 90 || angle_degrees == 270) {
1236
266
      std::swap(out_plane_width, out_plane_height);
1237
266
    }
1238
1239
488
    ComponentStorage out_component;
1240
488
    out_component.m_channel = component.m_channel;
1241
1242
488
    if (Error err = out_component.alloc(out_plane_width, out_plane_height,
1243
488
                                        component.m_datatype, component.m_bit_depth,
1244
488
                                        component.m_num_interleaved_components,
1245
488
                                        limits, out_img->m_memory_handle)) {
1246
0
      return err;
1247
0
    }
1248
1249
    // Clone per-component metadata (component_type, gimi_content_id, ...)
1250
    // from the source descriptions rather than re-deriving from chroma, so
1251
    // images built via add_component() preserve their original component
1252
    // types and content ids.
1253
488
    std::vector<const ComponentDescription*> src_descs;
1254
488
    src_descs.reserve(component.m_component_ids.size());
1255
488
    for (uint32_t cid : component.m_component_ids) {
1256
488
      src_descs.push_back(find_component_description(cid));
1257
488
    }
1258
488
    out_img->register_component_descriptions(out_component, src_descs);
1259
1260
488
    if (component.m_bit_depth <= 8) {
1261
218
      component.rotate_ccw<uint8_t>(angle_degrees, out_component);
1262
218
    }
1263
270
    else if (component.m_bit_depth <= 16) {
1264
270
      component.rotate_ccw<uint16_t>(angle_degrees, out_component);
1265
270
    }
1266
0
    else if (component.m_bit_depth <= 32) {
1267
0
      component.rotate_ccw<uint32_t>(angle_degrees, out_component);
1268
0
    }
1269
0
    else if (component.m_bit_depth <= 64) {
1270
0
      component.rotate_ccw<uint64_t>(angle_degrees, out_component);
1271
0
    }
1272
0
    else if (component.m_bit_depth <= 128) {
1273
0
      component.rotate_ccw<heif_complex64>(angle_degrees, out_component);
1274
0
    }
1275
0
    else {
1276
0
      std::stringstream sstr;
1277
0
      sstr << "Cannot rotate images with " << component.m_bit_depth << " bits per pixel";
1278
0
      return Error{heif_error_Unsupported_feature,
1279
0
                   heif_suberror_Unspecified,
1280
0
                   sstr.str()};
1281
0
    }
1282
1283
488
    out_img->m_storage.push_back(std::move(out_component));
1284
488
  }
1285
1286
488
  out_img->add_warnings(get_warnings());
1287
1288
488
  return out_img;
1289
488
}
1290
1291
template<typename T>
1292
void HeifPixelImage::ComponentStorage::rotate_ccw(int angle_degrees,
1293
                                            ComponentStorage& out_plane) const
1294
488
{
1295
488
  uint32_t w = m_width;
1296
488
  uint32_t h = m_height;
1297
1298
488
  size_t in_stride = stride / sizeof(T);
1299
488
  const T* in_data = static_cast<const T*>(mem);
1300
1301
488
  size_t out_stride = out_plane.stride / sizeof(T);
1302
488
  T* out_data = static_cast<T*>(out_plane.mem);
1303
1304
488
  if (angle_degrees == 270) {
1305
2.24k
    for (uint32_t x = 0; x < h; x++)
1306
7.48k
      for (uint32_t y = 0; y < w; y++) {
1307
5.38k
        out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y];
1308
5.38k
      }
1309
349
  } else if (angle_degrees == 180) {
1310
1.34k
    for (uint32_t y = 0; y < h; y++)
1311
12.2k
      for (uint32_t x = 0; x < w; x++) {
1312
11.1k
        out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)];
1313
11.1k
      }
1314
222
  } else if (angle_degrees == 90) {
1315
1.66k
    for (uint32_t x = 0; x < h; x++)
1316
5.51k
      for (uint32_t y = 0; y < w; y++) {
1317
3.97k
        out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)];
1318
3.97k
      }
1319
127
  }
1320
488
}
void HeifPixelImage::ComponentStorage::rotate_ccw<unsigned char>(int, HeifPixelImage::ComponentStorage&) const
Line
Count
Source
1294
218
{
1295
218
  uint32_t w = m_width;
1296
218
  uint32_t h = m_height;
1297
1298
218
  size_t in_stride = stride / sizeof(T);
1299
218
  const T* in_data = static_cast<const T*>(mem);
1300
1301
218
  size_t out_stride = out_plane.stride / sizeof(T);
1302
218
  T* out_data = static_cast<T*>(out_plane.mem);
1303
1304
218
  if (angle_degrees == 270) {
1305
975
    for (uint32_t x = 0; x < h; x++)
1306
3.47k
      for (uint32_t y = 0; y < w; y++) {
1307
2.55k
        out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y];
1308
2.55k
      }
1309
162
  } else if (angle_degrees == 180) {
1310
540
    for (uint32_t y = 0; y < h; y++)
1311
7.08k
      for (uint32_t x = 0; x < w; x++) {
1312
6.64k
        out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)];
1313
6.64k
      }
1314
106
  } else if (angle_degrees == 90) {
1315
959
    for (uint32_t x = 0; x < h; x++)
1316
2.75k
      for (uint32_t y = 0; y < w; y++) {
1317
1.85k
        out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)];
1318
1.85k
      }
1319
56
  }
1320
218
}
void HeifPixelImage::ComponentStorage::rotate_ccw<unsigned short>(int, HeifPixelImage::ComponentStorage&) const
Line
Count
Source
1294
270
{
1295
270
  uint32_t w = m_width;
1296
270
  uint32_t h = m_height;
1297
1298
270
  size_t in_stride = stride / sizeof(T);
1299
270
  const T* in_data = static_cast<const T*>(mem);
1300
1301
270
  size_t out_stride = out_plane.stride / sizeof(T);
1302
270
  T* out_data = static_cast<T*>(out_plane.mem);
1303
1304
270
  if (angle_degrees == 270) {
1305
1.26k
    for (uint32_t x = 0; x < h; x++)
1306
4.01k
      for (uint32_t y = 0; y < w; y++) {
1307
2.82k
        out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y];
1308
2.82k
      }
1309
187
  } else if (angle_degrees == 180) {
1310
800
    for (uint32_t y = 0; y < h; y++)
1311
5.20k
      for (uint32_t x = 0; x < w; x++) {
1312
4.51k
        out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)];
1313
4.51k
      }
1314
116
  } else if (angle_degrees == 90) {
1315
707
    for (uint32_t x = 0; x < h; x++)
1316
2.75k
      for (uint32_t y = 0; y < w; y++) {
1317
2.11k
        out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)];
1318
2.11k
      }
1319
71
  }
1320
270
}
Unexecuted instantiation: void HeifPixelImage::ComponentStorage::rotate_ccw<unsigned int>(int, HeifPixelImage::ComponentStorage&) const
Unexecuted instantiation: void HeifPixelImage::ComponentStorage::rotate_ccw<unsigned long>(int, HeifPixelImage::ComponentStorage&) const
Unexecuted instantiation: void HeifPixelImage::ComponentStorage::rotate_ccw<heif_complex64>(int, HeifPixelImage::ComponentStorage&) const
1321
1322
1323
template<typename T>
1324
void HeifPixelImage::ComponentStorage::mirror_inplace(heif_transform_mirror_direction direction)
1325
298
{
1326
298
  uint32_t w = m_width;
1327
298
  uint32_t h = m_height;
1328
1329
298
  T* data = static_cast<T*>(mem);
1330
1331
298
  if (direction == heif_transform_mirror_direction_horizontal) {
1332
7.67k
    for (uint32_t y = 0; y < h; y++) {
1333
11.9k
      for (uint32_t x = 0; x < w / 2; x++)
1334
4.43k
        std::swap(data[y * stride / sizeof(T) + x], data[y * stride / sizeof(T) + w - 1 - x]);
1335
7.56k
    }
1336
185
  } else {
1337
1.89k
    for (uint32_t y = 0; y < h / 2; y++) {
1338
12.7k
      for (uint32_t x = 0; x < w; x++)
1339
11.0k
        std::swap(data[y * stride / sizeof(T) + x], data[(h - 1 - y) * stride / sizeof(T) + x]);
1340
1.71k
    }
1341
185
  }
1342
298
}
void HeifPixelImage::ComponentStorage::mirror_inplace<unsigned char>(heif_transform_mirror_direction)
Line
Count
Source
1325
59
{
1326
59
  uint32_t w = m_width;
1327
59
  uint32_t h = m_height;
1328
1329
59
  T* data = static_cast<T*>(mem);
1330
1331
59
  if (direction == heif_transform_mirror_direction_horizontal) {
1332
2.33k
    for (uint32_t y = 0; y < h; y++) {
1333
4.92k
      for (uint32_t x = 0; x < w / 2; x++)
1334
2.61k
        std::swap(data[y * stride / sizeof(T) + x], data[y * stride / sizeof(T) + w - 1 - x]);
1335
2.30k
    }
1336
31
  } else {
1337
1.16k
    for (uint32_t y = 0; y < h / 2; y++) {
1338
3.59k
      for (uint32_t x = 0; x < w; x++)
1339
2.45k
        std::swap(data[y * stride / sizeof(T) + x], data[(h - 1 - y) * stride / sizeof(T) + x]);
1340
1.13k
    }
1341
28
  }
1342
59
}
void HeifPixelImage::ComponentStorage::mirror_inplace<unsigned short>(heif_transform_mirror_direction)
Line
Count
Source
1325
239
{
1326
239
  uint32_t w = m_width;
1327
239
  uint32_t h = m_height;
1328
1329
239
  T* data = static_cast<T*>(mem);
1330
1331
239
  if (direction == heif_transform_mirror_direction_horizontal) {
1332
5.33k
    for (uint32_t y = 0; y < h; y++) {
1333
7.06k
      for (uint32_t x = 0; x < w / 2; x++)
1334
1.81k
        std::swap(data[y * stride / sizeof(T) + x], data[y * stride / sizeof(T) + w - 1 - x]);
1335
5.25k
    }
1336
157
  } else {
1337
732
    for (uint32_t y = 0; y < h / 2; y++) {
1338
9.20k
      for (uint32_t x = 0; x < w; x++)
1339
8.62k
        std::swap(data[y * stride / sizeof(T) + x], data[(h - 1 - y) * stride / sizeof(T) + x]);
1340
575
    }
1341
157
  }
1342
239
}
Unexecuted instantiation: void HeifPixelImage::ComponentStorage::mirror_inplace<unsigned int>(heif_transform_mirror_direction)
Unexecuted instantiation: void HeifPixelImage::ComponentStorage::mirror_inplace<unsigned long>(heif_transform_mirror_direction)
Unexecuted instantiation: void HeifPixelImage::ComponentStorage::mirror_inplace<heif_complex64>(heif_transform_mirror_direction)
1343
1344
1345
Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::mirror_inplace(heif_transform_mirror_direction direction,
1346
                                                                       const heif_security_limits* limits)
1347
298
{
1348
  // TODO: Bayer pattern, polarization patterns and sensor maps reference
1349
  //   image geometry. This function mirrors the pixel data in place but
1350
  //   leaves those structures untouched, so a horizontal/vertical mirror
1351
  //   leaves them out of sync with the pixel layout. Either mirror these
1352
  //   structures along with the pixels, or return an error when such
1353
  //   metadata is present and the mirror would invalidate it.
1354
1355
  // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation
1356
1357
298
  bool need_conversion = false;
1358
1359
298
  if (get_chroma_format() == heif_chroma_422) {
1360
0
    if (direction == heif_transform_mirror_direction_horizontal && has_odd_width()) {
1361
0
      need_conversion = true;
1362
0
    }
1363
0
  }
1364
298
  else if (get_chroma_format() == heif_chroma_420) {
1365
0
    if (has_odd_width() || has_odd_height()) {
1366
0
      need_conversion = true;
1367
0
    }
1368
0
  }
1369
1370
298
  if (need_conversion) {
1371
0
    heif_color_conversion_options options{};
1372
0
    heif_color_conversion_options_set_defaults(&options);
1373
1374
0
    auto converted_image_result = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444,
1375
0
                                                     nclx_profile(), // default, undefined
1376
0
                                                     get_bits_per_pixel(heif_channel_Y), options, nullptr, limits);
1377
0
    if (!converted_image_result) {
1378
0
      return converted_image_result.error();
1379
0
    }
1380
1381
0
    return (*converted_image_result)->mirror_inplace(direction, limits);
1382
0
  }
1383
1384
1385
298
  for (auto& component : m_storage) {
1386
298
    if (component.m_bit_depth <= 8) {
1387
59
      component.mirror_inplace<uint8_t>(direction);
1388
59
    }
1389
239
    else if (component.m_bit_depth <= 16) {
1390
239
      component.mirror_inplace<uint16_t>(direction);
1391
239
    }
1392
0
    else if (component.m_bit_depth <= 32) {
1393
0
      component.mirror_inplace<uint32_t>(direction);
1394
0
    }
1395
0
    else if (component.m_bit_depth <= 64) {
1396
0
      component.mirror_inplace<uint64_t>(direction);
1397
0
    }
1398
0
    else if (component.m_bit_depth <= 128) {
1399
0
      component.mirror_inplace<heif_complex64>(direction);
1400
0
    }
1401
0
    else {
1402
0
      std::stringstream sstr;
1403
0
      sstr << "Cannot mirror images with " << component.m_bit_depth << " bits per pixel";
1404
0
      return Error{heif_error_Unsupported_feature,
1405
0
                   heif_suberror_Unspecified,
1406
0
                   sstr.str()};
1407
0
    }
1408
298
  }
1409
1410
298
  return shared_from_this();
1411
298
}
1412
1413
1414
int HeifPixelImage::ComponentStorage::get_bytes_per_pixel() const
1415
0
{
1416
0
  return m_bytes_per_pixel;
1417
0
}
1418
1419
1420
Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
1421
                                                             const heif_security_limits* limits) const
1422
0
{
1423
  // TODO: Bayer pattern, polarization patterns and sensor maps reference
1424
  //   image geometry and are currently copied verbatim by
1425
  //   forward_all_metadata_from(). A crop shifts the (0,0) origin and
1426
  //   changes the image dimensions, so the copied metadata may no longer
1427
  //   match the cropped image (e.g. a 2x2 Bayer pattern with an odd
1428
  //   left/top offset, or a sensor NUC map sized to the original image).
1429
  //   Either translate / resample these structures to the crop region, or
1430
  //   return an error when the crop would invalidate them.
1431
1432
  // (left, right, top, bottom) are coordinate endpoints of the kept region.
1433
  // Reject inverted or out-of-bounds rectangles so the unsigned arithmetic
1434
  // below cannot underflow into a multi-GB memcpy (issue #1746).
1435
0
  if (right < left || bottom < top || right >= m_width || bottom >= m_height) {
1436
0
    return Error{heif_error_Usage_error,
1437
0
                 heif_suberror_Invalid_parameter_value,
1438
0
                 "Invalid crop region"};
1439
0
  }
1440
1441
  // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before cropping
1442
1443
0
  bool need_conversion = false;
1444
1445
0
  if (get_chroma_format() == heif_chroma_422 && (left & 1) == 1) {
1446
0
      need_conversion = true;
1447
0
  }
1448
0
  else if (get_chroma_format() == heif_chroma_420 &&
1449
0
           ((left & 1) == 1 || (top & 1) == 1)) {
1450
0
    need_conversion = true;
1451
0
  }
1452
1453
0
  if (need_conversion) {
1454
0
    heif_color_conversion_options options{};
1455
0
    heif_color_conversion_options_set_defaults(&options);
1456
1457
0
    auto converted_image_result = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444,
1458
0
                                                     nclx_profile(), // default, undefined
1459
0
                                                     get_bits_per_pixel(heif_channel_Y), options, nullptr, limits);
1460
1461
0
    if (!converted_image_result) {
1462
0
      return converted_image_result.error();
1463
0
    }
1464
1465
0
    return (*converted_image_result)->crop(left, right, top, bottom, limits);
1466
0
  }
1467
1468
1469
1470
0
  auto out_img = std::make_shared<HeifPixelImage>();
1471
0
  out_img->create(right - left + 1, bottom - top + 1, m_colorspace, m_chroma);
1472
0
  out_img->copy_metadata_from(*this);
1473
1474
1475
  // --- crop all channels
1476
1477
0
  for (const auto& component : m_storage) {
1478
0
    heif_channel channel = component.m_channel;
1479
1480
0
    uint32_t plane_left = get_subsampled_size_h(left, channel, m_chroma, scaling_mode::is_divisible); // is always divisible
1481
0
    uint32_t plane_right = get_subsampled_size_h(right, channel, m_chroma, scaling_mode::round_down); // this keeps enough chroma since 'right' is a coordinate and not the width
1482
0
    uint32_t plane_top = get_subsampled_size_v(top, channel, m_chroma, scaling_mode::is_divisible);
1483
0
    uint32_t plane_bottom = get_subsampled_size_v(bottom, channel, m_chroma, scaling_mode::round_down);
1484
1485
0
    ComponentStorage out_plane;
1486
0
    out_plane.m_channel = channel;
1487
1488
0
    if (Error err = out_plane.alloc(plane_right - plane_left + 1,
1489
0
                                    plane_bottom - plane_top + 1,
1490
0
                                    component.m_datatype, component.m_bit_depth,
1491
0
                                    component.m_num_interleaved_components,
1492
0
                                    limits, out_img->m_memory_handle)) {
1493
0
      return err;
1494
0
    }
1495
1496
    // Clone per-component metadata (component_type, gimi_content_id, ...)
1497
    // from the source descriptions rather than re-deriving from chroma, so
1498
    // images built via add_component() preserve their original component
1499
    // types and content ids.
1500
0
    std::vector<const ComponentDescription*> src_descs;
1501
0
    src_descs.reserve(component.m_component_ids.size());
1502
0
    for (uint32_t cid : component.m_component_ids) {
1503
0
      src_descs.push_back(find_component_description(cid));
1504
0
    }
1505
0
    out_img->register_component_descriptions(out_plane, src_descs);
1506
1507
0
    int bytes_per_pixel = component.get_bytes_per_pixel();
1508
0
    component.crop(plane_left, plane_right, plane_top, plane_bottom, bytes_per_pixel, out_plane);
1509
1510
0
    out_img->m_storage.push_back(std::move(out_plane));
1511
0
  }
1512
1513
0
  out_img->add_warnings(get_warnings());
1514
1515
0
  return out_img;
1516
0
}
1517
1518
1519
void HeifPixelImage::ComponentStorage::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
1520
                                      int bytes_per_pixel, ComponentStorage& out_plane) const
1521
0
{
1522
0
  size_t in_stride = stride;
1523
0
  auto* in_data = static_cast<const uint8_t*>(mem);
1524
1525
0
  size_t out_stride = out_plane.stride;
1526
0
  auto* out_data = static_cast<uint8_t*>(out_plane.mem);
1527
1528
0
  for (uint32_t y = top; y <= bottom; y++) {
1529
0
    memcpy(&out_data[(y - top) * out_stride],
1530
0
           &in_data[y * in_stride + left * bytes_per_pixel],
1531
0
           (right - left + 1) * bytes_per_pixel);
1532
0
  }
1533
0
}
1534
1535
1536
Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_t a)
1537
9
{
1538
36
  for (const auto& channel : {heif_channel_R, heif_channel_G, heif_channel_B, heif_channel_Alpha}) {
1539
1540
36
    auto* comp = find_storage_for_channel(channel);
1541
36
    if (!comp) {
1542
1543
      // alpha channel is optional, R,G,B is required
1544
9
      if (channel == heif_channel_Alpha) {
1545
9
        continue;
1546
9
      }
1547
1548
0
      return {heif_error_Usage_error,
1549
0
              heif_suberror_Nonexisting_image_channel_referenced};
1550
1551
9
    }
1552
1553
27
    ComponentStorage& plane = *comp;
1554
1555
27
    if (plane.m_bit_depth != 8) {
1556
0
      return {heif_error_Unsupported_feature,
1557
0
              heif_suberror_Unspecified,
1558
0
              "Can currently only fill images with 8 bits per pixel"};
1559
0
    }
1560
1561
27
    size_t h = plane.m_height;
1562
1563
27
    size_t stride = plane.stride;
1564
27
    auto* data = static_cast<uint8_t*>(plane.mem);
1565
1566
27
    uint16_t val16;
1567
27
    switch (channel) {
1568
9
      case heif_channel_R:
1569
9
        val16 = r;
1570
9
        break;
1571
9
      case heif_channel_G:
1572
9
        val16 = g;
1573
9
        break;
1574
9
      case heif_channel_B:
1575
9
        val16 = b;
1576
9
        break;
1577
0
      case heif_channel_Alpha:
1578
0
        val16 = a;
1579
0
        break;
1580
0
      default:
1581
        // initialization only to avoid warning of uninitialized variable.
1582
0
        val16 = 0;
1583
        // Should already be detected by the check above ("find_storage_for_channel").
1584
0
        assert(false);
1585
27
    }
1586
1587
27
    auto val8 = static_cast<uint8_t>(val16 >> 8U);
1588
1589
1590
    // memset() even when h * stride > sizeof(size_t)
1591
1592
27
    if (std::numeric_limits<size_t>::max() / stride > h) {
1593
      // can fill in one step
1594
27
      memset(data, val8, stride * h);
1595
27
    }
1596
0
    else {
1597
      // fill line by line
1598
0
      auto* p = data;
1599
1600
0
      for (size_t y=0;y<h;y++) {
1601
0
        memset(p, val8, stride);
1602
0
        p += stride;
1603
0
      }
1604
0
    }
1605
27
  }
1606
1607
9
  return Error::Ok;
1608
9
}
1609
1610
1611
uint32_t negate_negative_int32(int32_t x)
1612
0
{
1613
0
  assert(x <= 0);
1614
1615
0
  if (x == INT32_MIN) {
1616
0
    return static_cast<uint32_t>(INT32_MAX) + 1;
1617
0
  }
1618
0
  else {
1619
0
    return static_cast<uint32_t>(-x);
1620
0
  }
1621
0
}
1622
1623
1624
Error HeifPixelImage::overlay(std::shared_ptr<HeifPixelImage>& overlay, int32_t dx, int32_t dy)
1625
0
{
1626
  // This function places the overlay using the full-resolution (dx,dy) offset
1627
  // directly as a per-plane offset. That is only correct when every plane has
1628
  // the full logical image size, i.e. for non-subsampled chroma formats.
1629
  // Subsampled chroma (4:2:0 / 4:2:2) would be mis-placed and could even write
1630
  // outside of the smaller Cb/Cr planes.
1631
0
  auto has_subsampled_chroma = [](heif_chroma chroma) {
1632
0
    return chroma == heif_chroma_420 || chroma == heif_chroma_422;
1633
0
  };
1634
1635
0
  if (has_subsampled_chroma(get_chroma_format()) ||
1636
0
      has_subsampled_chroma(overlay->get_chroma_format())) {
1637
0
    return {heif_error_Unsupported_feature,
1638
0
            heif_suberror_Unspecified,
1639
0
            "Overlaying images with subsampled chroma is not supported"};
1640
0
  }
1641
1642
0
  std::set<heif_channel> channels = overlay->get_channel_set();
1643
1644
0
  bool has_alpha = overlay->has_channel(heif_channel_Alpha);
1645
  //bool has_alpha_me = has_channel(heif_channel_Alpha);
1646
1647
0
  size_t alpha_stride = 0;
1648
0
  uint8_t* alpha_p;
1649
0
  alpha_p = overlay->get_channel_memory(heif_channel_Alpha, &alpha_stride);
1650
1651
0
  for (heif_channel channel : channels) {
1652
0
    if (!has_channel(channel)) {
1653
0
      continue;
1654
0
    }
1655
1656
0
    size_t in_stride = 0;
1657
0
    const uint8_t* in_p;
1658
1659
0
    size_t out_stride = 0;
1660
0
    uint8_t* out_p;
1661
1662
0
    in_p = overlay->get_channel_memory(channel, &in_stride);
1663
0
    out_p = get_channel_memory(channel, &out_stride);
1664
1665
0
    uint32_t in_w = overlay->get_width(channel);
1666
0
    uint32_t in_h = overlay->get_height(channel);
1667
1668
0
    uint32_t out_w = get_width(channel);
1669
0
    uint32_t out_h = get_height(channel);
1670
1671
1672
    // --- check whether overlay image overlaps with current image
1673
    // Note: all components share the logical image size, so if the overlay
1674
    // image lies completely outside for one component it does so for all of
1675
    // them -> we can return instead of just skipping the current component.
1676
1677
0
    if (dx > 0 && static_cast<uint32_t>(dx) >= out_w) {
1678
      // the overlay image is completely outside the right border -> skip overlaying
1679
0
      return Error::Ok;
1680
0
    }
1681
0
    else if (dx < 0 && in_w <= negate_negative_int32(dx)) {
1682
      // the overlay image is completely outside the left border -> skip overlaying
1683
0
      return Error::Ok;
1684
0
    }
1685
1686
0
    if (dy > 0 && static_cast<uint32_t>(dy) >= out_h) {
1687
      // the overlay image is completely outside the bottom border -> skip overlaying
1688
0
      return Error::Ok;
1689
0
    }
1690
0
    else if (dy < 0 && in_h <= negate_negative_int32(dy)) {
1691
      // the overlay image is completely outside the top border -> skip overlaying
1692
0
      return Error::Ok;
1693
0
    }
1694
1695
1696
    // --- compute overlapping area
1697
1698
    // top-left points where to start copying in source and destination
1699
0
    uint32_t in_x0;
1700
0
    uint32_t in_y0;
1701
0
    uint32_t out_x0;
1702
0
    uint32_t out_y0;
1703
1704
    // right border
1705
0
    if (dx + static_cast<int64_t>(in_w) > out_w) {
1706
      // overlay image extends partially outside of right border
1707
      // Notes:
1708
      // - (out_w-dx) cannot underflow because dx<out_w is ensured above
1709
      // - (out_w-dx) cannot overflow (for dx<0) because, as just checked, out_w-dx < in_w
1710
      //              and in_w fits into uint32_t
1711
0
      in_w = static_cast<uint32_t>(static_cast<int64_t>(out_w) - dx);
1712
0
    }
1713
1714
    // bottom border
1715
0
    if (dy + static_cast<int64_t>(in_h) > out_h) {
1716
      // overlay image extends partially outside of bottom border
1717
0
      in_h = static_cast<uint32_t>(static_cast<int64_t>(out_h) - dy);
1718
0
    }
1719
1720
    // left border
1721
0
    if (dx < 0) {
1722
      // overlay image starts partially outside of left border
1723
1724
0
      in_x0 = negate_negative_int32(dx);
1725
0
      out_x0 = 0;
1726
0
      in_w = in_w - in_x0; // in_x0 < in_w because in_w > -dx = in_x0
1727
0
    }
1728
0
    else {
1729
0
      in_x0 = 0;
1730
0
      out_x0 = static_cast<uint32_t>(dx);
1731
0
    }
1732
1733
    // top border
1734
0
    if (dy < 0) {
1735
      // overlay image started partially outside of top border
1736
1737
0
      in_y0 = negate_negative_int32(dy);
1738
0
      out_y0 = 0;
1739
0
      in_h = in_h - in_y0; // in_y0 < in_h because in_h > -dy = in_y0
1740
0
    }
1741
0
    else {
1742
0
      in_y0 = 0;
1743
0
      out_y0 = static_cast<uint32_t>(dy);
1744
0
    }
1745
1746
    // --- computer overlay in overlapping area
1747
1748
0
    for (uint32_t y = in_y0; y < in_h; y++) {
1749
0
      if (!has_alpha) {
1750
0
        memcpy(out_p + out_x0 + (out_y0 + y - in_y0) * out_stride,
1751
0
               in_p + in_x0 + y * in_stride,
1752
0
               in_w);
1753
0
      }
1754
0
      else {
1755
0
        for (uint32_t x = in_x0; x < in_w; x++) {
1756
0
          uint8_t* outptr = &out_p[out_x0 + (out_y0 + y - in_y0) * out_stride + x];
1757
0
          uint8_t in_val = in_p[in_x0 + y * in_stride + x];
1758
0
          uint8_t alpha_val = alpha_p[in_x0 + y * alpha_stride + x];
1759
1760
0
          *outptr = (uint8_t) ((in_val * alpha_val + *outptr * (255 - alpha_val)) / 255);
1761
0
        }
1762
0
      }
1763
0
    }
1764
0
  }
1765
1766
0
  return Error::Ok;
1767
0
}
1768
1769
1770
Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr<HeifPixelImage>& out_img,
1771
                                             uint32_t width, uint32_t height,
1772
                                             const heif_security_limits* limits) const
1773
0
{
1774
0
  out_img = std::make_shared<HeifPixelImage>();
1775
0
  out_img->create(width, height, m_colorspace, m_chroma);
1776
1777
1778
  // --- create output image with scaled planes
1779
1780
0
  if (has_channel(heif_channel_interleaved)) {
1781
0
    if (auto err = out_img->add_channel(heif_channel_interleaved, width, height, get_bits_per_pixel(heif_channel_interleaved), limits)) {
1782
0
      return err;
1783
0
    }
1784
0
  }
1785
0
  else {
1786
0
    if (get_colorspace() == heif_colorspace_RGB) {
1787
0
      if (!has_channel(heif_channel_R) ||
1788
0
          !has_channel(heif_channel_G) ||
1789
0
          !has_channel(heif_channel_B)) {
1790
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "RGB input without R,G,B, planes"};
1791
0
      }
1792
1793
0
      if (auto err = out_img->add_channel(heif_channel_R, width, height, get_bits_per_pixel(heif_channel_R), limits)) {
1794
0
        return err;
1795
0
      }
1796
0
      if (auto err = out_img->add_channel(heif_channel_G, width, height, get_bits_per_pixel(heif_channel_G), limits)) {
1797
0
        return err;
1798
0
      }
1799
0
      if (auto err = out_img->add_channel(heif_channel_B, width, height, get_bits_per_pixel(heif_channel_B), limits)) {
1800
0
        return err;
1801
0
      }
1802
0
    }
1803
0
    else if (get_colorspace() == heif_colorspace_monochrome) {
1804
0
      if (!has_channel(heif_channel_Y)) {
1805
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "monochrome input with no Y plane"};
1806
0
      }
1807
1808
0
      if (auto err = out_img->add_channel(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y), limits)) {
1809
0
        return err;
1810
0
      }
1811
0
    }
1812
0
    else if (get_colorspace() == heif_colorspace_YCbCr) {
1813
0
      if (!has_channel(heif_channel_Y) ||
1814
0
          !has_channel(heif_channel_Cb) ||
1815
0
          !has_channel(heif_channel_Cr)) {
1816
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "YCbCr image without Y,Cb,Cr planes"};
1817
0
      }
1818
1819
0
      uint32_t cw, ch;
1820
0
      get_subsampled_size(width, height, heif_channel_Cb, get_chroma_format(), &cw, &ch);
1821
0
      if (auto err = out_img->add_channel(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y), limits)) {
1822
0
        return err;
1823
0
      }
1824
0
      if (auto err = out_img->add_channel(heif_channel_Cb, cw, ch, get_bits_per_pixel(heif_channel_Cb), limits)) {
1825
0
        return err;
1826
0
      }
1827
0
      if (auto err = out_img->add_channel(heif_channel_Cr, cw, ch, get_bits_per_pixel(heif_channel_Cr), limits)) {
1828
0
        return err;
1829
0
      }
1830
0
    }
1831
0
    else {
1832
0
      return {heif_error_Invalid_input, heif_suberror_Unspecified, "unknown color configuration"};
1833
0
    }
1834
1835
0
    if (has_channel(heif_channel_Alpha)) {
1836
0
      if (auto err = out_img->add_channel(heif_channel_Alpha, width, height, get_bits_per_pixel(heif_channel_Alpha), limits)) {
1837
0
        return err;
1838
0
      }
1839
0
    }
1840
0
  }
1841
1842
1843
  // --- scale all channels
1844
1845
0
  int nInterleaved = num_interleaved_components_per_plane(m_chroma);
1846
0
  if (nInterleaved > 1) {
1847
0
    const auto* comp = find_storage_for_channel(heif_channel_interleaved);
1848
0
    assert(comp != nullptr); // the plane must exist since we have an interleaved chroma format
1849
0
    const ComponentStorage& plane = *comp;
1850
1851
0
    uint32_t out_w = out_img->get_width(heif_channel_interleaved);
1852
0
    uint32_t out_h = out_img->get_height(heif_channel_interleaved);
1853
1854
0
    if (plane.m_bit_depth <= 8) {
1855
      // SDR interleaved
1856
1857
0
      size_t in_stride = plane.stride;
1858
0
      const auto* in_data = static_cast<const uint8_t*>(plane.mem);
1859
1860
0
      size_t out_stride = 0;
1861
0
      auto* out_data = out_img->get_channel_memory(heif_channel_interleaved, &out_stride);
1862
1863
0
      for (uint32_t y = 0; y < out_h; y++) {
1864
0
        uint32_t iy = static_cast<uint32_t>(static_cast<uint64_t>(y) * m_height / height);
1865
1866
0
        for (uint32_t x = 0; x < out_w; x++) {
1867
0
          uint32_t ix = static_cast<uint32_t>(static_cast<uint64_t>(x) * m_width / width);
1868
1869
0
          for (int c = 0; c < nInterleaved; c++) {
1870
0
            out_data[y * out_stride + x * nInterleaved + c] = in_data[iy * in_stride + ix * nInterleaved + c];
1871
0
          }
1872
0
        }
1873
0
      }
1874
0
    }
1875
0
    else {
1876
      // HDR interleaved
1877
      // TODO: untested
1878
1879
0
      size_t in_stride = plane.stride;
1880
0
      const uint16_t* in_data = static_cast<const uint16_t*>(plane.mem);
1881
1882
0
      size_t out_stride = 0;
1883
0
      uint16_t* out_data = out_img->get_channel_memory<uint16_t>(heif_channel_interleaved, &out_stride);
1884
1885
0
      in_stride /= 2;
1886
0
      out_stride /= 2;
1887
1888
0
      for (uint32_t y = 0; y < out_h; y++) {
1889
0
        uint32_t iy = static_cast<uint32_t>(static_cast<uint64_t>(y) * m_height / height);
1890
1891
0
        for (uint32_t x = 0; x < out_w; x++) {
1892
0
          uint32_t ix = static_cast<uint32_t>(static_cast<uint64_t>(x) * m_width / width);
1893
1894
0
          for (int c = 0; c < nInterleaved; c++) {
1895
0
            out_data[y * out_stride + x * nInterleaved + c] = in_data[iy * in_stride + ix * nInterleaved + c];
1896
0
          }
1897
0
        }
1898
0
      }
1899
0
    }
1900
0
  }
1901
0
  else {
1902
0
    for (const auto& component : m_storage) {
1903
0
      heif_channel channel = component.m_channel;
1904
0
      const ComponentStorage& plane = component;
1905
1906
0
      if (!out_img->has_channel(channel)) {
1907
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "scaling input has extra color plane"};
1908
0
      }
1909
1910
1911
0
      uint32_t out_w = out_img->get_width(channel);
1912
0
      uint32_t out_h = out_img->get_height(channel);
1913
1914
0
      if (plane.m_bit_depth <= 8) {
1915
        // SDR planar
1916
1917
0
        size_t in_stride = plane.stride;
1918
0
        const auto* in_data = static_cast<const uint8_t*>(plane.mem);
1919
1920
0
        size_t out_stride = 0;
1921
0
        auto* out_data = out_img->get_channel_memory(channel, &out_stride);
1922
1923
0
        for (uint32_t y = 0; y < out_h; y++) {
1924
0
          uint32_t iy = static_cast<uint32_t>(static_cast<uint64_t>(y) * m_height / height);
1925
1926
0
          for (uint32_t x = 0; x < out_w; x++) {
1927
0
            uint32_t ix = static_cast<uint32_t>(static_cast<uint64_t>(x) * m_width / width);
1928
1929
0
            out_data[y * out_stride + x] = in_data[iy * in_stride + ix];
1930
0
          }
1931
0
        }
1932
0
      }
1933
0
      else {
1934
        // HDR planar
1935
1936
0
        size_t in_stride = plane.stride;
1937
0
        const uint16_t* in_data = static_cast<const uint16_t*>(plane.mem);
1938
1939
0
        size_t out_stride = 0;
1940
0
        uint16_t* out_data = out_img->get_channel_memory<uint16_t>(channel, &out_stride);
1941
1942
0
        in_stride /= 2;
1943
0
        out_stride /= 2;
1944
1945
0
        for (uint32_t y = 0; y < out_h; y++) {
1946
0
          uint32_t iy = static_cast<uint32_t>(static_cast<uint64_t>(y) * m_height / height);
1947
1948
0
          for (uint32_t x = 0; x < out_w; x++) {
1949
0
            uint32_t ix = static_cast<uint32_t>(static_cast<uint64_t>(x) * m_width / width);
1950
1951
0
            out_data[y * out_stride + x] = in_data[iy * in_stride + ix];
1952
0
          }
1953
0
        }
1954
0
      }
1955
0
    }
1956
0
  }
1957
1958
0
  return Error::Ok;
1959
0
}
1960
1961
1962
void HeifPixelImage::debug_dump() const
1963
0
{
1964
0
  auto channels = get_channel_set();
1965
0
  for (auto c : channels) {
1966
0
    size_t stride = 0;
1967
0
    const uint8_t* p = get_channel_memory(c, &stride);
1968
1969
    // clamp the dump region to the actual plane size to avoid reading past it
1970
0
    uint32_t dump_w = std::min(get_width(c), 8u);
1971
0
    uint32_t dump_h = std::min(get_height(c), 8u);
1972
1973
0
    for (uint32_t y = 0; y < dump_h; y++) {
1974
0
      for (uint32_t x = 0; x < dump_w; x++) {
1975
0
        printf("%02x ", p[y * stride + x]);
1976
0
      }
1977
0
      printf("\n");
1978
0
    }
1979
0
  }
1980
0
}
1981
1982
Error HeifPixelImage::create_clone_image_at_new_size(const std::shared_ptr<const HeifPixelImage>& source, uint32_t w, uint32_t h,
1983
                                                     const heif_security_limits* limits)
1984
0
{
1985
0
  heif_colorspace colorspace = source->get_colorspace();
1986
0
  heif_chroma chroma = source->get_chroma_format();
1987
1988
0
  create(w, h, colorspace, chroma);
1989
1990
0
  for (const auto& src_plane : source->m_storage) {
1991
    // TODO: do we also support images where some planes (e.g. the alpha-plane) have a different size than the main image?
1992
    //       We could do this by scaling all planes proportionally. This would also handle chroma channels implicitly.
1993
0
    uint32_t plane_w = channel_width(w, chroma, src_plane.m_channel);
1994
0
    uint32_t plane_h = channel_height(h, chroma, src_plane.m_channel);
1995
1996
0
    ComponentStorage plane;
1997
0
    plane.m_channel = src_plane.m_channel;
1998
0
    plane.m_component_ids = src_plane.m_component_ids;
1999
2000
0
    if (auto err = plane.alloc(plane_w, plane_h, src_plane.m_datatype, src_plane.m_bit_depth,
2001
0
                               src_plane.m_num_interleaved_components, limits, m_memory_handle)) {
2002
0
      return err;
2003
0
    }
2004
2005
0
    m_storage.push_back(plane);
2006
0
  }
2007
2008
  // The source's descriptions carry the source's geometry; the planes above
2009
  // were allocated at the new (w,h) size, so descriptions must be resized to
2010
  // match — otherwise get_component_width/height returns stale source dims.
2011
0
  auto descs = source->get_component_descriptions();
2012
0
  for (auto& desc : descs) {
2013
0
    desc.width = channel_width(w, chroma, desc.channel);
2014
0
    desc.height = channel_height(h, chroma, desc.channel);
2015
0
  }
2016
0
  set_component_descriptions(std::move(descs), source->peek_next_component_id());
2017
2018
0
  copy_metadata_from(*source);
2019
2020
0
  return Error::Ok;
2021
0
}
2022
2023
2024
Result<std::shared_ptr<HeifPixelImage>>
2025
HeifPixelImage::extract_image_area(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h,
2026
                                   const heif_security_limits* limits) const
2027
0
{
2028
  // The top-left corner must lie inside the image. Without this check,
2029
  // get_width() - x0 (and the per-channel offsets derived from x0/y0) would
2030
  // underflow and the copy loop below would read far outside the source planes.
2031
0
  if (x0 >= get_width() || y0 >= get_height()) {
2032
0
    return Error{heif_error_Usage_error,
2033
0
                 heif_suberror_Invalid_parameter_value,
2034
0
                 "extract_image_area: top-left position is outside the image"};
2035
0
  }
2036
2037
0
  uint32_t minW = std::min(w, get_width() - x0);
2038
0
  uint32_t minH = std::min(h, get_height() - y0);
2039
2040
0
  auto areaImg = std::make_shared<HeifPixelImage>();
2041
0
  Error err = areaImg->create_clone_image_at_new_size(shared_from_this(), minW, minH, limits);
2042
0
  if (err) {
2043
0
    return err;
2044
0
  }
2045
2046
0
  std::set<enum heif_channel> channels = get_channel_set();
2047
0
  heif_chroma chroma = get_chroma_format();
2048
2049
0
  for (heif_channel channel : channels) {
2050
2051
0
    size_t src_stride;
2052
0
    const uint8_t* src_data = get_channel_memory(channel, &src_stride);
2053
2054
0
    size_t out_stride;
2055
0
    uint8_t* out_data = areaImg->get_channel_memory(channel, &out_stride);
2056
2057
0
    if (areaImg->get_bits_per_pixel(channel) != get_bits_per_pixel(channel)) {
2058
0
      return Error{
2059
0
        heif_error_Invalid_input,
2060
0
        heif_suberror_Wrong_tile_image_pixel_depth
2061
0
      };
2062
0
    }
2063
2064
0
    uint32_t xs = channel_width(x0, chroma, channel);
2065
0
    uint32_t ys = channel_height(y0, chroma, channel);
2066
2067
    // Clamp copy size to source plane bounds to avoid chroma rounding mismatch OOB read.
2068
0
    uint32_t src_plane_h = channel_height(get_height(), chroma, channel);
2069
0
    uint32_t src_plane_w = channel_width(get_width(), chroma, channel);
2070
0
    uint32_t copy_width = std::min(channel_width(minW, chroma, channel), src_plane_w - xs);
2071
0
    uint32_t copy_height = std::min(channel_height(minH, chroma, channel), src_plane_h - ys);
2072
2073
0
    copy_width *= get_storage_bits_per_pixel(channel) / 8;
2074
0
    xs *= get_storage_bits_per_pixel(channel) / 8;
2075
2076
0
    for (uint32_t py = 0; py < copy_height; py++) {
2077
0
      memcpy(out_data + py * out_stride,
2078
0
             src_data + xs + (ys + py) * src_stride,
2079
0
             copy_width);
2080
0
    }
2081
0
  }
2082
2083
0
  err = areaImg->extend_to_size_with_zero(w,h,limits);
2084
0
  if (err) {
2085
0
    return err;
2086
0
  }
2087
2088
0
  return areaImg;
2089
0
}
2090
2091
2092
// --- index-based component access methods
2093
2094
HeifPixelImage::ComponentStorage* HeifPixelImage::find_storage_for_component(uint32_t component_id)
2095
0
{
2096
0
  for (auto& plane : m_storage) {
2097
    // we search through all indices in case we have an interleaved plane
2098
0
    if (std::find(plane.m_component_ids.begin(),
2099
0
                  plane.m_component_ids.end(),
2100
0
                  component_id) != plane.m_component_ids.end()) {
2101
0
      return &plane;
2102
0
    }
2103
0
  }
2104
0
  return nullptr;
2105
0
}
2106
2107
2108
const HeifPixelImage::ComponentStorage* HeifPixelImage::find_storage_for_component(uint32_t component_id) const
2109
0
{
2110
0
  return const_cast<HeifPixelImage*>(this)->find_storage_for_component(component_id);
2111
0
}
2112
2113
2114
heif_channel HeifPixelImage::get_component_channel(uint32_t component_id) const
2115
0
{
2116
0
  auto* desc = find_component_description(component_id);
2117
0
  assert(desc);
2118
0
  return desc->channel;
2119
0
}
2120
2121
2122
uint32_t HeifPixelImage::get_component_width(uint32_t component_id) const
2123
0
{
2124
0
  auto* desc = find_component_description(component_id);
2125
0
  assert(desc);
2126
0
  return desc->width;
2127
0
}
2128
2129
2130
uint32_t HeifPixelImage::get_component_height(uint32_t component_id) const
2131
0
{
2132
0
  auto* desc = find_component_description(component_id);
2133
0
  assert(desc);
2134
0
  return desc->height;
2135
0
}
2136
2137
2138
uint16_t HeifPixelImage::get_component_bits_per_pixel(uint32_t component_id) const
2139
0
{
2140
0
  auto* desc = find_component_description(component_id);
2141
0
  assert(desc);
2142
0
  return desc->bit_depth;
2143
0
}
2144
2145
2146
uint16_t HeifPixelImage::get_component_storage_bits_per_pixel(uint32_t component_id) const
2147
0
{
2148
  // Storage is a buffer-layout concern (alignment / padding), so this stays
2149
  // routed through ComponentStorage rather than the description.
2150
0
  auto* comp = find_storage_for_component(component_id);
2151
0
  assert(comp);
2152
0
  uint32_t bpp = comp->get_bytes_per_pixel() * 8;
2153
0
  assert(bpp);
2154
0
  return static_cast<uint16_t>(bpp);
2155
0
}
2156
2157
2158
heif_component_datatype HeifPixelImage::get_component_datatype(uint32_t component_id) const
2159
0
{
2160
0
  auto* desc = find_component_description(component_id);
2161
0
  assert(desc);
2162
0
  return desc->datatype;
2163
0
}
2164
2165
2166
uint16_t HeifPixelImage::get_component_type(uint32_t component_id) const
2167
0
{
2168
0
  if (const auto* desc = find_component_description(component_id)) {
2169
0
    return desc->component_type;
2170
0
  }
2171
0
  return heif_cmpd_component_type_UNDEFINED;
2172
0
}
2173
2174
2175
std::vector<uint32_t> HeifPixelImage::get_component_ids_interleaved() const
2176
0
{
2177
0
  const ComponentStorage* comp = find_storage_for_channel(heif_channel_interleaved);
2178
0
  assert(comp);
2179
0
  return comp->m_component_ids;
2180
0
}
2181
2182
2183
Result<uint32_t> HeifPixelImage::add_component(uint32_t width, uint32_t height,
2184
                                               uint16_t component_type,
2185
                                               heif_component_datatype datatype, int bit_depth,
2186
                                               const heif_security_limits* limits)
2187
0
{
2188
0
  heif_channel channel = map_uncompressed_component_to_channel(component_type);
2189
2190
0
  ComponentStorage plane;
2191
0
  plane.m_channel = channel;
2192
2193
0
  if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
2194
0
    return {err};
2195
0
  }
2196
2197
0
  register_component_descriptions(plane, std::vector<uint16_t>{component_type});
2198
0
  uint32_t component_id = plane.m_component_ids.front();
2199
0
  m_storage.push_back(std::move(plane));
2200
0
  return component_id;
2201
0
}
2202
2203
2204
uint32_t HeifPixelImage::add_component_without_data(uint16_t component_type)
2205
0
{
2206
0
  uint32_t new_component_id = mint_component_id();
2207
2208
0
  ComponentDescription desc;
2209
0
  desc.component_id = new_component_id;
2210
0
  desc.channel = map_uncompressed_component_to_channel(component_type);
2211
0
  desc.component_type = component_type;
2212
0
  desc.has_data_plane = false;
2213
0
  add_component_description(std::move(desc));
2214
2215
0
  return new_component_id;
2216
0
}
2217
2218
2219
void HeifPixelImage::clone_component_descriptions_from(const ImageDescription& src)
2220
0
{
2221
0
  set_component_descriptions(src.get_component_descriptions(),
2222
0
                             src.peek_next_component_id());
2223
0
}
2224
2225
2226
void HeifPixelImage::apply_descriptions_from(const ImageDescription& src)
2227
1.13k
{
2228
1.13k
  const auto& src_descs = src.get_component_descriptions();
2229
1.13k
  if (src_descs.empty()) {
2230
247
    return; // nothing to apply (e.g. grid/iden ImageItem with no description)
2231
247
  }
2232
2233
  // Skip when this image's descriptions already match src's exactly. This
2234
  // is the unci decode path: the decoder used clone_component_descriptions_from
2235
  // (item) so the full description list (including any cpat reference-only
2236
  // entries with has_data_plane=false) was copied verbatim. Comparing the
2237
  // full lists also handles multiple planes that share a channel
2238
  // (e.g. unci multi-component-of-same-type), which the channel-keyed remap
2239
  // below can't represent.
2240
883
  const auto& my_descs = get_component_descriptions();
2241
883
  if (my_descs.size() == src_descs.size()) {
2242
133
    bool already_aligned = true;
2243
532
    for (size_t i = 0; i < src_descs.size(); i++) {
2244
399
      if (my_descs[i].component_id != src_descs[i].component_id ||
2245
399
          my_descs[i].channel != src_descs[i].channel) {
2246
0
        already_aligned = false;
2247
0
        break;
2248
0
      }
2249
399
    }
2250
133
    if (already_aligned) {
2251
133
      return;
2252
133
    }
2253
133
  }
2254
2255
  // Snapshot pre-remap descriptions keyed by channel (for any "extra"
2256
  // channels not in src that we need to keep, like alpha-from-aux).
2257
750
  std::map<heif_channel, ComponentDescription> auto_minted_by_channel;
2258
2.25k
  for (const auto& d : my_descs) {
2259
2.25k
    auto_minted_by_channel[d.channel] = d;
2260
2.25k
  }
2261
2262
  // Build a channel -> actual-plane-dimensions map. For tile decodes the
2263
  // src description carries full-image dims, but the decoded plane was
2264
  // allocated at tile size; the description we publish should match what
2265
  // the buffer actually contains.
2266
750
  std::map<heif_channel, std::pair<uint32_t, uint32_t>> plane_dims_by_channel;
2267
2.25k
  for (const auto& plane : m_storage) {
2268
2.25k
    plane_dims_by_channel[plane.m_channel] = {plane.m_width, plane.m_height};
2269
2.25k
  }
2270
2271
  // Build the new component list from src's data-plane descriptions and a
2272
  // channel -> src-id map.
2273
750
  std::vector<ComponentDescription> new_components;
2274
750
  std::map<heif_channel, uint32_t> src_id_by_channel;
2275
750
  for (const auto& d : src_descs) {
2276
750
    if (d.has_data_plane) {
2277
750
      ComponentDescription copy = d;
2278
750
      auto it = plane_dims_by_channel.find(d.channel);
2279
750
      if (it != plane_dims_by_channel.end()) {
2280
750
        copy.width = it->second.first;
2281
750
        copy.height = it->second.second;
2282
750
      }
2283
750
      new_components.push_back(copy);
2284
750
      src_id_by_channel[d.channel] = d.component_id;
2285
750
    }
2286
750
  }
2287
2288
  // Compute a starting id for any extras (above src's high-water mark).
2289
750
  uint32_t next_id = src.peek_next_component_id();
2290
750
  for (const auto& d : new_components) {
2291
750
    if (d.component_id >= next_id) next_id = d.component_id + 1;
2292
750
  }
2293
2294
  // Remap each plane's m_component_ids by channel match against src; for
2295
  // channels not in src, re-add the auto-minted description with a fresh id.
2296
2.25k
  for (auto& plane : m_storage) {
2297
2.25k
    if (plane.m_component_ids.empty()) continue;
2298
2299
2.25k
    heif_channel ch = plane.m_channel;
2300
2.25k
    auto src_it = src_id_by_channel.find(ch);
2301
2.25k
    if (src_it != src_id_by_channel.end()) {
2302
750
      plane.m_component_ids.assign(1, src_it->second);
2303
1.50k
    } else {
2304
1.50k
      auto auto_it = auto_minted_by_channel.find(ch);
2305
1.50k
      if (auto_it != auto_minted_by_channel.end()) {
2306
1.50k
        ComponentDescription extra = auto_it->second;
2307
1.50k
        extra.component_id = next_id++;
2308
1.50k
        new_components.push_back(extra);
2309
1.50k
        plane.m_component_ids.assign(1, extra.component_id);
2310
1.50k
      }
2311
1.50k
    }
2312
2.25k
  }
2313
2314
750
  set_component_descriptions(std::move(new_components), next_id);
2315
750
}
2316
2317
2318
Error HeifPixelImage::allocate_buffer_for_component(uint32_t component_id,
2319
                                                    const heif_security_limits* limits)
2320
0
{
2321
0
  auto* desc = find_component_description(component_id);
2322
0
  if (!desc) {
2323
0
    return {heif_error_Usage_error,
2324
0
            heif_suberror_Invalid_parameter_value,
2325
0
            "allocate_buffer_for_component: unknown component id"};
2326
0
  }
2327
0
  if (!desc->has_data_plane) {
2328
0
    return Error::Ok; // reference component (e.g. cpat); no buffer needed
2329
0
  }
2330
2331
0
  ComponentStorage plane;
2332
0
  plane.m_channel = desc->channel;
2333
0
  plane.m_component_ids = std::vector{component_id};
2334
0
  if (Error err = plane.alloc(desc->width, desc->height,
2335
0
                              desc->datatype, desc->bit_depth,
2336
0
                              1, limits, m_memory_handle)) {
2337
0
    return err;
2338
0
  }
2339
0
  m_storage.push_back(plane);
2340
0
  return Error::Ok;
2341
0
}
2342
2343
2344
#if 0
2345
Result<uint32_t> HeifPixelImage::add_component_for_index(uint32_t component_index,
2346
                                                          uint32_t width, uint32_t height,
2347
                                                          heif_component_datatype datatype, int bit_depth,
2348
                                                          const heif_security_limits* limits)
2349
{
2350
  if (component_index >= m_cmpd_component_types.size()) {
2351
    return Error{heif_error_Usage_error, heif_suberror_Invalid_parameter_value,
2352
                 "component_index out of range of cmpd table"};
2353
  }
2354
2355
  uint16_t component_type = m_cmpd_component_types[component_index];
2356
2357
  ComponentStorage plane;
2358
  plane.m_channel = map_uncompressed_component_to_channel(component_type);
2359
  plane.m_component_index = std::vector{component_index};
2360
  if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
2361
    return err;
2362
  }
2363
2364
  m_storage.push_back(plane);
2365
  return component_index;
2366
}
2367
#endif
2368
2369
2370
std::vector<uint32_t> HeifPixelImage::get_used_component_ids() const
2371
0
{
2372
0
  const auto& descs = get_component_descriptions();
2373
0
  std::vector<uint32_t> indices;
2374
0
  indices.reserve(descs.size());
2375
2376
0
  for (const auto& desc : descs) {
2377
0
    indices.push_back(desc.component_id);
2378
0
  }
2379
2380
0
  return indices;
2381
0
}
2382
2383
2384
std::vector<uint32_t> HeifPixelImage::get_used_planar_component_ids() const
2385
0
{
2386
0
  std::vector<uint32_t> indices;
2387
2388
0
  for (const auto& plane : m_storage) {
2389
0
    if (plane.m_component_ids.size() == 1) {
2390
0
      indices.push_back(plane.m_component_ids[0]);
2391
0
    }
2392
0
  }
2393
2394
0
  return indices;
2395
0
}
2396
2397
2398
uint8_t* HeifPixelImage::get_component(uint32_t component_id, size_t* out_stride)
2399
0
{
2400
0
  return get_component_memory<uint8_t>(component_id, out_stride);
2401
0
}
2402
2403
2404
const uint8_t* HeifPixelImage::get_component(uint32_t component_id, size_t* out_stride) const
2405
0
{
2406
0
  return get_component_memory<uint8_t>(component_id, out_stride);
2407
0
}