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