/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 | } |