Coverage Report

Created: 2025-07-23 08:18

/src/libheif/libheif/pixelimage.cc
Line
Count
Source (jump to first uncovered line)
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 <cstring>
28
#include <utility>
29
#include <limits>
30
#include <algorithm>
31
#include <color-conversion/colorconversion.h>
32
33
34
heif_chroma chroma_from_subsampling(int h, int v)
35
0
{
36
0
  if (h == 2 && v == 2) {
37
0
    return heif_chroma_420;
38
0
  }
39
0
  else if (h == 2 && v == 1) {
40
0
    return heif_chroma_422;
41
0
  }
42
0
  else if (h == 1 && v == 1) {
43
0
    return heif_chroma_444;
44
0
  }
45
0
  else {
46
0
    assert(false);
47
0
    return heif_chroma_undefined;
48
0
  }
49
0
}
50
51
52
uint32_t chroma_width(uint32_t w, heif_chroma chroma)
53
0
{
54
0
  switch (chroma) {
55
0
    case heif_chroma_420:
56
0
    case heif_chroma_422:
57
0
      return (w+1)/2;
58
0
    default:
59
0
      return w;
60
0
  }
61
0
}
62
63
uint32_t chroma_height(uint32_t h, heif_chroma chroma)
64
0
{
65
0
  switch (chroma) {
66
0
    case heif_chroma_420:
67
0
      return (h+1)/2;
68
0
    default:
69
0
      return h;
70
0
  }
71
0
}
72
73
uint32_t channel_width(uint32_t w, heif_chroma chroma, heif_channel channel)
74
0
{
75
0
  if (channel == heif_channel_Cb || channel == heif_channel_Cr) {
76
0
    return chroma_width(w, chroma);
77
0
  }
78
0
  else {
79
0
    return w;
80
0
  }
81
0
}
82
83
uint32_t channel_height(uint32_t h, heif_chroma chroma, heif_channel channel)
84
0
{
85
0
  if (channel == heif_channel_Cb || channel == heif_channel_Cr) {
86
0
    return chroma_height(h, chroma);
87
0
  }
88
0
  else {
89
0
    return h;
90
0
  }
91
0
}
92
93
HeifPixelImage::~HeifPixelImage()
94
20.4k
{
95
44.2k
  for (auto& iter : m_planes) {
96
44.2k
    delete[] iter.second.allocated_mem;
97
44.2k
  }
98
99
20.4k
  heif_tai_timestamp_packet_release(m_tai_timestamp);
100
20.4k
}
101
102
103
int num_interleaved_pixels_per_plane(heif_chroma chroma)
104
49.1k
{
105
49.1k
  switch (chroma) {
106
0
    case heif_chroma_undefined:
107
909
    case heif_chroma_monochrome:
108
5.86k
    case heif_chroma_420:
109
8.91k
    case heif_chroma_422:
110
37.8k
    case heif_chroma_444:
111
37.8k
      return 1;
112
113
11.2k
    case heif_chroma_interleaved_RGB:
114
11.2k
    case heif_chroma_interleaved_RRGGBB_BE:
115
11.2k
    case heif_chroma_interleaved_RRGGBB_LE:
116
11.2k
      return 3;
117
118
42
    case heif_chroma_interleaved_RGBA:
119
42
    case heif_chroma_interleaved_RRGGBBAA_BE:
120
42
    case heif_chroma_interleaved_RRGGBBAA_LE:
121
42
      return 4;
122
49.1k
  }
123
124
0
  assert(false);
125
0
  return 0;
126
0
}
127
128
129
bool is_integer_multiple_of_chroma_size(uint32_t width,
130
                                        uint32_t height,
131
                                        heif_chroma chroma)
132
0
{
133
0
  switch (chroma) {
134
0
    case heif_chroma_444:
135
0
    case heif_chroma_monochrome:
136
0
      return true;
137
0
    case heif_chroma_422:
138
0
      return (width & 1) == 0;
139
0
    case heif_chroma_420:
140
0
      return (width & 1) == 0 && (height & 1) == 0;
141
0
    default:
142
0
      assert(false);
143
0
      return false;
144
0
  }
145
0
}
146
147
148
std::vector<heif_chroma> get_valid_chroma_values_for_colorspace(heif_colorspace colorspace)
149
7.73k
{
150
7.73k
  switch (colorspace) {
151
7.19k
    case heif_colorspace_YCbCr:
152
7.19k
      return {heif_chroma_420, heif_chroma_422, heif_chroma_444};
153
154
0
    case heif_colorspace_RGB:
155
0
      return {heif_chroma_444,
156
0
              heif_chroma_interleaved_RGB,
157
0
              heif_chroma_interleaved_RGBA,
158
0
              heif_chroma_interleaved_RRGGBB_BE,
159
0
              heif_chroma_interleaved_RRGGBBAA_BE,
160
0
              heif_chroma_interleaved_RRGGBB_LE,
161
0
              heif_chroma_interleaved_RRGGBBAA_LE};
162
163
541
    case heif_colorspace_monochrome:
164
541
      return {heif_chroma_monochrome};
165
166
0
    case heif_colorspace_nonvisual:
167
0
      return {heif_chroma_undefined};
168
169
0
    default:
170
0
      return {};
171
7.73k
  }
172
7.73k
}
173
174
175
void HeifPixelImage::create(uint32_t width, uint32_t height, heif_colorspace colorspace, heif_chroma chroma)
176
20.4k
{
177
20.4k
  m_width = width;
178
20.4k
  m_height = height;
179
20.4k
  m_colorspace = colorspace;
180
20.4k
  m_chroma = chroma;
181
20.4k
}
182
183
static uint32_t rounded_size(uint32_t s)
184
88.4k
{
185
88.4k
  s = (s + 1U) & ~1U;
186
187
88.4k
  if (s < 64) {
188
14.3k
    s = 64;
189
14.3k
  }
190
191
88.4k
  return s;
192
88.4k
}
193
194
Error HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t height, int bit_depth,
195
                                const heif_security_limits* limits)
196
43.4k
{
197
43.4k
  assert(!has_channel(channel));
198
199
43.4k
  ImagePlane plane;
200
43.4k
  int num_interleaved_pixels = num_interleaved_pixels_per_plane(m_chroma);
201
202
  // for backwards compatibility, allow for 24/32 bits for RGB/RGBA interleaved chromas
203
204
43.4k
  if (m_chroma == heif_chroma_interleaved_RGB && bit_depth == 24) {
205
0
    bit_depth = 8;
206
0
  }
207
208
43.4k
  if (m_chroma == heif_chroma_interleaved_RGBA && bit_depth == 32) {
209
0
    bit_depth = 8;
210
0
  }
211
212
43.4k
  if (auto err = plane.alloc(width, height, heif_channel_datatype_unsigned_integer, bit_depth, num_interleaved_pixels, limits, m_memory_handle)) {
213
0
    return err;
214
0
  }
215
43.4k
  else {
216
43.4k
    m_planes.insert(std::make_pair(channel, plane));
217
43.4k
    return Error::Ok;
218
43.4k
  }
219
43.4k
}
220
221
222
Error HeifPixelImage::add_channel(heif_channel channel, uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
223
                                  const heif_security_limits* limits)
224
807
{
225
807
  ImagePlane plane;
226
807
  if (Error err = plane.alloc(width, height, datatype, bit_depth, 1, limits, m_memory_handle)) {
227
0
    return err;
228
0
  }
229
807
  else {
230
807
    m_planes.insert(std::make_pair(channel, plane));
231
807
    return Error::Ok;
232
807
  }
233
807
}
234
235
236
Error HeifPixelImage::ImagePlane::alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth,
237
                                        int num_interleaved_components,
238
                                        const heif_security_limits* limits,
239
                                        MemoryHandle& memory_handle)
240
44.2k
{
241
44.2k
  assert(bit_depth >= 1);
242
44.2k
  assert(bit_depth <= 128);
243
244
44.2k
  if (width == 0 || height == 0) {
245
0
    return {heif_error_Usage_error,
246
0
            heif_suberror_Unspecified,
247
0
            "Invalid image size"};
248
0
  }
249
250
  // use 16 byte alignment (enough for 128 bit data-types). Every row is an integer number of data-elements.
251
44.2k
  uint16_t alignment = 16; // must be power of two
252
253
44.2k
  m_width = width;
254
44.2k
  m_height = height;
255
256
44.2k
  m_mem_width = rounded_size(width);
257
44.2k
  m_mem_height = rounded_size(height);
258
259
44.2k
  assert(num_interleaved_components > 0 && num_interleaved_components <= 255);
260
261
44.2k
  m_bit_depth = static_cast<uint8_t>(bit_depth);
262
44.2k
  m_num_interleaved_components = static_cast<uint8_t>(num_interleaved_components);
263
44.2k
  m_datatype = datatype;
264
265
266
44.2k
  int bytes_per_component = get_bytes_per_pixel();
267
44.2k
  int bytes_per_pixel = num_interleaved_components * bytes_per_component;
268
269
44.2k
  stride = m_mem_width * bytes_per_pixel;
270
44.2k
  stride = (stride + alignment - 1U) & ~(alignment - 1U);
271
272
44.2k
  assert(alignment>=1);
273
274
44.2k
  if (limits &&
275
44.2k
      limits->max_image_size_pixels &&
276
44.2k
      limits->max_image_size_pixels / height < width) {
277
278
0
    std::stringstream sstr;
279
0
    sstr << "Allocating an image of size " << width << "x" << height << " exceeds the security limit of "
280
0
         << limits->max_image_size_pixels << " pixels";
281
282
0
    return {heif_error_Memory_allocation_error,
283
0
            heif_suberror_Security_limit_exceeded,
284
0
            sstr.str()};
285
0
  }
286
287
44.2k
  allocation_size = static_cast<size_t>(m_mem_height) * stride + alignment - 1;
288
289
44.2k
  if (auto err = memory_handle.alloc(allocation_size, limits, "image data")) {
290
0
    return err;
291
0
  }
292
293
    // --- allocate memory
294
295
44.2k
  allocated_mem = new (std::nothrow) uint8_t[allocation_size];
296
44.2k
  if (allocated_mem == nullptr) {
297
0
    std::stringstream sstr;
298
0
    sstr << "Allocating " << allocation_size << " bytes failed";
299
300
0
    return {heif_error_Memory_allocation_error,
301
0
            heif_suberror_Unspecified,
302
0
            sstr.str()};
303
0
  }
304
305
44.2k
  uint8_t* mem_8 = allocated_mem;
306
307
  // shift beginning of image data to aligned memory position
308
309
44.2k
  auto mem_start_addr = (uint64_t) mem_8;
310
44.2k
  auto mem_start_offset = (mem_start_addr & (alignment - 1U));
311
44.2k
  if (mem_start_offset != 0) {
312
0
    mem_8 += alignment - mem_start_offset;
313
0
  }
314
315
44.2k
  mem = mem_8;
316
317
44.2k
  return Error::Ok;
318
44.2k
}
319
320
321
Error HeifPixelImage::extend_padding_to_size(uint32_t width, uint32_t height, bool adjust_size,
322
                                             const heif_security_limits* limits)
323
0
{
324
0
  for (auto& planeIter : m_planes) {
325
0
    auto* plane = &planeIter.second;
326
327
0
    uint32_t subsampled_width, subsampled_height;
328
0
    get_subsampled_size(width, height, planeIter.first, m_chroma,
329
0
                        &subsampled_width, &subsampled_height);
330
331
0
    uint32_t old_width = plane->m_width;
332
0
    uint32_t old_height = plane->m_height;
333
334
0
    int bytes_per_pixel = get_storage_bits_per_pixel(planeIter.first) / 8;
335
336
0
    if (plane->m_mem_width < subsampled_width ||
337
0
        plane->m_mem_height < subsampled_height) {
338
339
0
      ImagePlane newPlane;
340
0
      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, plane->m_datatype, plane->m_bit_depth,
341
0
                                    num_interleaved_pixels_per_plane(m_chroma),
342
0
                                    limits, m_memory_handle))
343
0
      {
344
0
        return err;
345
0
      }
346
347
      // This is not needed, but we have to silence the clang-tidy false positive.
348
0
      if (!newPlane.mem) {
349
0
        return Error::InternalError;
350
0
      }
351
352
      // copy the visible part of the old plane into the new plane
353
354
0
      for (uint32_t y = 0; y < plane->m_height; y++) {
355
0
        memcpy(static_cast<uint8_t*>(newPlane.mem) + y * newPlane.stride,
356
0
               static_cast<uint8_t*>(plane->mem) + y * plane->stride,
357
0
               plane->m_width * bytes_per_pixel);
358
0
      }
359
360
0
      planeIter.second = newPlane;
361
0
      plane = &planeIter.second;
362
0
    }
363
364
    // extend plane size
365
366
0
    if (old_width != subsampled_width) {
367
0
      for (uint32_t y = 0; y < old_height; y++) {
368
0
        for (uint32_t x = old_width; x < subsampled_width; x++) {
369
0
          memcpy(static_cast<uint8_t*>(plane->mem) + y * plane->stride + x * bytes_per_pixel,
370
0
                 static_cast<uint8_t*>(plane->mem) + y * plane->stride + (old_width - 1) * bytes_per_pixel,
371
0
                 bytes_per_pixel);
372
0
        }
373
0
      }
374
0
    }
375
376
0
    for (uint32_t y = old_height; y < subsampled_height; y++) {
377
0
      memcpy(static_cast<uint8_t*>(plane->mem) + y * plane->stride,
378
0
             static_cast<uint8_t*>(plane->mem) + (old_height - 1) * plane->stride,
379
0
             subsampled_width * bytes_per_pixel);
380
0
    }
381
382
383
0
    if (adjust_size) {
384
0
      plane->m_width = subsampled_width;
385
0
      plane->m_height = subsampled_height;
386
0
    }
387
0
  }
388
389
  // modify logical image size, if requested
390
391
0
  if (adjust_size) {
392
0
    m_width = width;
393
0
    m_height = height;
394
0
  }
395
396
0
  return Error::Ok;
397
0
}
398
399
400
Error HeifPixelImage::extend_to_size_with_zero(uint32_t width, uint32_t height, const heif_security_limits* limits)
401
0
{
402
0
  for (auto& planeIter : m_planes) {
403
0
    auto* plane = &planeIter.second;
404
405
0
    uint32_t subsampled_width, subsampled_height;
406
0
    get_subsampled_size(width, height, planeIter.first, m_chroma,
407
0
                        &subsampled_width, &subsampled_height);
408
409
0
    uint32_t old_width = plane->m_width;
410
0
    uint32_t old_height = plane->m_height;
411
412
0
    int bytes_per_pixel = get_storage_bits_per_pixel(planeIter.first) / 8;
413
414
0
    if (plane->m_mem_width < subsampled_width ||
415
0
        plane->m_mem_height < subsampled_height) {
416
417
0
      ImagePlane newPlane;
418
0
      if (auto err = newPlane.alloc(subsampled_width, subsampled_height, plane->m_datatype, plane->m_bit_depth, num_interleaved_pixels_per_plane(m_chroma), limits, m_memory_handle)) {
419
0
        return err;
420
0
      }
421
422
      // This is not needed, but we have to silence the clang-tidy false positive.
423
0
      if (!newPlane.mem) {
424
0
        return Error::InternalError;
425
0
      }
426
427
      // copy the visible part of the old plane into the new plane
428
429
0
      for (uint32_t y = 0; y < plane->m_height; y++) {
430
0
        memcpy(static_cast<uint8_t*>(newPlane.mem) + y * newPlane.stride,
431
0
               static_cast<uint8_t*>(plane->mem) + y * plane->stride,
432
0
               plane->m_width * bytes_per_pixel);
433
0
      }
434
435
0
      planeIter.second = newPlane;
436
0
      plane = &planeIter.second;
437
0
    }
438
439
    // extend plane size
440
441
0
    uint8_t fill = 0;
442
0
    if (bytes_per_pixel == 1 && (planeIter.first == heif_channel_Cb || planeIter.first == heif_channel_Cr)) {
443
0
      fill = 128;
444
0
    }
445
446
0
    if (old_width != subsampled_width) {
447
0
      for (uint32_t y = 0; y < old_height; y++) {
448
0
        memset(static_cast<uint8_t*>(plane->mem) + y * plane->stride + old_width * bytes_per_pixel,
449
0
               fill,
450
0
               bytes_per_pixel * (subsampled_width - old_width));
451
0
      }
452
0
    }
453
454
0
    for (uint32_t y = old_height; y < subsampled_height; y++) {
455
0
      memset(static_cast<uint8_t*>(plane->mem) + y * plane->stride,
456
0
             fill,
457
0
             subsampled_width * bytes_per_pixel);
458
0
    }
459
460
461
0
    plane->m_width = subsampled_width;
462
0
    plane->m_height = subsampled_height;
463
0
  }
464
465
  // modify the logical image size
466
467
0
  m_width = width;
468
0
  m_height = height;
469
470
0
  return Error::Ok;
471
0
}
472
473
bool HeifPixelImage::has_channel(heif_channel channel) const
474
78.3k
{
475
78.3k
  return (m_planes.find(channel) != m_planes.end());
476
78.3k
}
477
478
479
bool HeifPixelImage::has_alpha() const
480
0
{
481
0
  return has_channel(heif_channel_Alpha) ||
482
0
         get_chroma_format() == heif_chroma_interleaved_RGBA ||
483
0
         get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE ||
484
0
         get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE;
485
0
}
486
487
488
uint32_t HeifPixelImage::get_width(enum heif_channel channel) const
489
10.6k
{
490
10.6k
  auto iter = m_planes.find(channel);
491
10.6k
  if (iter == m_planes.end()) {
492
0
    return 0;
493
0
  }
494
495
10.6k
  return iter->second.m_width;
496
10.6k
}
497
498
499
uint32_t HeifPixelImage::get_height(enum heif_channel channel) const
500
10.6k
{
501
10.6k
  auto iter = m_planes.find(channel);
502
10.6k
  if (iter == m_planes.end()) {
503
0
    return 0;
504
0
  }
505
506
10.6k
  return iter->second.m_height;
507
10.6k
}
508
509
510
std::set<heif_channel> HeifPixelImage::get_channel_set() const
511
5.68k
{
512
5.68k
  std::set<heif_channel> channels;
513
514
16.0k
  for (const auto& plane : m_planes) {
515
16.0k
    channels.insert(plane.first);
516
16.0k
  }
517
518
5.68k
  return channels;
519
5.68k
}
520
521
522
uint8_t HeifPixelImage::get_storage_bits_per_pixel(enum heif_channel channel) const
523
5.64k
{
524
5.64k
  if (channel == heif_channel_interleaved) {
525
5.63k
    auto chroma = get_chroma_format();
526
5.63k
    switch (chroma) {
527
5.61k
      case heif_chroma_interleaved_RGB:
528
5.61k
        return 24;
529
19
      case heif_chroma_interleaved_RGBA:
530
19
        return 32;
531
0
      case heif_chroma_interleaved_RRGGBB_BE:
532
0
      case heif_chroma_interleaved_RRGGBB_LE:
533
0
        return 48;
534
0
      case heif_chroma_interleaved_RRGGBBAA_BE:
535
0
      case heif_chroma_interleaved_RRGGBBAA_LE:
536
0
        return 64;
537
0
      default:
538
0
        return -1; // invalid channel/chroma specification
539
5.63k
    }
540
5.63k
  }
541
10
  else {
542
10
    uint32_t bpp = (get_bits_per_pixel(channel) + 7U) & ~7U;
543
10
    assert(bpp <= 255);
544
10
    return static_cast<uint8_t>(bpp);
545
10
  }
546
5.64k
}
547
548
549
uint8_t HeifPixelImage::get_bits_per_pixel(enum heif_channel channel) const
550
57.1k
{
551
57.1k
  auto iter = m_planes.find(channel);
552
57.1k
  if (iter == m_planes.end()) {
553
0
    return -1;
554
0
  }
555
556
57.1k
  return iter->second.m_bit_depth;
557
57.1k
}
558
559
560
uint8_t HeifPixelImage::get_visual_image_bits_per_pixel() const
561
5.65k
{
562
5.65k
  switch (m_colorspace) {
563
508
    case heif_colorspace_monochrome:
564
508
      return get_bits_per_pixel(heif_channel_Y);
565
0
      break;
566
5.07k
    case heif_colorspace_YCbCr:
567
5.07k
      return std::max(get_bits_per_pixel(heif_channel_Y),
568
5.07k
                      std::max(get_bits_per_pixel(heif_channel_Cb),
569
5.07k
                               get_bits_per_pixel(heif_channel_Cr)));
570
0
      break;
571
73
    case heif_colorspace_RGB:
572
73
      return std::max(get_bits_per_pixel(heif_channel_R),
573
73
                      std::max(get_bits_per_pixel(heif_channel_G),
574
73
                               get_bits_per_pixel(heif_channel_B)));
575
0
      break;
576
0
    case heif_colorspace_nonvisual:
577
0
      return 0;
578
0
      break;
579
0
    default:
580
0
      assert(false);
581
0
      return 0;
582
0
      break;
583
5.65k
  }
584
5.65k
}
585
586
587
heif_channel_datatype HeifPixelImage::get_datatype(enum heif_channel channel) const
588
0
{
589
0
  auto iter = m_planes.find(channel);
590
0
  if (iter == m_planes.end()) {
591
0
    return heif_channel_datatype_undefined;
592
0
  }
593
594
0
  return iter->second.m_datatype;
595
0
}
596
597
598
int HeifPixelImage::get_number_of_interleaved_components(heif_channel channel) const
599
0
{
600
0
  auto iter = m_planes.find(channel);
601
0
  if (iter == m_planes.end()) {
602
0
    return 0;
603
0
  }
604
605
0
  return iter->second.m_num_interleaved_components;
606
0
}
607
608
609
Error HeifPixelImage::copy_new_plane_from(const std::shared_ptr<const HeifPixelImage>& src_image,
610
                                          heif_channel src_channel,
611
                                          heif_channel dst_channel,
612
                                          const heif_security_limits* limits)
613
10
{
614
10
  assert(src_image->has_channel(src_channel));
615
10
  assert(!has_channel(dst_channel));
616
617
10
  uint32_t width = src_image->get_width(src_channel);
618
10
  uint32_t height = src_image->get_height(src_channel);
619
620
10
  auto src_plane_iter = src_image->m_planes.find(src_channel);
621
10
  assert(src_plane_iter != src_image->m_planes.end());
622
10
  const auto& src_plane = src_plane_iter->second;
623
624
10
  auto err = add_channel(dst_channel, width, height,
625
10
                         src_plane.m_datatype,
626
10
                         src_image->get_bits_per_pixel(src_channel), limits);
627
10
  if (err) {
628
0
    return err;
629
0
  }
630
631
10
  uint8_t* dst;
632
10
  size_t dst_stride = 0;
633
634
10
  const uint8_t* src;
635
10
  size_t src_stride = 0;
636
637
10
  src = src_image->get_plane(src_channel, &src_stride);
638
10
  dst = get_plane(dst_channel, &dst_stride);
639
640
10
  uint32_t bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8);
641
642
916
  for (uint32_t y = 0; y < height; y++) {
643
906
    memcpy(dst + y * dst_stride, src + y * src_stride, bpl);
644
906
  }
645
646
10
  return Error::Ok;
647
10
}
648
649
650
Error HeifPixelImage::extract_alpha_from_RGBA(const std::shared_ptr<const HeifPixelImage>& src_image,
651
                                              const heif_security_limits* limits)
652
0
{
653
0
  uint32_t width = src_image->get_width();
654
0
  uint32_t height = src_image->get_height();
655
656
0
  if (Error err = add_plane(heif_channel_Y, width, height, src_image->get_bits_per_pixel(heif_channel_interleaved), limits)) {
657
0
    return err;
658
0
  }
659
660
0
  uint8_t* dst;
661
0
  size_t dst_stride = 0;
662
663
0
  const uint8_t* src;
664
0
  size_t src_stride = 0;
665
666
0
  src = src_image->get_plane(heif_channel_interleaved, &src_stride);
667
0
  dst = get_plane(heif_channel_Y, &dst_stride);
668
669
  //int bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8);
670
671
0
  for (uint32_t y = 0; y < height; y++) {
672
0
    for (uint32_t x = 0; x < width; x++) {
673
0
      dst[y * dst_stride + x] = src[y * src_stride + 4 * x + 3];
674
0
    }
675
0
  }
676
677
0
  return Error::Ok;
678
0
}
679
680
681
Error HeifPixelImage::fill_new_plane(heif_channel dst_channel, uint16_t value, int width, int height, int bpp,
682
                                     const heif_security_limits* limits)
683
0
{
684
0
  if (Error err = add_plane(dst_channel, width, height, bpp, limits)) {
685
0
    return err;
686
0
  }
687
688
0
  fill_plane(dst_channel, value);
689
690
0
  return Error::Ok;
691
0
}
692
693
694
void HeifPixelImage::fill_plane(heif_channel dst_channel, uint16_t value)
695
0
{
696
0
  int num_interleaved = num_interleaved_pixels_per_plane(m_chroma);
697
698
0
  int bpp = get_bits_per_pixel(dst_channel);
699
0
  uint32_t width = get_width(dst_channel);
700
0
  uint32_t height = get_height(dst_channel);
701
702
0
  if (bpp <= 8) {
703
0
    uint8_t* dst;
704
0
    size_t dst_stride = 0;
705
0
    dst = get_plane(dst_channel, &dst_stride);
706
0
    uint32_t width_bytes = width * num_interleaved;
707
708
0
    for (uint32_t y = 0; y < height; y++) {
709
0
      memset(dst + y * dst_stride, value, width_bytes);
710
0
    }
711
0
  }
712
0
  else {
713
0
    uint16_t* dst;
714
0
    size_t dst_stride = 0;
715
0
    dst = get_channel<uint16_t>(dst_channel, &dst_stride);
716
717
0
    for (uint32_t y = 0; y < height; y++) {
718
0
      for (uint32_t x = 0; x < width * num_interleaved; x++) {
719
0
        dst[y * dst_stride + x] = value;
720
0
      }
721
0
    }
722
0
  }
723
0
}
724
725
726
void HeifPixelImage::transfer_plane_from_image_as(const std::shared_ptr<HeifPixelImage>& source,
727
                                                  heif_channel src_channel,
728
                                                  heif_channel dst_channel)
729
26
{
730
  // TODO: check that dst_channel does not exist yet
731
732
26
  ImagePlane plane = source->m_planes[src_channel];
733
26
  source->m_planes.erase(src_channel);
734
26
  source->m_memory_handle.free(plane.allocation_size);
735
736
26
  m_planes.insert(std::make_pair(dst_channel, plane));
737
738
  // Note: we assume that image planes are never transferred between heif_contexts
739
26
  m_memory_handle.alloc(plane.allocation_size,
740
26
                        source->m_memory_handle.get_security_limits(),
741
26
                        "transferred image data");
742
26
}
743
744
745
bool is_interleaved_with_alpha(heif_chroma chroma)
746
11.3k
{
747
11.3k
  switch (chroma) {
748
0
    case heif_chroma_undefined:
749
508
    case heif_chroma_monochrome:
750
1.78k
    case heif_chroma_420:
751
2.69k
    case heif_chroma_422:
752
5.66k
    case heif_chroma_444:
753
11.2k
    case heif_chroma_interleaved_RGB:
754
11.2k
    case heif_chroma_interleaved_RRGGBB_BE:
755
11.2k
    case heif_chroma_interleaved_RRGGBB_LE:
756
11.2k
      return false;
757
758
23
    case heif_chroma_interleaved_RGBA:
759
23
    case heif_chroma_interleaved_RRGGBBAA_BE:
760
23
    case heif_chroma_interleaved_RRGGBBAA_LE:
761
23
      return true;
762
11.3k
  }
763
764
0
  assert(false);
765
0
  return false;
766
0
}
767
768
769
Error HeifPixelImage::copy_image_to(const std::shared_ptr<const HeifPixelImage>& source, uint32_t x0, uint32_t y0)
770
0
{
771
0
  std::set<enum heif_channel> channels = source->get_channel_set();
772
773
0
  uint32_t w = get_width();
774
0
  uint32_t h = get_height();
775
0
  heif_chroma chroma = get_chroma_format();
776
777
778
0
  for (heif_channel channel : channels) {
779
780
0
    size_t tile_stride;
781
0
    const uint8_t* tile_data = source->get_plane(channel, &tile_stride);
782
783
0
    size_t out_stride;
784
0
    uint8_t* out_data = get_plane(channel, &out_stride);
785
786
0
    if (w <= x0 || h <= y0) {
787
0
      return {heif_error_Invalid_input,
788
0
              heif_suberror_Invalid_grid_data};
789
0
    }
790
791
0
    if (source->get_bits_per_pixel(channel) != get_bits_per_pixel(channel)) {
792
0
      return {heif_error_Invalid_input,
793
0
              heif_suberror_Wrong_tile_image_pixel_depth};
794
0
    }
795
796
0
    uint32_t src_width = source->get_width(channel);
797
0
    uint32_t src_height = source->get_height(channel);
798
799
0
    uint32_t copy_width = std::min(src_width, channel_width(w - x0, chroma, channel));
800
0
    uint32_t copy_height = std::min(src_height, channel_height(h - y0, chroma, channel));
801
802
0
    copy_width *= source->get_storage_bits_per_pixel(channel) / 8;
803
804
0
    uint32_t xs = channel_width(x0, chroma, channel);
805
0
    uint32_t ys = channel_height(y0, chroma, channel);
806
0
    xs *= source->get_storage_bits_per_pixel(channel) / 8;
807
808
0
    for (uint32_t py = 0; py < copy_height; py++) {
809
0
      memcpy(out_data + xs + (ys + py) * out_stride,
810
0
             tile_data + py * tile_stride,
811
0
             copy_width);
812
0
    }
813
0
  }
814
815
0
  return Error::Ok;
816
0
}
817
818
819
Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::rotate_ccw(int angle_degrees, const heif_security_limits* limits)
820
208
{
821
  // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation
822
823
208
  bool need_conversion = false;
824
825
208
  if (get_chroma_format() == heif_chroma_422) {
826
0
    if (angle_degrees == 90 || angle_degrees == 270) {
827
0
      need_conversion = true;
828
0
    }
829
0
    else if (angle_degrees == 180 && has_odd_height()) {
830
0
      need_conversion = true;
831
0
    }
832
0
  }
833
208
  else if (get_chroma_format() == heif_chroma_420) {
834
21
    if (angle_degrees == 90 && has_odd_width()) {
835
2
      need_conversion = true;
836
2
    }
837
19
    else if (angle_degrees == 180 && (has_odd_width() || has_odd_height())) {
838
3
      need_conversion = true;
839
3
    }
840
16
    else if (angle_degrees == 270 && has_odd_height()) {
841
2
      need_conversion = true;
842
2
    }
843
21
  }
844
845
208
  if (need_conversion) {
846
7
    heif_color_conversion_options options{};
847
7
    heif_color_conversion_options_set_defaults(&options);
848
849
7
    auto converted_image_result = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444, nullptr, get_bits_per_pixel(heif_channel_Y), options, nullptr, limits);
850
7
    if (converted_image_result.error) {
851
0
      return converted_image_result.error;
852
0
    }
853
854
7
    return (*converted_image_result)->rotate_ccw(angle_degrees, limits);
855
7
  }
856
857
858
  // --- create output image
859
860
201
  if (angle_degrees == 0) {
861
16
    return shared_from_this();
862
16
  }
863
864
185
  uint32_t out_width = m_width;
865
185
  uint32_t out_height = m_height;
866
867
185
  if (angle_degrees == 90 || angle_degrees == 270) {
868
124
    std::swap(out_width, out_height);
869
124
  }
870
871
185
  std::shared_ptr<HeifPixelImage> out_img = std::make_shared<HeifPixelImage>();
872
185
  out_img->create(out_width, out_height, m_colorspace, m_chroma);
873
874
875
  // --- rotate all channels
876
877
491
  for (const auto &plane_pair: m_planes) {
878
491
    heif_channel channel = plane_pair.first;
879
491
    const ImagePlane &plane = plane_pair.second;
880
881
    /*
882
    if (plane.bit_depth != 8) {
883
      return Error(heif_error_Unsupported_feature,
884
                   heif_suberror_Unspecified,
885
                   "Can currently only rotate images with 8 bits per pixel");
886
    }
887
    */
888
889
491
    uint32_t out_plane_width = plane.m_width;
890
491
    uint32_t out_plane_height = plane.m_height;
891
892
491
    if (angle_degrees == 90 || angle_degrees == 270) {
893
326
      std::swap(out_plane_width, out_plane_height);
894
326
    }
895
896
491
    Error err = out_img->add_channel(channel, out_plane_width, out_plane_height, plane.m_datatype, plane.m_bit_depth, limits);
897
491
    if (err) {
898
0
      return err;
899
0
    }
900
901
491
    auto out_plane_iter = out_img->m_planes.find(channel);
902
491
    assert(out_plane_iter != out_img->m_planes.end());
903
491
    ImagePlane& out_plane = out_plane_iter->second;
904
905
491
    if (plane.m_bit_depth <= 8) {
906
308
      plane.rotate_ccw<uint8_t>(angle_degrees, out_plane);
907
308
    }
908
183
    else if (plane.m_bit_depth <= 16) {
909
183
      plane.rotate_ccw<uint16_t>(angle_degrees, out_plane);
910
183
    }
911
0
    else if (plane.m_bit_depth <= 32) {
912
0
      plane.rotate_ccw<uint32_t>(angle_degrees, out_plane);
913
0
    }
914
0
    else if (plane.m_bit_depth <= 64) {
915
0
      plane.rotate_ccw<uint64_t>(angle_degrees, out_plane);
916
0
    }
917
0
    else if (plane.m_bit_depth <= 128) {
918
0
      plane.rotate_ccw<heif_complex64>(angle_degrees, out_plane);
919
0
    }
920
491
  }
921
  // --- pass the color profiles to the new image
922
923
185
  out_img->set_color_profile_nclx(get_color_profile_nclx());
924
185
  out_img->set_color_profile_icc(get_color_profile_icc());
925
926
185
  return out_img;
927
185
}
928
929
template<typename T>
930
void HeifPixelImage::ImagePlane::rotate_ccw(int angle_degrees,
931
                                            ImagePlane& out_plane) const
932
491
{
933
491
  uint32_t w = m_width;
934
491
  uint32_t h = m_height;
935
936
491
  uint32_t in_stride = stride / uint32_t(sizeof(T));
937
491
  const T* in_data = static_cast<const T*>(mem);
938
939
491
  uint32_t out_stride = out_plane.stride / uint32_t(sizeof(T));
940
491
  T* out_data = static_cast<T*>(out_plane.mem);
941
942
491
  if (angle_degrees == 270) {
943
797k
    for (uint32_t x = 0; x < h; x++)
944
16.2M
      for (uint32_t y = 0; y < w; y++) {
945
15.4M
        out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y];
946
15.4M
      }
947
329
  } else if (angle_degrees == 180) {
948
533k
    for (uint32_t y = 0; y < h; y++)
949
5.56M
      for (uint32_t x = 0; x < w; x++) {
950
5.03M
        out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)];
951
5.03M
      }
952
165
  } else if (angle_degrees == 90) {
953
190k
    for (uint32_t x = 0; x < h; x++)
954
7.52M
      for (uint32_t y = 0; y < w; y++) {
955
7.33M
        out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)];
956
7.33M
      }
957
164
  }
958
491
}
void HeifPixelImage::ImagePlane::rotate_ccw<unsigned char>(int, HeifPixelImage::ImagePlane&) const
Line
Count
Source
932
308
{
933
308
  uint32_t w = m_width;
934
308
  uint32_t h = m_height;
935
936
308
  uint32_t in_stride = stride / uint32_t(sizeof(T));
937
308
  const T* in_data = static_cast<const T*>(mem);
938
939
308
  uint32_t out_stride = out_plane.stride / uint32_t(sizeof(T));
940
308
  T* out_data = static_cast<T*>(out_plane.mem);
941
942
308
  if (angle_degrees == 270) {
943
790k
    for (uint32_t x = 0; x < h; x++)
944
14.1M
      for (uint32_t y = 0; y < w; y++) {
945
13.3M
        out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y];
946
13.3M
      }
947
206
  } else if (angle_degrees == 180) {
948
528k
    for (uint32_t y = 0; y < h; y++)
949
3.79M
      for (uint32_t x = 0; x < w; x++) {
950
3.27M
        out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)];
951
3.27M
      }
952
114
  } else if (angle_degrees == 90) {
953
183k
    for (uint32_t x = 0; x < h; x++)
954
5.57M
      for (uint32_t y = 0; y < w; y++) {
955
5.39M
        out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)];
956
5.39M
      }
957
92
  }
958
308
}
void HeifPixelImage::ImagePlane::rotate_ccw<unsigned short>(int, HeifPixelImage::ImagePlane&) const
Line
Count
Source
932
183
{
933
183
  uint32_t w = m_width;
934
183
  uint32_t h = m_height;
935
936
183
  uint32_t in_stride = stride / uint32_t(sizeof(T));
937
183
  const T* in_data = static_cast<const T*>(mem);
938
939
183
  uint32_t out_stride = out_plane.stride / uint32_t(sizeof(T));
940
183
  T* out_data = static_cast<T*>(out_plane.mem);
941
942
183
  if (angle_degrees == 270) {
943
7.22k
    for (uint32_t x = 0; x < h; x++)
944
2.12M
      for (uint32_t y = 0; y < w; y++) {
945
2.11M
        out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y];
946
2.11M
      }
947
123
  } else if (angle_degrees == 180) {
948
4.57k
    for (uint32_t y = 0; y < h; y++)
949
1.76M
      for (uint32_t x = 0; x < w; x++) {
950
1.76M
        out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)];
951
1.76M
      }
952
72
  } else if (angle_degrees == 90) {
953
7.05k
    for (uint32_t x = 0; x < h; x++)
954
1.94M
      for (uint32_t y = 0; y < w; y++) {
955
1.94M
        out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)];
956
1.94M
      }
957
72
  }
958
183
}
Unexecuted instantiation: void HeifPixelImage::ImagePlane::rotate_ccw<unsigned int>(int, HeifPixelImage::ImagePlane&) const
Unexecuted instantiation: void HeifPixelImage::ImagePlane::rotate_ccw<unsigned long>(int, HeifPixelImage::ImagePlane&) const
Unexecuted instantiation: void HeifPixelImage::ImagePlane::rotate_ccw<heif_complex64>(int, HeifPixelImage::ImagePlane&) const
959
960
961
template<typename T>
962
void HeifPixelImage::ImagePlane::mirror_inplace(heif_transform_mirror_direction direction)
963
380
{
964
380
  uint32_t w = m_width;
965
380
  uint32_t h = m_height;
966
967
380
  T* data = static_cast<T*>(mem);
968
969
380
  if (direction == heif_transform_mirror_direction_horizontal) {
970
1.73M
    for (uint32_t y = 0; y < h; y++) {
971
7.65M
      for (uint32_t x = 0; x < w / 2; x++)
972
5.91M
        std::swap(data[y * stride / sizeof(T) + x], data[y * stride / sizeof(T) + w - 1 - x]);
973
1.73M
    }
974
191
  } else {
975
156k
    for (uint32_t y = 0; y < h / 2; y++) {
976
6.77M
      for (uint32_t x = 0; x < w; x++)
977
6.62M
        std::swap(data[y * stride / sizeof(T) + x], data[(h - 1 - y) * stride / sizeof(T) + x]);
978
156k
    }
979
189
  }
980
380
}
void HeifPixelImage::ImagePlane::mirror_inplace<unsigned char>(heif_transform_mirror_direction)
Line
Count
Source
963
235
{
964
235
  uint32_t w = m_width;
965
235
  uint32_t h = m_height;
966
967
235
  T* data = static_cast<T*>(mem);
968
969
235
  if (direction == heif_transform_mirror_direction_horizontal) {
970
1.71M
    for (uint32_t y = 0; y < h; y++) {
971
7.12M
      for (uint32_t x = 0; x < w / 2; x++)
972
5.41M
        std::swap(data[y * stride / sizeof(T) + x], data[y * stride / sizeof(T) + w - 1 - x]);
973
1.71M
    }
974
123
  } else {
975
138k
    for (uint32_t y = 0; y < h / 2; y++) {
976
6.11M
      for (uint32_t x = 0; x < w; x++)
977
5.98M
        std::swap(data[y * stride / sizeof(T) + x], data[(h - 1 - y) * stride / sizeof(T) + x]);
978
138k
    }
979
123
  }
980
235
}
void HeifPixelImage::ImagePlane::mirror_inplace<unsigned short>(heif_transform_mirror_direction)
Line
Count
Source
963
145
{
964
145
  uint32_t w = m_width;
965
145
  uint32_t h = m_height;
966
967
145
  T* data = static_cast<T*>(mem);
968
969
145
  if (direction == heif_transform_mirror_direction_horizontal) {
970
25.3k
    for (uint32_t y = 0; y < h; y++) {
971
528k
      for (uint32_t x = 0; x < w / 2; x++)
972
502k
        std::swap(data[y * stride / sizeof(T) + x], data[y * stride / sizeof(T) + w - 1 - x]);
973
25.2k
    }
974
79
  } else {
975
18.5k
    for (uint32_t y = 0; y < h / 2; y++) {
976
657k
      for (uint32_t x = 0; x < w; x++)
977
639k
        std::swap(data[y * stride / sizeof(T) + x], data[(h - 1 - y) * stride / sizeof(T) + x]);
978
18.4k
    }
979
66
  }
980
145
}
Unexecuted instantiation: void HeifPixelImage::ImagePlane::mirror_inplace<unsigned int>(heif_transform_mirror_direction)
Unexecuted instantiation: void HeifPixelImage::ImagePlane::mirror_inplace<unsigned long>(heif_transform_mirror_direction)
Unexecuted instantiation: void HeifPixelImage::ImagePlane::mirror_inplace<heif_complex64>(heif_transform_mirror_direction)
981
982
983
Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::mirror_inplace(heif_transform_mirror_direction direction,
984
                                                                       const heif_security_limits* limits)
985
171
{
986
  // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation
987
988
171
  bool need_conversion = false;
989
990
171
  if (get_chroma_format() == heif_chroma_422) {
991
0
    if (direction == heif_transform_mirror_direction_horizontal && has_odd_width()) {
992
0
      need_conversion = true;
993
0
    }
994
0
  }
995
171
  else if (get_chroma_format() == heif_chroma_420) {
996
25
    if (has_odd_width() || has_odd_height()) {
997
21
      need_conversion = true;
998
21
    }
999
25
  }
1000
1001
171
  if (need_conversion) {
1002
21
    heif_color_conversion_options options{};
1003
21
    heif_color_conversion_options_set_defaults(&options);
1004
1005
21
    auto converted_image_result = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444, nullptr, get_bits_per_pixel(heif_channel_Y), options, nullptr, limits);
1006
21
    if (converted_image_result.error) {
1007
0
      return converted_image_result.error;
1008
0
    }
1009
1010
21
    return (*converted_image_result)->mirror_inplace(direction, limits);
1011
21
  }
1012
1013
1014
380
  for (auto& plane_pair : m_planes) {
1015
380
    ImagePlane& plane = plane_pair.second;
1016
1017
380
    if (plane.m_bit_depth <= 8) {
1018
235
      plane.mirror_inplace<uint8_t>(direction);
1019
235
    }
1020
145
    else if (plane.m_bit_depth <= 16) {
1021
145
      plane.mirror_inplace<uint16_t>(direction);
1022
145
    }
1023
0
    else if (plane.m_bit_depth <= 32) {
1024
0
      plane.mirror_inplace<uint32_t>(direction);
1025
0
    }
1026
0
    else if (plane.m_bit_depth <= 64) {
1027
0
      plane.mirror_inplace<uint64_t>(direction);
1028
0
    }
1029
0
    else if (plane.m_bit_depth <= 128) {
1030
0
      plane.mirror_inplace<heif_complex64>(direction);
1031
0
    }
1032
0
    else {
1033
0
      std::stringstream sstr;
1034
0
      sstr << "Cannot mirror images with " << plane.m_bit_depth << " bits per pixel";
1035
0
      return Error{heif_error_Unsupported_feature,
1036
0
                   heif_suberror_Unspecified,
1037
0
                   sstr.str()};
1038
0
    }
1039
380
  }
1040
1041
150
  return shared_from_this();
1042
150
}
1043
1044
1045
int HeifPixelImage::ImagePlane::get_bytes_per_pixel() const
1046
44.5k
{
1047
44.5k
  if (m_bit_depth <= 8) {
1048
32.9k
    return 1;
1049
32.9k
  }
1050
11.5k
  else if (m_bit_depth <= 16) {
1051
11.5k
    return 2;
1052
11.5k
  }
1053
0
  else if (m_bit_depth <= 32) {
1054
0
    return 4;
1055
0
  }
1056
0
  else if (m_bit_depth <= 64) {
1057
0
    return 8;
1058
0
  }
1059
0
  else {
1060
0
    assert(m_bit_depth <= 128);
1061
0
    return 16;
1062
0
  }
1063
44.5k
}
1064
1065
1066
Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
1067
                                                             const heif_security_limits* limits) const
1068
102
{
1069
  // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before cropping
1070
1071
102
  bool need_conversion = false;
1072
1073
102
  if (get_chroma_format() == heif_chroma_422 && (left & 1) == 1) {
1074
0
      need_conversion = true;
1075
0
  }
1076
102
  else if (get_chroma_format() == heif_chroma_420 &&
1077
102
           ((left & 1) == 1 || (top & 1) == 1)) {
1078
0
    need_conversion = true;
1079
0
  }
1080
1081
102
  if (need_conversion) {
1082
0
    heif_color_conversion_options options{};
1083
0
    heif_color_conversion_options_set_defaults(&options);
1084
1085
0
    auto converted_image_result = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444, nullptr, get_bits_per_pixel(heif_channel_Y), options, nullptr, limits);
1086
0
    if (converted_image_result.error) {
1087
0
      return converted_image_result.error;
1088
0
    }
1089
1090
0
    return (*converted_image_result)->crop(left, right, top, bottom, limits);
1091
0
  }
1092
1093
1094
1095
102
  auto out_img = std::make_shared<HeifPixelImage>();
1096
102
  out_img->create(right - left + 1, bottom - top + 1, m_colorspace, m_chroma);
1097
1098
1099
  // --- crop all channels
1100
1101
306
  for (const auto& plane_pair : m_planes) {
1102
306
    heif_channel channel = plane_pair.first;
1103
306
    const ImagePlane& plane = plane_pair.second;
1104
1105
306
    uint32_t plane_left = get_subsampled_size_h(left, channel, m_chroma, scaling_mode::is_divisible); // is always divisible
1106
306
    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
1107
306
    uint32_t plane_top = get_subsampled_size_v(top, channel, m_chroma, scaling_mode::is_divisible);
1108
306
    uint32_t plane_bottom = get_subsampled_size_v(bottom, channel, m_chroma, scaling_mode::round_down);
1109
1110
306
    auto err = out_img->add_channel(channel,
1111
306
                                    plane_right - plane_left + 1,
1112
306
                                    plane_bottom - plane_top + 1,
1113
306
                                    plane.m_datatype,
1114
306
                                    plane.m_bit_depth,
1115
306
                                    limits);
1116
306
    if (err) {
1117
0
      return err;
1118
0
    }
1119
1120
306
    auto out_plane_iter = out_img->m_planes.find(channel);
1121
306
    assert(out_plane_iter != out_img->m_planes.end());
1122
306
    ImagePlane& out_plane = out_plane_iter->second;
1123
1124
306
    int bytes_per_pixel = plane.get_bytes_per_pixel();
1125
306
    plane.crop(plane_left, plane_right, plane_top, plane_bottom, bytes_per_pixel, out_plane);
1126
306
  }
1127
1128
  // --- pass the color profiles to the new image
1129
1130
102
  out_img->set_color_profile_nclx(get_color_profile_nclx());
1131
102
  out_img->set_color_profile_icc(get_color_profile_icc());
1132
1133
102
  return out_img;
1134
102
}
1135
1136
1137
void HeifPixelImage::ImagePlane::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
1138
                                      int bytes_per_pixel, ImagePlane& out_plane) const
1139
306
{
1140
306
  uint32_t in_stride = stride;
1141
306
  auto* in_data = static_cast<const uint8_t*>(mem);
1142
1143
306
  uint32_t out_stride = out_plane.stride;
1144
306
  auto* out_data = static_cast<uint8_t*>(out_plane.mem);
1145
1146
996
  for (uint32_t y = top; y <= bottom; y++) {
1147
690
    memcpy(&out_data[(y - top) * out_stride],
1148
690
           &in_data[y * in_stride + left * bytes_per_pixel],
1149
690
           (right - left + 1) * bytes_per_pixel);
1150
690
  }
1151
306
}
1152
1153
1154
Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_t a)
1155
78
{
1156
312
  for (const auto& channel : {heif_channel_R, heif_channel_G, heif_channel_B, heif_channel_Alpha}) {
1157
1158
312
    const auto plane_iter = m_planes.find(channel);
1159
312
    if (plane_iter == m_planes.end()) {
1160
1161
      // alpha channel is optional, R,G,B is required
1162
78
      if (channel == heif_channel_Alpha) {
1163
78
        continue;
1164
78
      }
1165
1166
0
      return {heif_error_Usage_error,
1167
0
              heif_suberror_Nonexisting_image_channel_referenced};
1168
1169
78
    }
1170
1171
234
    ImagePlane& plane = plane_iter->second;
1172
1173
234
    if (plane.m_bit_depth != 8) {
1174
0
      return {heif_error_Unsupported_feature,
1175
0
              heif_suberror_Unspecified,
1176
0
              "Can currently only fill images with 8 bits per pixel"};
1177
0
    }
1178
1179
234
    size_t h = plane.m_height;
1180
1181
234
    size_t stride = plane.stride;
1182
234
    auto* data = static_cast<uint8_t*>(plane.mem);
1183
1184
234
    uint16_t val16;
1185
234
    switch (channel) {
1186
78
      case heif_channel_R:
1187
78
        val16 = r;
1188
78
        break;
1189
78
      case heif_channel_G:
1190
78
        val16 = g;
1191
78
        break;
1192
78
      case heif_channel_B:
1193
78
        val16 = b;
1194
78
        break;
1195
0
      case heif_channel_Alpha:
1196
0
        val16 = a;
1197
0
        break;
1198
0
      default:
1199
        // initialization only to avoid warning of uninitialized variable.
1200
0
        val16 = 0;
1201
        // Should already be detected by the check above ("m_planes.find").
1202
0
        assert(false);
1203
234
    }
1204
1205
234
    auto val8 = static_cast<uint8_t>(val16 >> 8U);
1206
1207
1208
    // memset() even when h * stride > sizeof(size_t)
1209
1210
234
    if (std::numeric_limits<size_t>::max() / stride > h) {
1211
      // can fill in one step
1212
234
      memset(data, val8, stride * h);
1213
234
    }
1214
0
    else {
1215
      // fill line by line
1216
0
      auto* p = data;
1217
1218
0
      for (size_t y=0;y<h;y++) {
1219
0
        memset(p, val8, stride);
1220
0
        p += stride;
1221
0
      }
1222
0
    }
1223
234
  }
1224
1225
78
  return Error::Ok;
1226
78
}
1227
1228
1229
uint32_t negate_negative_int32(int32_t x)
1230
0
{
1231
0
  assert(x <= 0);
1232
1233
0
  if (x == INT32_MIN) {
1234
0
    return static_cast<uint32_t>(INT32_MAX) + 1;
1235
0
  }
1236
0
  else {
1237
0
    return static_cast<uint32_t>(-x);
1238
0
  }
1239
0
}
1240
1241
1242
Error HeifPixelImage::overlay(std::shared_ptr<HeifPixelImage>& overlay, int32_t dx, int32_t dy)
1243
0
{
1244
0
  std::set<enum heif_channel> channels = overlay->get_channel_set();
1245
1246
0
  bool has_alpha = overlay->has_channel(heif_channel_Alpha);
1247
  //bool has_alpha_me = has_channel(heif_channel_Alpha);
1248
1249
0
  size_t alpha_stride = 0;
1250
0
  uint8_t* alpha_p;
1251
0
  alpha_p = overlay->get_plane(heif_channel_Alpha, &alpha_stride);
1252
1253
0
  for (heif_channel channel : channels) {
1254
0
    if (!has_channel(channel)) {
1255
0
      continue;
1256
0
    }
1257
1258
0
    size_t in_stride = 0;
1259
0
    const uint8_t* in_p;
1260
1261
0
    size_t out_stride = 0;
1262
0
    uint8_t* out_p;
1263
1264
0
    in_p = overlay->get_plane(channel, &in_stride);
1265
0
    out_p = get_plane(channel, &out_stride);
1266
1267
0
    uint32_t in_w = overlay->get_width(channel);
1268
0
    uint32_t in_h = overlay->get_height(channel);
1269
1270
0
    uint32_t out_w = get_width(channel);
1271
0
    uint32_t out_h = get_height(channel);
1272
1273
1274
    // --- check whether overlay image overlaps with current image
1275
1276
0
    if (dx > 0 && static_cast<uint32_t>(dx) >= out_w) {
1277
      // the overlay image is completely outside the right border -> skip overlaying
1278
0
      return Error::Ok;
1279
0
    }
1280
0
    else if (dx < 0 && in_w <= negate_negative_int32(dx)) {
1281
      // the overlay image is completely outside the left border -> skip overlaying
1282
0
      return Error::Ok;
1283
0
    }
1284
1285
0
    if (dy > 0 && static_cast<uint32_t>(dy) >= out_h) {
1286
      // the overlay image is completely outside the bottom border -> skip overlaying
1287
0
      return Error::Ok;
1288
0
    }
1289
0
    else if (dy < 0 && in_h <= negate_negative_int32(dy)) {
1290
      // the overlay image is completely outside the top border -> skip overlaying
1291
0
      return Error::Ok;
1292
0
    }
1293
1294
1295
    // --- compute overlapping area
1296
1297
    // top-left points where to start copying in source and destination
1298
0
    uint32_t in_x0;
1299
0
    uint32_t in_y0;
1300
0
    uint32_t out_x0;
1301
0
    uint32_t out_y0;
1302
1303
    // right border
1304
0
    if (dx + static_cast<int64_t>(in_w) > out_w) {
1305
      // overlay image extends partially outside of right border
1306
      // Notes:
1307
      // - (out_w-dx) cannot underflow because dx<out_w is ensured above
1308
      // - (out_w-dx) cannot overflow (for dx<0) because, as just checked, out_w-dx < in_w
1309
      //              and in_w fits into uint32_t
1310
0
      in_w = static_cast<uint32_t>(static_cast<int64_t>(out_w) - dx);
1311
0
    }
1312
1313
    // bottom border
1314
0
    if (dy + static_cast<int64_t>(in_h) > out_h) {
1315
      // overlay image extends partially outside of bottom border
1316
0
      in_h = static_cast<uint32_t>(static_cast<int64_t>(out_h) - dy);
1317
0
    }
1318
1319
    // left border
1320
0
    if (dx < 0) {
1321
      // overlay image starts partially outside of left border
1322
1323
0
      in_x0 = negate_negative_int32(dx);
1324
0
      out_x0 = 0;
1325
0
      in_w = in_w - in_x0; // in_x0 < in_w because in_w > -dx = in_x0
1326
0
    }
1327
0
    else {
1328
0
      in_x0 = 0;
1329
0
      out_x0 = static_cast<uint32_t>(dx);
1330
0
    }
1331
1332
    // top border
1333
0
    if (dy < 0) {
1334
      // overlay image started partially outside of top border
1335
1336
0
      in_y0 = negate_negative_int32(dy);
1337
0
      out_y0 = 0;
1338
0
      in_h = in_h - in_y0; // in_y0 < in_h because in_h > -dy = in_y0
1339
0
    }
1340
0
    else {
1341
0
      in_y0 = 0;
1342
0
      out_y0 = static_cast<uint32_t>(dy);
1343
0
    }
1344
1345
    // --- computer overlay in overlapping area
1346
1347
0
    for (uint32_t y = in_y0; y < in_h; y++) {
1348
0
      if (!has_alpha) {
1349
0
        memcpy(out_p + out_x0 + (out_y0 + y - in_y0) * out_stride,
1350
0
               in_p + in_x0 + y * in_stride,
1351
0
               in_w - in_x0);
1352
0
      }
1353
0
      else {
1354
0
        for (uint32_t x = in_x0; x < in_w; x++) {
1355
0
          uint8_t* outptr = &out_p[out_x0 + (out_y0 + y - in_y0) * out_stride + x];
1356
0
          uint8_t in_val = in_p[in_x0 + y * in_stride + x];
1357
0
          uint8_t alpha_val = alpha_p[in_x0 + y * in_stride + x];
1358
1359
0
          *outptr = (uint8_t) ((in_val * alpha_val + *outptr * (255 - alpha_val)) / 255);
1360
0
        }
1361
0
      }
1362
0
    }
1363
0
  }
1364
1365
0
  return Error::Ok;
1366
0
}
1367
1368
1369
Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr<HeifPixelImage>& out_img,
1370
                                             uint32_t width, uint32_t height,
1371
                                             const heif_security_limits* limits) const
1372
21
{
1373
21
  out_img = std::make_shared<HeifPixelImage>();
1374
21
  out_img->create(width, height, m_colorspace, m_chroma);
1375
1376
1377
  // --- create output image with scaled planes
1378
1379
21
  if (has_channel(heif_channel_interleaved)) {
1380
0
    if (auto err = out_img->add_plane(heif_channel_interleaved, width, height, get_bits_per_pixel(heif_channel_interleaved), limits)) {
1381
0
      return err;
1382
0
    }
1383
0
  }
1384
21
  else {
1385
21
    if (get_colorspace() == heif_colorspace_RGB) {
1386
0
      if (!has_channel(heif_channel_R) ||
1387
0
          !has_channel(heif_channel_G) ||
1388
0
          !has_channel(heif_channel_B)) {
1389
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "RGB input without R,G,B, planes"};
1390
0
      }
1391
1392
0
      if (auto err = out_img->add_plane(heif_channel_R, width, height, get_bits_per_pixel(heif_channel_R), limits)) {
1393
0
        return err;
1394
0
      }
1395
0
      if (auto err = out_img->add_plane(heif_channel_G, width, height, get_bits_per_pixel(heif_channel_G), limits)) {
1396
0
        return err;
1397
0
      }
1398
0
      if (auto err = out_img->add_plane(heif_channel_B, width, height, get_bits_per_pixel(heif_channel_B), limits)) {
1399
0
        return err;
1400
0
      }
1401
0
    }
1402
21
    else if (get_colorspace() == heif_colorspace_monochrome) {
1403
15
      if (!has_channel(heif_channel_Y)) {
1404
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "monochrome input with no Y plane"};
1405
0
      }
1406
1407
15
      if (auto err = out_img->add_plane(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y), limits)) {
1408
0
        return err;
1409
0
      }
1410
15
    }
1411
6
    else if (get_colorspace() == heif_colorspace_YCbCr) {
1412
6
      if (!has_channel(heif_channel_Y) ||
1413
6
          !has_channel(heif_channel_Cb) ||
1414
6
          !has_channel(heif_channel_Cr)) {
1415
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "YCbCr image without Y,Cb,Cr planes"};
1416
0
      }
1417
1418
6
      uint32_t cw, ch;
1419
6
      get_subsampled_size(width, height, heif_channel_Cb, get_chroma_format(), &cw, &ch);
1420
6
      if (auto err = out_img->add_plane(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y), limits)) {
1421
0
        return err;
1422
0
      }
1423
6
      if (auto err = out_img->add_plane(heif_channel_Cb, cw, ch, get_bits_per_pixel(heif_channel_Cb), limits)) {
1424
0
        return err;
1425
0
      }
1426
6
      if (auto err = out_img->add_plane(heif_channel_Cr, cw, ch, get_bits_per_pixel(heif_channel_Cr), limits)) {
1427
0
        return err;
1428
0
      }
1429
6
    }
1430
0
    else {
1431
0
      return {heif_error_Invalid_input, heif_suberror_Unspecified, "unknown color configuration"};
1432
0
    }
1433
1434
21
    if (has_channel(heif_channel_Alpha)) {
1435
0
      if (auto err = out_img->add_plane(heif_channel_Alpha, width, height, get_bits_per_pixel(heif_channel_Alpha), limits)) {
1436
0
        return err;
1437
0
      }
1438
0
    }
1439
21
  }
1440
1441
1442
  // --- scale all channels
1443
1444
21
  int nInterleaved = num_interleaved_pixels_per_plane(m_chroma);
1445
21
  if (nInterleaved > 1) {
1446
0
    auto plane_iter = m_planes.find(heif_channel_interleaved);
1447
0
    assert(plane_iter != m_planes.end()); // the plane must exist since we have an interleaved chroma format
1448
0
    const ImagePlane& plane = plane_iter->second;
1449
1450
0
    uint32_t out_w = out_img->get_width(heif_channel_interleaved);
1451
0
    uint32_t out_h = out_img->get_height(heif_channel_interleaved);
1452
1453
0
    if (plane.m_bit_depth <= 8) {
1454
      // SDR interleaved
1455
1456
0
      size_t in_stride = plane.stride;
1457
0
      const auto* in_data = static_cast<const uint8_t*>(plane.mem);
1458
1459
0
      size_t out_stride = 0;
1460
0
      auto* out_data = out_img->get_plane(heif_channel_interleaved, &out_stride);
1461
1462
0
      for (uint32_t y = 0; y < out_h; y++) {
1463
0
        uint32_t iy = y * m_height / height;
1464
1465
0
        for (uint32_t x = 0; x < out_w; x++) {
1466
0
          uint32_t ix = x * m_width / width;
1467
1468
0
          for (int c = 0; c < nInterleaved; c++) {
1469
0
            out_data[y * out_stride + x * nInterleaved + c] = in_data[iy * in_stride + ix * nInterleaved + c];
1470
0
          }
1471
0
        }
1472
0
      }
1473
0
    }
1474
0
    else {
1475
      // HDR interleaved
1476
      // TODO: untested
1477
1478
0
      size_t in_stride = plane.stride;
1479
0
      const uint16_t* in_data = static_cast<const uint16_t*>(plane.mem);
1480
1481
0
      size_t out_stride = 0;
1482
0
      uint16_t* out_data = out_img->get_channel<uint16_t>(heif_channel_interleaved, &out_stride);
1483
1484
0
      in_stride /= 2;
1485
1486
0
      for (uint32_t y = 0; y < out_h; y++) {
1487
0
        uint32_t iy = y * m_height / height;
1488
1489
0
        for (uint32_t x = 0; x < out_w; x++) {
1490
0
          uint32_t ix = x * m_width / width;
1491
1492
0
          for (int c = 0; c < nInterleaved; c++) {
1493
0
            out_data[y * out_stride + x * nInterleaved + c] = in_data[iy * in_stride + ix * nInterleaved + c];
1494
0
          }
1495
0
        }
1496
0
      }
1497
0
    }
1498
0
  }
1499
21
  else {
1500
33
    for (const auto& plane_pair : m_planes) {
1501
33
      heif_channel channel = plane_pair.first;
1502
33
      const ImagePlane& plane = plane_pair.second;
1503
1504
33
      if (!out_img->has_channel(channel)) {
1505
0
        return {heif_error_Invalid_input, heif_suberror_Unspecified, "scaling input has extra color plane"};
1506
0
      }
1507
1508
1509
33
      uint32_t out_w = out_img->get_width(channel);
1510
33
      uint32_t out_h = out_img->get_height(channel);
1511
1512
33
      if (plane.m_bit_depth <= 8) {
1513
        // SDR planar
1514
1515
24
        size_t in_stride = plane.stride;
1516
24
        const auto* in_data = static_cast<const uint8_t*>(plane.mem);
1517
1518
24
        size_t out_stride = 0;
1519
24
        auto* out_data = out_img->get_plane(channel, &out_stride);
1520
1521
1.42k
        for (uint32_t y = 0; y < out_h; y++) {
1522
1.40k
          uint32_t iy = y * m_height / height;
1523
1524
178k
          for (uint32_t x = 0; x < out_w; x++) {
1525
176k
            uint32_t ix = x * m_width / width;
1526
1527
176k
            out_data[y * out_stride + x] = in_data[iy * in_stride + ix];
1528
176k
          }
1529
1.40k
        }
1530
24
      }
1531
9
      else {
1532
        // HDR planar
1533
1534
9
        size_t in_stride = plane.stride;
1535
9
        const uint16_t* in_data = static_cast<const uint16_t*>(plane.mem);
1536
1537
9
        size_t out_stride = 0;
1538
9
        uint16_t* out_data = out_img->get_channel<uint16_t>(channel, &out_stride);
1539
1540
9
        in_stride /= 2;
1541
1542
26
        for (uint32_t y = 0; y < out_h; y++) {
1543
17
          uint32_t iy = y * m_height / height;
1544
1545
42
          for (uint32_t x = 0; x < out_w; x++) {
1546
25
            uint32_t ix = x * m_width / width;
1547
1548
25
            out_data[y * out_stride + x] = in_data[iy * in_stride + ix];
1549
25
          }
1550
17
        }
1551
9
      }
1552
33
    }
1553
21
  }
1554
1555
21
  return Error::Ok;
1556
21
}
1557
1558
1559
void HeifPixelImage::forward_all_metadata_from(const std::shared_ptr<const HeifPixelImage>& src_image)
1560
0
{
1561
0
  set_color_profile_nclx(src_image->get_color_profile_nclx());
1562
0
  set_color_profile_icc(src_image->get_color_profile_icc());
1563
1564
0
  if (src_image->has_nonsquare_pixel_ratio()) {
1565
0
    uint32_t h,v;
1566
0
    src_image->get_pixel_ratio(&h,&v);
1567
0
    set_pixel_ratio(h,v);
1568
0
  }
1569
1570
0
  if (src_image->has_clli()) {
1571
0
    set_clli(src_image->get_clli());
1572
0
  }
1573
1574
0
  if (src_image->has_mdcv()) {
1575
0
    set_mdcv(src_image->get_mdcv());
1576
0
  }
1577
1578
0
  set_premultiplied_alpha(src_image->is_premultiplied_alpha());
1579
1580
  // TODO: TAI timestamp and contentID (once we merge that branch)
1581
1582
  // TODO: should we also forward the warnings? It might be better to do that in ImageItem_Grid.
1583
0
}
1584
1585
1586
void HeifPixelImage::debug_dump() const
1587
0
{
1588
0
  auto channels = get_channel_set();
1589
0
  for (auto c : channels) {
1590
0
    size_t stride = 0;
1591
0
    const uint8_t* p = get_plane(c, &stride);
1592
1593
0
    for (int y = 0; y < 8; y++) {
1594
0
      for (int x = 0; x < 8; x++) {
1595
0
        printf("%02x ", p[y * stride + x]);
1596
0
      }
1597
0
      printf("\n");
1598
0
    }
1599
0
  }
1600
0
}
1601
1602
Error HeifPixelImage::create_clone_image_at_new_size(const std::shared_ptr<const HeifPixelImage>& source, uint32_t w, uint32_t h,
1603
                                                     const heif_security_limits* limits)
1604
0
{
1605
0
  heif_colorspace colorspace = source->get_colorspace();
1606
0
  heif_chroma chroma = source->get_chroma_format();
1607
1608
0
  create(w, h, colorspace, chroma);
1609
1610
0
  switch (colorspace) {
1611
0
    case heif_colorspace_monochrome:
1612
0
      if (auto err = add_plane(heif_channel_Y, w, h, source->get_bits_per_pixel(heif_channel_Y), limits)) {
1613
0
        return err;
1614
0
      }
1615
0
      break;
1616
0
    case heif_colorspace_YCbCr:
1617
0
      if (auto err = add_plane(heif_channel_Y, w, h, source->get_bits_per_pixel(heif_channel_Y), limits)) {
1618
0
        return err;
1619
0
      }
1620
0
      if (auto err = add_plane(heif_channel_Cb, chroma_width(w, chroma), chroma_height(h, chroma), source->get_bits_per_pixel(heif_channel_Cb), limits)) {
1621
0
        return err;
1622
0
      }
1623
0
      if (auto err = add_plane(heif_channel_Cr, chroma_width(w, chroma), chroma_height(h, chroma), source->get_bits_per_pixel(heif_channel_Cr), limits)) {
1624
0
        return err;
1625
0
      }
1626
0
      break;
1627
0
    case heif_colorspace_RGB:
1628
0
      if (auto err = add_plane(heif_channel_R, w, h, source->get_bits_per_pixel(heif_channel_R), limits)) {
1629
0
        return err;
1630
0
      }
1631
0
      if (auto err = add_plane(heif_channel_G, w, h, source->get_bits_per_pixel(heif_channel_G), limits)) {
1632
0
        return err;
1633
0
      }
1634
0
      if (auto err = add_plane(heif_channel_B, w, h, source->get_bits_per_pixel(heif_channel_B), limits)) {
1635
0
        return err;
1636
0
      }
1637
0
      break;
1638
0
    default:
1639
0
      assert(false);
1640
0
      break;
1641
0
  }
1642
1643
0
  if (source->has_alpha()) {
1644
0
      if (auto err = add_plane(heif_channel_Alpha, w, h, source->get_bits_per_pixel(heif_channel_Alpha), limits)) {
1645
0
        return err;
1646
0
      }
1647
0
  }
1648
1649
0
  return Error::Ok;
1650
0
}
1651
1652
1653
Result<std::shared_ptr<HeifPixelImage>>
1654
HeifPixelImage::extract_image_area(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h,
1655
                                   const heif_security_limits* limits) const
1656
0
{
1657
0
  uint32_t minW = std::min(w, get_width() - x0);
1658
0
  uint32_t minH = std::min(h, get_height() - y0);
1659
1660
0
  auto areaImg = std::make_shared<HeifPixelImage>();
1661
0
  Error err = areaImg->create_clone_image_at_new_size(shared_from_this(), minW, minH, limits);
1662
0
  if (err) {
1663
0
    return err;
1664
0
  }
1665
1666
0
  std::set<enum heif_channel> channels = get_channel_set();
1667
0
  heif_chroma chroma = get_chroma_format();
1668
1669
0
  for (heif_channel channel : channels) {
1670
1671
0
    size_t src_stride;
1672
0
    const uint8_t* src_data = get_plane(channel, &src_stride);
1673
1674
0
    size_t out_stride;
1675
0
    uint8_t* out_data = areaImg->get_plane(channel, &out_stride);
1676
1677
0
    if (areaImg->get_bits_per_pixel(channel) != get_bits_per_pixel(channel)) {
1678
0
      return Error{
1679
0
        heif_error_Invalid_input,
1680
0
        heif_suberror_Wrong_tile_image_pixel_depth
1681
0
      };
1682
0
    }
1683
1684
0
    uint32_t copy_width = channel_width(minW, chroma, channel);
1685
0
    uint32_t copy_height = channel_height(minH, chroma, channel);
1686
1687
0
    copy_width *= get_storage_bits_per_pixel(channel) / 8;
1688
1689
0
    uint32_t xs = channel_width(x0, chroma, channel);
1690
0
    uint32_t ys = channel_height(y0, chroma, channel);
1691
0
    xs *= get_storage_bits_per_pixel(channel) / 8;
1692
1693
0
    for (uint32_t py = 0; py < copy_height; py++) {
1694
0
      memcpy(out_data + py * out_stride,
1695
0
             src_data + xs + (ys + py) * src_stride,
1696
0
             copy_width);
1697
0
    }
1698
0
  }
1699
1700
0
  err = areaImg->extend_to_size_with_zero(w,h,limits);
1701
0
  if (err) {
1702
0
    return err;
1703
0
  }
1704
1705
0
  return areaImg;
1706
0
}