/src/libheif/libheif/image-items/image_item.cc
Line | Count | Source |
1 | | /* |
2 | | * HEIF image base codec. |
3 | | * Copyright (c) 2024 Dirk Farin <dirk.farin@gmail.com> |
4 | | * |
5 | | * This file is part of libheif. |
6 | | * |
7 | | * libheif is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as |
9 | | * published by the Free Software Foundation, either version 3 of |
10 | | * the License, or (at your option) any later version. |
11 | | * |
12 | | * libheif is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libheif. If not, see <http://www.gnu.org/licenses/>. |
19 | | */ |
20 | | |
21 | | #include "image_item.h" |
22 | | #include "mask_image.h" |
23 | | #include "context.h" |
24 | | #include "file.h" |
25 | | #include "jpeg.h" |
26 | | #include "jpeg2000.h" |
27 | | #include "avif.h" |
28 | | #include "avc.h" |
29 | | #include "hevc.h" |
30 | | #include "grid.h" |
31 | | #include "overlay.h" |
32 | | #include "iden.h" |
33 | | #include "tiled.h" |
34 | | #include "codecs/decoder.h" |
35 | | #include "color-conversion/colorconversion.h" |
36 | | #include "api_structs.h" |
37 | | #include "plugin_registry.h" |
38 | | #include "security_limits.h" |
39 | | |
40 | | #include <algorithm> |
41 | | #include <limits> |
42 | | #include <cassert> |
43 | | #include <cstring> |
44 | | #include <sstream> |
45 | | //#include <ranges> |
46 | | |
47 | | #if WITH_UNCOMPRESSED_CODEC |
48 | | #include "image-items/unc_image.h" |
49 | | #endif |
50 | | |
51 | | |
52 | | ImageItem::ImageItem(HeifContext* context) |
53 | 20.4k | : m_heif_context(context) |
54 | 20.4k | { |
55 | 20.4k | memset(&m_depth_representation_info, 0, sizeof(m_depth_representation_info)); |
56 | 20.4k | } |
57 | | |
58 | | |
59 | | ImageItem::ImageItem(HeifContext* context, heif_item_id id) |
60 | 20.4k | : ImageItem(context) |
61 | 20.4k | { |
62 | 20.4k | m_id = id; |
63 | 20.4k | } |
64 | | |
65 | | |
66 | | bool ImageItem::is_property_essential(const std::shared_ptr<Box>& property) const |
67 | 0 | { |
68 | 0 | if (property->get_short_type() == fourcc("ispe")) { |
69 | 0 | return is_ispe_essential(); |
70 | 0 | } |
71 | 0 | else { |
72 | 0 | return property->is_essential(); |
73 | 0 | } |
74 | 0 | } |
75 | | |
76 | | |
77 | | std::shared_ptr<HeifFile> ImageItem::get_file() const |
78 | 26.8k | { |
79 | 26.8k | return m_heif_context->get_heif_file(); |
80 | 26.8k | } |
81 | | |
82 | | |
83 | | heif_property_id ImageItem::add_property(const std::shared_ptr<Box>& property, bool essential) |
84 | 3.56k | { |
85 | 3.56k | if (!property) { |
86 | 85 | return 0; |
87 | 85 | } |
88 | | |
89 | | // HeifFile::add_property() deduplicates the property box, so only remember it here if the |
90 | | // item does not hold it yet. The returned id is the position in the item's property list and |
91 | | // is correct in both cases. |
92 | 3.47k | if (std::find(m_properties.begin(), m_properties.end(), property) == m_properties.end()) { |
93 | 3.47k | m_properties.push_back(property); |
94 | 3.47k | } |
95 | | |
96 | 3.47k | return get_file()->add_property(get_id(), property, essential); |
97 | 3.56k | } |
98 | | |
99 | | |
100 | | heif_property_id ImageItem::add_property_without_deduplication(const std::shared_ptr<Box>& property, bool essential) |
101 | 0 | { |
102 | 0 | if (!property) { |
103 | 0 | return 0; |
104 | 0 | } |
105 | | |
106 | 0 | m_properties.push_back(property); |
107 | 0 | return get_file()->add_property_without_deduplication(get_id(), property, essential); |
108 | 0 | } |
109 | | |
110 | | |
111 | | heif_compression_format ImageItem::compression_format_from_fourcc_infe_type(uint32_t type) |
112 | 0 | { |
113 | 0 | switch (type) { |
114 | 0 | case fourcc("jpeg"): |
115 | 0 | return heif_compression_JPEG; |
116 | 0 | case fourcc("hvc1"): |
117 | 0 | return heif_compression_HEVC; |
118 | 0 | case fourcc("av01"): |
119 | 0 | return heif_compression_AV1; |
120 | 0 | case fourcc("vvc1"): |
121 | 0 | return heif_compression_VVC; |
122 | 0 | case fourcc("j2k1"): |
123 | 0 | return heif_compression_JPEG2000; |
124 | 0 | case fourcc("unci"): |
125 | 0 | return heif_compression_uncompressed; |
126 | 0 | case fourcc("mski"): |
127 | 0 | return heif_compression_mask; |
128 | 0 | default: |
129 | 0 | return heif_compression_undefined; |
130 | 0 | } |
131 | 0 | } |
132 | | |
133 | | uint32_t ImageItem::compression_format_to_fourcc_infe_type(heif_compression_format format) |
134 | 0 | { |
135 | 0 | switch (format) { |
136 | 0 | case heif_compression_JPEG: |
137 | 0 | return fourcc("jpeg"); |
138 | 0 | case heif_compression_HEVC: |
139 | 0 | return fourcc("hvc1"); |
140 | 0 | case heif_compression_AV1: |
141 | 0 | return fourcc("av01"); |
142 | 0 | case heif_compression_VVC: |
143 | 0 | return fourcc("vvc1"); |
144 | 0 | case heif_compression_JPEG2000: |
145 | 0 | return fourcc("j2k1"); |
146 | 0 | case heif_compression_uncompressed: |
147 | 0 | return fourcc("unci"); |
148 | 0 | case heif_compression_mask: |
149 | 0 | return fourcc("mski"); |
150 | 0 | default: |
151 | 0 | return 0; |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | | |
156 | | std::shared_ptr<ImageItem> ImageItem::alloc_for_infe_box(HeifContext* ctx, const std::shared_ptr<Box_infe>& infe) |
157 | 20.4k | { |
158 | 20.4k | uint32_t item_type = infe->get_item_type_4cc(); |
159 | 20.4k | heif_item_id id = infe->get_item_ID(); |
160 | | |
161 | 20.4k | if (item_type == fourcc("jpeg") || |
162 | 20.1k | (item_type == fourcc("mime") && infe->get_content_type() == "image/jpeg")) { |
163 | 275 | return std::make_shared<ImageItem_JPEG>(ctx, id); |
164 | 275 | } |
165 | 20.1k | else if (item_type == fourcc("hvc1")) { |
166 | 12.3k | return std::make_shared<ImageItem_HEVC>(ctx, id); |
167 | 12.3k | } |
168 | 7.81k | else if (item_type == fourcc("av01")) { |
169 | 2.23k | return std::make_shared<ImageItem_AVIF>(ctx, id); |
170 | 2.23k | } |
171 | 5.57k | else if (item_type == fourcc("vvc1")) { |
172 | 15 | return std::make_shared<ImageItem_VVC>(ctx, id); |
173 | 15 | } |
174 | 5.56k | else if (item_type == fourcc("avc1")) { |
175 | 21 | return std::make_shared<ImageItem_AVC>(ctx, id); |
176 | 21 | } |
177 | 5.54k | else if (item_type == fourcc("unci")) { |
178 | | #if WITH_UNCOMPRESSED_CODEC |
179 | | return std::make_shared<ImageItem_uncompressed>(ctx, id); |
180 | | #else |
181 | | // It is an image item type that we do not support. Thus, generate an ImageItem_Error. |
182 | | |
183 | 11 | std::stringstream sstr; |
184 | 11 | sstr << "Image item of type '" << fourcc_to_string(item_type) << "' is not supported."; |
185 | 11 | Error err{ heif_error_Unsupported_feature, heif_suberror_Unsupported_image_type, sstr.str() }; |
186 | 11 | return std::make_shared<ImageItem_Error>(ctx, item_type, id, err); |
187 | 11 | #endif |
188 | 11 | } |
189 | 5.53k | else if (item_type == fourcc("j2k1")) { |
190 | 28 | return std::make_shared<ImageItem_JPEG2000>(ctx, id); |
191 | 28 | } |
192 | 5.50k | else if (item_type == fourcc("lhv1")) { |
193 | 9 | return std::make_shared<ImageItem_Error>(ctx, item_type, id, |
194 | 9 | Error{heif_error_Unsupported_feature, |
195 | 9 | heif_suberror_Unsupported_image_type, |
196 | 9 | "Layered HEVC images (lhv1) are not supported yet"}); |
197 | 9 | } |
198 | 5.49k | else if (item_type == fourcc("mski")) { |
199 | 17 | return std::make_shared<ImageItem_mask>(ctx, id); |
200 | 17 | } |
201 | 5.47k | else if (item_type == fourcc("grid")) { |
202 | 574 | return std::make_shared<ImageItem_Grid>(ctx, id); |
203 | 574 | } |
204 | 4.90k | else if (item_type == fourcc("iovl")) { |
205 | 84 | return std::make_shared<ImageItem_Overlay>(ctx, id); |
206 | 84 | } |
207 | 4.81k | else if (item_type == fourcc("iden")) { |
208 | 38 | return std::make_shared<ImageItem_iden>(ctx, id); |
209 | 38 | } |
210 | | #if HEIF_ENABLE_EXPERIMENTAL_FEATURES |
211 | | else if (item_type == fourcc("tili")) { |
212 | | return std::make_shared<ImageItem_Tiled>(ctx, id); |
213 | | } |
214 | | #endif |
215 | 4.78k | else { |
216 | | // This item has an unknown type. It could be an image or anything else. |
217 | | // Do not process the item. |
218 | | |
219 | 4.78k | return nullptr; |
220 | 4.78k | } |
221 | 20.4k | } |
222 | | |
223 | | |
224 | | std::shared_ptr<ImageItem> ImageItem::alloc_for_compression_format(HeifContext* ctx, heif_compression_format format) |
225 | 0 | { |
226 | 0 | switch (format) { |
227 | 0 | case heif_compression_JPEG: |
228 | 0 | return std::make_shared<ImageItem_JPEG>(ctx); |
229 | 0 | case heif_compression_HEVC: |
230 | 0 | return std::make_shared<ImageItem_HEVC>(ctx); |
231 | 0 | case heif_compression_AV1: |
232 | 0 | return std::make_shared<ImageItem_AVIF>(ctx); |
233 | 0 | case heif_compression_VVC: |
234 | 0 | return std::make_shared<ImageItem_VVC>(ctx); |
235 | 0 | case heif_compression_AVC: |
236 | 0 | return std::make_shared<ImageItem_AVC>(ctx); |
237 | | #if WITH_UNCOMPRESSED_CODEC |
238 | | case heif_compression_uncompressed: |
239 | | return std::make_shared<ImageItem_uncompressed>(ctx); |
240 | | #endif |
241 | 0 | case heif_compression_JPEG2000: |
242 | 0 | case heif_compression_HTJ2K: |
243 | 0 | return std::make_shared<ImageItem_JPEG2000>(ctx); |
244 | 0 | case heif_compression_mask: |
245 | 0 | return std::make_shared<ImageItem_mask>(ctx); |
246 | 0 | default: |
247 | 0 | assert(false); |
248 | 0 | return nullptr; |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | | |
253 | | Result<Encoder::CodedImageData> ImageItem::encode_to_bitstream_and_boxes(const std::shared_ptr<HeifPixelImage>& image, |
254 | | heif_encoder* encoder, |
255 | | const heif_encoding_options& options, |
256 | | heif_image_input_class input_class) |
257 | 0 | { |
258 | | // === generate compressed image bitstream |
259 | |
|
260 | 0 | Result<Encoder::CodedImageData> encodeResult = encode(image, encoder, options, input_class); |
261 | 0 | if (!encodeResult) { |
262 | 0 | return encodeResult; |
263 | 0 | } |
264 | | |
265 | 0 | Encoder::CodedImageData& codedImage = *encodeResult; |
266 | | |
267 | | // === generate properties |
268 | | |
269 | | // --- choose which color profile to put into 'colr' box |
270 | |
|
271 | 0 | auto colr_boxes = add_color_profile(image, options, input_class, options.output_nclx_profile); |
272 | 0 | codedImage.properties.insert(codedImage.properties.end(), |
273 | 0 | colr_boxes.begin(), |
274 | 0 | colr_boxes.end()); |
275 | | |
276 | | |
277 | | // --- ispe |
278 | | // Note: 'ispe' must come before the transformation properties |
279 | |
|
280 | 0 | uint32_t input_width, input_height; |
281 | 0 | input_width = image->get_width(); |
282 | 0 | input_height = image->get_height(); |
283 | | |
284 | | // --- get the real size of the encoded image |
285 | | |
286 | | // highest priority: codedImageData |
287 | 0 | uint32_t encoded_width = codedImage.encoded_image_width; |
288 | 0 | uint32_t encoded_height = codedImage.encoded_image_height; |
289 | | |
290 | | // second priority: query plugin API |
291 | 0 | if (encoded_width == 0 && |
292 | 0 | encoder->plugin->plugin_api_version >= 3 && |
293 | 0 | encoder->plugin->query_encoded_size != nullptr) { |
294 | |
|
295 | 0 | encoder->plugin->query_encoded_size(encoder->encoder, |
296 | 0 | input_width, input_height, |
297 | 0 | &encoded_width, |
298 | 0 | &encoded_height); |
299 | 0 | } |
300 | 0 | else if (encoded_width == 0) { |
301 | | // fallback priority: use input size |
302 | 0 | encoded_width = input_width; |
303 | 0 | encoded_height = input_height; |
304 | 0 | } |
305 | |
|
306 | 0 | auto ispe = std::make_shared<Box_ispe>(); |
307 | 0 | ispe->set_size(encoded_width, encoded_height); |
308 | 0 | ispe->set_is_essential(is_ispe_essential()); |
309 | 0 | codedImage.properties.push_back(ispe); |
310 | | |
311 | | |
312 | | // --- clap (if needed) |
313 | |
|
314 | 0 | if (input_width != encoded_width || |
315 | 0 | input_height != encoded_height) { |
316 | |
|
317 | 0 | auto clap = std::make_shared<Box_clap>(); |
318 | 0 | if (Error err = clap->set(input_width, input_height, encoded_width, encoded_height)) { |
319 | 0 | return err; |
320 | 0 | } |
321 | 0 | codedImage.properties.push_back(clap); |
322 | 0 | } |
323 | | |
324 | | |
325 | | |
326 | | // --- add common metadata properties (pixi, ...) |
327 | | |
328 | 0 | auto colorspace = image->get_colorspace(); |
329 | 0 | auto chroma = image->get_chroma_format(); |
330 | | |
331 | | |
332 | | // --- write PIXI property |
333 | |
|
334 | 0 | std::shared_ptr<Box_pixi> pixi = std::make_shared<Box_pixi>(); |
335 | 0 | bool valid_pixi = false; |
336 | |
|
337 | 0 | if (colorspace == heif_colorspace_filter_array) { |
338 | | // Skip pixi for filter array images — bit depth info is in uncC |
339 | 0 | } |
340 | 0 | else if (colorspace == heif_colorspace_monochrome) { |
341 | 0 | valid_pixi = pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y)); |
342 | 0 | } |
343 | 0 | else if (colorspace == heif_colorspace_YCbCr) { |
344 | 0 | valid_pixi = (pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y)) && |
345 | 0 | pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cb)) && |
346 | 0 | pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cr))); |
347 | 0 | } |
348 | 0 | else if (colorspace == heif_colorspace_RGB) { |
349 | 0 | if (chroma == heif_chroma_444) { |
350 | 0 | valid_pixi = (pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_R)) && |
351 | 0 | pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_G)) && |
352 | 0 | pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_B))); |
353 | 0 | } |
354 | 0 | else if (chroma == heif_chroma_interleaved_RGB || |
355 | 0 | chroma == heif_chroma_interleaved_RGBA || |
356 | 0 | chroma == heif_chroma_interleaved_RRGGBB_LE || |
357 | 0 | chroma == heif_chroma_interleaved_RRGGBB_BE || |
358 | 0 | chroma == heif_chroma_interleaved_RRGGBBAA_LE || |
359 | 0 | chroma == heif_chroma_interleaved_RRGGBBAA_BE) { |
360 | 0 | uint16_t bpp = image->get_bits_per_pixel(heif_channel_interleaved); |
361 | 0 | valid_pixi = (pixi->add_channel_bits(bpp) && |
362 | 0 | pixi->add_channel_bits(bpp) && |
363 | 0 | pixi->add_channel_bits(bpp)); |
364 | 0 | } |
365 | 0 | } |
366 | |
|
367 | 0 | if (valid_pixi) { |
368 | 0 | codedImage.properties.push_back(pixi); |
369 | 0 | } |
370 | | |
371 | | // --- generate properties for image extra data |
372 | | |
373 | | // copy over ImageDescription into image item |
374 | 0 | *static_cast<ImageDescription*>(this) = static_cast<ImageDescription>(*image); |
375 | |
|
376 | 0 | auto extra_data_properties = image->generate_property_boxes(false); |
377 | 0 | codedImage.properties.insert(codedImage.properties.end(), |
378 | 0 | extra_data_properties.begin(), |
379 | 0 | extra_data_properties.end()); |
380 | |
|
381 | 0 | return encodeResult; |
382 | 0 | } |
383 | | |
384 | | |
385 | | Error ImageItem::encode_to_item(HeifContext* ctx, |
386 | | const std::shared_ptr<HeifPixelImage>& image, |
387 | | heif_encoder* encoder, |
388 | | const heif_encoding_options& options, |
389 | | heif_image_input_class input_class) |
390 | 0 | { |
391 | 0 | uint32_t input_width = image->get_width(); |
392 | 0 | uint32_t input_height = image->get_height(); |
393 | |
|
394 | 0 | set_size(input_width, input_height); |
395 | | |
396 | | |
397 | | // compress image and assign data to item |
398 | |
|
399 | 0 | Result<Encoder::CodedImageData> codingResult = encode_to_bitstream_and_boxes(image, encoder, options, input_class); |
400 | 0 | if (!codingResult) { |
401 | 0 | return codingResult.error(); |
402 | 0 | } |
403 | | |
404 | 0 | Encoder::CodedImageData& codedImage = *codingResult; |
405 | |
|
406 | 0 | auto infe_result = ctx->get_heif_file()->add_new_infe_box(get_infe_type()); |
407 | 0 | if (!infe_result) { |
408 | 0 | return infe_result.error(); |
409 | 0 | } |
410 | 0 | auto infe_box = *infe_result; |
411 | 0 | heif_item_id image_id = infe_box->get_item_ID(); |
412 | 0 | set_id(image_id); |
413 | |
|
414 | 0 | ctx->get_heif_file()->append_iloc_data(image_id, codedImage.bitstream, 0); |
415 | | |
416 | | |
417 | | // set item properties |
418 | |
|
419 | 0 | for (auto& propertyBox : codingResult->properties) { |
420 | 0 | bool essential = is_property_essential(propertyBox); |
421 | | |
422 | | // TODO: can we simply use add_property() ? |
423 | 0 | int index = ctx->get_heif_file()->get_ipco_box()->find_or_append_child_box(propertyBox); |
424 | 0 | ctx->get_heif_file()->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{essential, |
425 | 0 | uint16_t(index + 1)}); |
426 | 0 | } |
427 | | |
428 | | |
429 | | // MIAF 7.3.6.7 |
430 | | // This is according to MIAF without Amd2. With Amd2, the restriction has been lifted and the image is MIAF compatible. |
431 | | // However, since AVIF is based on MIAF, the whole image would be invalid in that case. |
432 | | |
433 | | // We might remove this code at a later point in time when MIAF Amd2 is in wide use. |
434 | |
|
435 | 0 | if (encoder->plugin->compression_format != heif_compression_AV1 && |
436 | 0 | image->get_colorspace() == heif_colorspace_YCbCr) { |
437 | 0 | if (!is_integer_multiple_of_chroma_size(image->get_width(), |
438 | 0 | image->get_height(), |
439 | 0 | image->get_chroma_format())) { |
440 | 0 | mark_not_miaf_compatible(); |
441 | 0 | } |
442 | 0 | } |
443 | | |
444 | | // TODO: move this into encode_to_bistream_and_boxes() |
445 | 0 | if (Error err = ctx->get_heif_file()->add_orientation_properties(image_id, options.image_orientation)) { |
446 | 0 | return err; |
447 | 0 | } |
448 | | |
449 | 0 | return Error::Ok; |
450 | 0 | } |
451 | | |
452 | | bool ImageItem::has_ispe_resolution() const |
453 | 0 | { |
454 | 0 | return get_property<Box_ispe>() != nullptr; |
455 | 0 | } |
456 | | |
457 | | uint32_t ImageItem::get_ispe_width() const |
458 | 19.2k | { |
459 | 19.2k | auto ispe = get_property<Box_ispe>(); |
460 | 19.2k | if (!ispe) { |
461 | 1.03k | return 0; |
462 | 1.03k | } |
463 | 18.2k | else { |
464 | 18.2k | return ispe->get_width(); |
465 | 18.2k | } |
466 | 19.2k | } |
467 | | |
468 | | |
469 | | uint32_t ImageItem::get_ispe_height() const |
470 | 19.2k | { |
471 | 19.2k | auto ispe = get_property<Box_ispe>(); |
472 | 19.2k | if (!ispe) { |
473 | 1.03k | return 0; |
474 | 1.03k | } |
475 | 18.2k | else { |
476 | 18.2k | return ispe->get_height(); |
477 | 18.2k | } |
478 | 19.2k | } |
479 | | |
480 | | |
481 | | void ImageItem::get_tile_size(uint32_t& w, uint32_t& h) const |
482 | 0 | { |
483 | 0 | w = get_width(); |
484 | 0 | h = get_height(); |
485 | 0 | } |
486 | | |
487 | | |
488 | | Error ImageItem::postprocess_coded_image_colorspace(heif_colorspace* inout_colorspace, heif_chroma* inout_chroma) const |
489 | 17.1k | { |
490 | | #if 0 |
491 | | auto pixi = m_heif_context->get_heif_file()->get_property<Box_pixi>(id); |
492 | | if (pixi && pixi->get_num_channels() == 1) { |
493 | | *out_colorspace = heif_colorspace_monochrome; |
494 | | *out_chroma = heif_chroma_monochrome; |
495 | | } |
496 | | #endif |
497 | | |
498 | 17.1k | if (*inout_colorspace == heif_colorspace_YCbCr) { |
499 | 11.5k | auto nclx = get_color_profile_nclx(); |
500 | 11.5k | if (nclx.get_matrix_coefficients() == 0) { |
501 | 18 | *inout_colorspace = heif_colorspace_RGB; |
502 | 18 | *inout_chroma = heif_chroma_444; // TODO: this or keep the original chroma? |
503 | 18 | } |
504 | 11.5k | } |
505 | | |
506 | 17.1k | return Error::Ok; |
507 | 17.1k | } |
508 | | |
509 | | |
510 | | Error ImageItem::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const |
511 | 17.6k | { |
512 | 17.6k | auto decoderResult = get_decoder(); |
513 | 17.6k | if (!decoderResult) { |
514 | 66 | return decoderResult.error(); |
515 | 66 | } |
516 | | |
517 | 17.5k | auto decoder = *decoderResult; |
518 | | |
519 | 17.5k | Error err = decoder->get_coded_image_colorspace(out_colorspace, out_chroma); |
520 | 17.5k | if (err) { |
521 | 401 | return err; |
522 | 401 | } |
523 | | |
524 | 17.1k | postprocess_coded_image_colorspace(out_colorspace, out_chroma); |
525 | | |
526 | 17.1k | return Error::Ok; |
527 | 17.5k | } |
528 | | |
529 | | |
530 | | int ImageItem::get_luma_bits_per_pixel() const |
531 | 17.4k | { |
532 | 17.4k | auto decoderResult = get_decoder(); |
533 | 17.4k | if (!decoderResult) { |
534 | 0 | return -1; |
535 | 0 | } |
536 | | |
537 | 17.4k | auto decoder = *decoderResult; |
538 | | |
539 | 17.4k | return decoder->get_luma_bits_per_pixel(); |
540 | 17.4k | } |
541 | | |
542 | | |
543 | | int ImageItem::get_chroma_bits_per_pixel() const |
544 | 10.4k | { |
545 | 10.4k | auto decoderResult = get_decoder(); |
546 | 10.4k | if (!decoderResult) { |
547 | 0 | return -1; |
548 | 0 | } |
549 | | |
550 | 10.4k | auto decoder = *decoderResult; |
551 | | |
552 | 10.4k | return decoder->get_chroma_bits_per_pixel(); |
553 | 10.4k | } |
554 | | |
555 | | |
556 | | Result<Encoder::CodedImageData> ImageItem::encode(const std::shared_ptr<HeifPixelImage>& image, |
557 | | heif_encoder* h_encoder, |
558 | | const heif_encoding_options& options, |
559 | | heif_image_input_class input_class) |
560 | 0 | { |
561 | 0 | auto encoder = get_encoder(); |
562 | 0 | return encoder->encode(image, h_encoder, options, input_class); |
563 | 0 | } |
564 | | |
565 | | |
566 | | void ImageItem::set_alpha_channel(std::shared_ptr<ImageItem> img) |
567 | 135 | { |
568 | 135 | m_alpha_channel = std::move(img); |
569 | 135 | if (!m_alpha_channel) { |
570 | 0 | return; |
571 | 0 | } |
572 | | |
573 | | // Avoid emitting a duplicate Alpha description if set_alpha_channel was |
574 | | // called more than once. |
575 | 246 | for (const auto& d : get_component_descriptions()) { |
576 | 246 | if (d.channel == heif_channel_Alpha) { |
577 | 3 | return; |
578 | 3 | } |
579 | 246 | } |
580 | | |
581 | | // Bit depth of the alpha plane comes from the alpha aux item's coded |
582 | | // image (typically a monochrome HEVC/AVIF channel). Fall back to 8 bpp |
583 | | // if the decoder cannot tell us. |
584 | 132 | int alpha_bpp = m_alpha_channel->get_luma_bits_per_pixel(); |
585 | 132 | if (alpha_bpp <= 0) { |
586 | 27 | alpha_bpp = 8; |
587 | 27 | } |
588 | | |
589 | 132 | ComponentDescription desc; |
590 | 132 | desc.component_id = mint_component_id(); |
591 | 132 | desc.channel = heif_channel_Alpha; |
592 | 132 | desc.component_type = heif_cmpd_component_type_alpha; |
593 | 132 | desc.datatype = heif_component_datatype_unsigned_integer; |
594 | 132 | desc.bit_depth = static_cast<uint16_t>(alpha_bpp); |
595 | 132 | desc.width = get_ispe_width(); |
596 | 132 | desc.height = get_ispe_height(); |
597 | 132 | desc.has_data_plane = true; |
598 | 132 | add_component_description(std::move(desc)); |
599 | 132 | } |
600 | | |
601 | | |
602 | | void ImageItem::populate_component_descriptions() |
603 | 31.0k | { |
604 | | // Idempotent: a subclass override (e.g. unci) may already have populated. |
605 | 31.0k | if (!get_component_descriptions().empty()) { |
606 | 0 | return; |
607 | 0 | } |
608 | | |
609 | | // Visual codecs (HEVC/AVC/AVIF/JPEG/JPEG2000/VVC). Requires the decoder to |
610 | | // be initialized so we can read colorspace / chroma / bit depths from the |
611 | | // codec config. If the decoder isn't ready (e.g. on the encoder-output |
612 | | // path that doesn't call initialize_decoder, or for items whose codec is |
613 | | // not supported), bail out and leave m_components empty. |
614 | 31.0k | auto decoderResult = get_decoder(); |
615 | 31.0k | if (!decoderResult || !*decoderResult) { |
616 | 20.3k | return; |
617 | 20.3k | } |
618 | 10.7k | heif_colorspace colorspace = heif_colorspace_undefined; |
619 | 10.7k | heif_chroma chroma = heif_chroma_undefined; |
620 | 10.7k | if (Error err = get_coded_image_colorspace(&colorspace, &chroma); err) { |
621 | 283 | return; |
622 | 283 | } |
623 | | |
624 | 10.4k | uint32_t img_w = get_ispe_width(); |
625 | 10.4k | uint32_t img_h = get_ispe_height(); |
626 | 10.4k | int luma_bpp = get_luma_bits_per_pixel(); |
627 | 10.4k | int chroma_bpp = get_chroma_bits_per_pixel(); |
628 | 10.4k | if (luma_bpp <= 0) luma_bpp = 8; |
629 | 10.4k | if (chroma_bpp <= 0) chroma_bpp = luma_bpp; |
630 | | |
631 | 10.4k | auto emit = [this](heif_channel ch, uint16_t type, int bpp, |
632 | 25.3k | uint32_t w, uint32_t h) { |
633 | 25.3k | ComponentDescription desc; |
634 | 25.3k | desc.component_id = mint_component_id(); |
635 | 25.3k | desc.channel = ch; |
636 | 25.3k | desc.component_type = type; |
637 | 25.3k | desc.datatype = heif_component_datatype_unsigned_integer; |
638 | 25.3k | desc.bit_depth = static_cast<uint16_t>(bpp); |
639 | 25.3k | desc.width = w; |
640 | 25.3k | desc.height = h; |
641 | 25.3k | desc.has_data_plane = true; |
642 | 25.3k | add_component_description(std::move(desc)); |
643 | 25.3k | }; |
644 | | |
645 | 10.4k | switch (colorspace) { |
646 | 2.97k | case heif_colorspace_monochrome: |
647 | 2.97k | emit(heif_channel_Y, heif_cmpd_component_type_monochrome, luma_bpp, img_w, img_h); |
648 | 2.97k | break; |
649 | | |
650 | 7.47k | case heif_colorspace_YCbCr: { |
651 | 7.47k | uint32_t cw = channel_width(img_w, chroma, heif_channel_Cb); |
652 | 7.47k | uint32_t ch_ = channel_height(img_h, chroma, heif_channel_Cb); |
653 | 7.47k | emit(heif_channel_Y, heif_cmpd_component_type_Y, luma_bpp, img_w, img_h); |
654 | 7.47k | emit(heif_channel_Cb, heif_cmpd_component_type_Cb, chroma_bpp, cw, ch_); |
655 | 7.47k | emit(heif_channel_Cr, heif_cmpd_component_type_Cr, chroma_bpp, cw, ch_); |
656 | 7.47k | break; |
657 | 0 | } |
658 | | |
659 | 0 | case heif_colorspace_RGB: |
660 | 0 | emit(heif_channel_R, heif_cmpd_component_type_red, luma_bpp, img_w, img_h); |
661 | 0 | emit(heif_channel_G, heif_cmpd_component_type_green, luma_bpp, img_w, img_h); |
662 | 0 | emit(heif_channel_B, heif_cmpd_component_type_blue, luma_bpp, img_w, img_h); |
663 | 0 | break; |
664 | | |
665 | 0 | default: |
666 | | // Other colorspaces (filter_array, nonvisual) are unci-only and are |
667 | | // populated by the unci override. |
668 | 0 | break; |
669 | 10.4k | } |
670 | 10.4k | } |
671 | | |
672 | | |
673 | | bool ImageItem::populate_descriptions_from_child(const ImageItem& child, |
674 | | uint32_t child_w, uint32_t child_h) |
675 | 36 | { |
676 | 36 | const auto& child_descs = child.get_component_descriptions(); |
677 | 36 | if (child_descs.empty()) { |
678 | 36 | return false; |
679 | 36 | } |
680 | | |
681 | 0 | uint32_t img_w = get_ispe_width(); |
682 | 0 | uint32_t img_h = get_ispe_height(); |
683 | 0 | if (img_w == 0 || img_h == 0 || child_w == 0 || child_h == 0) { |
684 | 0 | return false; |
685 | 0 | } |
686 | | |
687 | 0 | for (const auto& src : child_descs) { |
688 | 0 | ComponentDescription d = src; |
689 | 0 | d.component_id = mint_component_id(); |
690 | 0 | if (src.has_data_plane) { |
691 | | // Preserve subsampling ratio: a half-size chroma plane in the child |
692 | | // becomes half of img_w/h in the wrapper. |
693 | 0 | uint64_t w64 = static_cast<uint64_t>(img_w) * src.width / child_w; |
694 | 0 | uint64_t h64 = static_cast<uint64_t>(img_h) * src.height / child_h; |
695 | 0 | d.width = static_cast<uint32_t>(w64); |
696 | 0 | d.height = static_cast<uint32_t>(h64); |
697 | 0 | } |
698 | 0 | add_component_description(std::move(d)); |
699 | 0 | } |
700 | 0 | return true; |
701 | 0 | } |
702 | | |
703 | | |
704 | | std::vector<std::shared_ptr<Box_colr> > |
705 | | ImageItem::add_color_profile(const std::shared_ptr<HeifPixelImage>& image, |
706 | | const heif_encoding_options& options, |
707 | | heif_image_input_class input_class, |
708 | | const heif_color_profile_nclx* target_heif_nclx) |
709 | 0 | { |
710 | 0 | std::vector<std::shared_ptr<Box_colr> > colr_boxes; |
711 | |
|
712 | 0 | if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { |
713 | | // No color profile for non-visual images (e.g. elevation data) |
714 | 0 | if (image->get_colorspace() == heif_colorspace_custom) { |
715 | 0 | return colr_boxes; |
716 | 0 | } |
717 | | |
718 | 0 | auto icc_profile = image->get_color_profile_icc(); |
719 | 0 | if (icc_profile) { |
720 | 0 | auto colr = std::make_shared<Box_colr>(); |
721 | 0 | colr->set_color_profile(icc_profile); |
722 | 0 | colr_boxes.push_back(colr); |
723 | 0 | } |
724 | | |
725 | | |
726 | | // save nclx profile |
727 | |
|
728 | 0 | bool save_nclx_profile = (options.output_nclx_profile != nullptr); |
729 | | |
730 | | // if there is an ICC profile, only save NCLX when we chose to save both profiles |
731 | 0 | if (icc_profile && !(options.version >= 3 && |
732 | 0 | options.save_two_colr_boxes_when_ICC_and_nclx_available)) { |
733 | 0 | save_nclx_profile = false; |
734 | 0 | } |
735 | | |
736 | | // we might have turned off nclx completely because macOS/iOS cannot read it |
737 | 0 | if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) { |
738 | 0 | save_nclx_profile = false; |
739 | 0 | } |
740 | |
|
741 | 0 | if (save_nclx_profile) { |
742 | 0 | auto target_nclx_profile = std::make_shared<color_profile_nclx>(); |
743 | 0 | target_nclx_profile->set_from_heif_color_profile_nclx(target_heif_nclx); |
744 | |
|
745 | 0 | auto colr = std::make_shared<Box_colr>(); |
746 | 0 | colr->set_color_profile(target_nclx_profile); |
747 | 0 | colr_boxes.push_back(colr); |
748 | 0 | } |
749 | 0 | } |
750 | | |
751 | 0 | return colr_boxes; |
752 | 0 | } |
753 | | |
754 | | |
755 | | Error ImageItem::transform_requested_tile_position_to_original_tile_position(uint32_t& tile_x, uint32_t& tile_y) const |
756 | 0 | { |
757 | 0 | Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties(); |
758 | 0 | if (!propertiesResult) { |
759 | 0 | return propertiesResult.error(); |
760 | 0 | } |
761 | | |
762 | | // The caller's (tile_x, tile_y) are in the *displayed* tile grid, so they |
763 | | // must be validated against the displayed dimensions, not the in-file ones. |
764 | | // For rotations of 90°/270° the displayed grid has its columns and rows |
765 | | // swapped relative to the file. Using the file dims (as before) both let |
766 | | // out-of-range coordinates through and produced unsigned underflows inside |
767 | | // the inverse-rotation formulas (e.g. `num_rows - 1 - tile_x` with |
768 | | // `tile_x >= num_rows`). |
769 | 0 | heif_image_tiling tiling = get_heif_image_tiling(); |
770 | 0 | if (Error err = process_image_transformations_on_tiling(tiling)) { |
771 | 0 | return err; |
772 | 0 | } |
773 | | |
774 | 0 | if (tile_x >= tiling.num_columns || tile_y >= tiling.num_rows) { |
775 | 0 | return {heif_error_Usage_error, |
776 | 0 | heif_suberror_Unspecified, |
777 | 0 | "Tile coordinate out of range for displayed image"}; |
778 | 0 | } |
779 | | |
780 | | // Walk the property chain in reverse, undoing each transformation as we go. |
781 | | // Track the current (intermediate) tile-grid dimensions so each inverse uses |
782 | | // the right extent and so 90°/270° rotations swap dims for subsequent steps. |
783 | 0 | uint32_t cur_cols = tiling.num_columns; |
784 | 0 | uint32_t cur_rows = tiling.num_rows; |
785 | |
|
786 | 0 | for (auto propIter = propertiesResult->rbegin(); propIter != propertiesResult->rend(); propIter++) { |
787 | 0 | if (auto irot = std::dynamic_pointer_cast<Box_irot>(*propIter)) { |
788 | 0 | switch (irot->get_rotation_ccw()) { |
789 | 0 | case 90: { |
790 | 0 | uint32_t tx0 = cur_rows - 1 - tile_y; |
791 | 0 | uint32_t ty0 = tile_x; |
792 | 0 | tile_x = tx0; |
793 | 0 | tile_y = ty0; |
794 | 0 | std::swap(cur_cols, cur_rows); |
795 | 0 | break; |
796 | 0 | } |
797 | 0 | case 270: { |
798 | 0 | uint32_t tx0 = tile_y; |
799 | 0 | uint32_t ty0 = cur_cols - 1 - tile_x; |
800 | 0 | tile_x = tx0; |
801 | 0 | tile_y = ty0; |
802 | 0 | std::swap(cur_cols, cur_rows); |
803 | 0 | break; |
804 | 0 | } |
805 | 0 | case 180: { |
806 | 0 | tile_x = cur_cols - 1 - tile_x; |
807 | 0 | tile_y = cur_rows - 1 - tile_y; |
808 | 0 | break; |
809 | 0 | } |
810 | 0 | case 0: |
811 | 0 | break; |
812 | 0 | default: |
813 | 0 | assert(false); |
814 | 0 | break; |
815 | 0 | } |
816 | 0 | } |
817 | | |
818 | 0 | if (auto imir = std::dynamic_pointer_cast<Box_imir>(*propIter)) { |
819 | 0 | switch (imir->get_mirror_direction()) { |
820 | 0 | case heif_transform_mirror_direction_horizontal: |
821 | 0 | tile_x = cur_cols - 1 - tile_x; |
822 | 0 | break; |
823 | 0 | case heif_transform_mirror_direction_vertical: |
824 | 0 | tile_y = cur_rows - 1 - tile_y; |
825 | 0 | break; |
826 | 0 | default: |
827 | 0 | assert(false); |
828 | 0 | break; |
829 | 0 | } |
830 | 0 | } |
831 | 0 | } |
832 | | |
833 | 0 | return Error::Ok; |
834 | 0 | } |
835 | | |
836 | | |
837 | | void ImageItem::set_clli(const heif_content_light_level& clli) |
838 | 0 | { |
839 | 0 | ImageDescription::set_clli(clli); |
840 | 0 | add_property(create_clli_box(), false); |
841 | 0 | } |
842 | | |
843 | | |
844 | | void ImageItem::set_mdcv(const heif_mastering_display_colour_volume& mdcv) |
845 | 0 | { |
846 | 0 | ImageDescription::set_mdcv(mdcv); |
847 | 0 | add_property(create_mdcv_box(), false); |
848 | 0 | } |
849 | | |
850 | | |
851 | | void ImageItem::set_amve(const heif_ambient_viewing_environment& amve) |
852 | 0 | { |
853 | 0 | ImageDescription::set_amve(amve); |
854 | 0 | add_property(create_amve_box(), false); |
855 | 0 | } |
856 | | |
857 | | |
858 | | void ImageItem::set_nominal_diffuse_white_luminance(uint32_t luminance) |
859 | 0 | { |
860 | 0 | ImageDescription::set_nominal_diffuse_white_luminance(luminance); |
861 | 0 | add_property(create_ndwt_box(), false); |
862 | 0 | } |
863 | | |
864 | | |
865 | | void ImageItem::set_pixel_ratio(uint32_t h, uint32_t v) |
866 | 0 | { |
867 | 0 | ImageDescription::set_pixel_ratio(h, v); |
868 | 0 | add_property(create_pasp_box(), false); |
869 | 0 | } |
870 | | |
871 | | |
872 | | void ImageItem::set_color_profile_nclx(const nclx_profile& profile) |
873 | 735 | { |
874 | 735 | ImageDescription::set_color_profile_nclx(profile); |
875 | 735 | add_property(create_colr_box_nclx(), false); |
876 | 735 | } |
877 | | |
878 | | |
879 | | void ImageItem::set_color_profile_icc(const std::shared_ptr<const color_profile_raw>& profile) |
880 | 2.82k | { |
881 | 2.82k | ImageDescription::set_color_profile_icc(profile); |
882 | 2.82k | add_property(create_colr_box_icc(), false); |
883 | 2.82k | } |
884 | | |
885 | | void ImageItem::set_omaf_image_projection(heif_omaf_image_projection projection) |
886 | 0 | { |
887 | 0 | ImageDescription::set_omaf_image_projection(projection); |
888 | 0 | add_property(create_prfr_box(), true); |
889 | 0 | } |
890 | | |
891 | | |
892 | | namespace { |
893 | | |
894 | | // Detect cycles in the decode reference graph reached from `root`, following the |
895 | | // same edges the decode recursion follows. This uses an explicit heap worklist |
896 | | // rather than recursion on purpose: the graph depth is influenced by the input |
897 | | // (a chain of derived items, and unbounded when the item-count limit is |
898 | | // disabled), so a recursive walk could exhaust the native stack and crash the |
899 | | // process before any decode, on the read path of an untrusted file. The worklist |
900 | | // grows on the heap instead, so depth is bounded only by available memory. |
901 | | // |
902 | | // It is a depth-first walk. `on_path` holds the items on the current |
903 | | // root-to-node path; reaching one that is already on the path is a cycle. |
904 | | // `verified` memoizes items whose subtree is already proven acyclic, so a shared |
905 | | // sub-image reached through several paths is visited once and the walk stays |
906 | | // linear rather than exponential in the number of root-to-item paths |
907 | | // (cf. the decode amplification bound, GHSA-x8xm-cm2c-cfc8). |
908 | | Error check_decode_reference_cycles(const ImageItem* root) |
909 | 6.66k | { |
910 | 6.66k | std::set<heif_item_id> on_path; // items on the current DFS path |
911 | 6.66k | std::set<heif_item_id> verified; // items whose subtree is proven acyclic |
912 | | |
913 | | // Collect the decode-input children of an item, in the exact order the decode |
914 | | // recursion follows them: the derived-image ('dimg') inputs (grid tiles, |
915 | | // overlay inputs, the 'iden' base) first, then the alpha ('auxl') auxiliary. |
916 | | // The returned shared_ptrs keep the child ImageItems alive for as long as the |
917 | | // frame that holds them stays on the worklist. |
918 | 7.47k | auto collect_children = [](const ImageItem* item) { |
919 | 7.47k | std::vector<std::shared_ptr<const ImageItem>> children; |
920 | 7.47k | auto file = item->get_file(); |
921 | 7.47k | auto iref = file ? file->get_iref_box() : nullptr; |
922 | 7.47k | if (iref) { |
923 | 1.56k | for (heif_item_id child_id : iref->get_references(item->get_id(), fourcc("dimg"))) { |
924 | 1.16k | if (auto child = item->get_context()->get_image(child_id, true)) { |
925 | 747 | children.push_back(std::move(child)); |
926 | 747 | } |
927 | 1.16k | } |
928 | 1.56k | } |
929 | 7.47k | if (const auto& alpha = item->get_alpha_channel()) { |
930 | 85 | children.push_back(alpha); |
931 | 85 | } |
932 | 7.47k | return children; |
933 | 7.47k | }; |
934 | | |
935 | | // One worklist frame per item currently on the DFS path. `next` is the index |
936 | | // of the child to descend into next; when it reaches the end, the item's whole |
937 | | // subtree has been proven acyclic and the item leaves the path. |
938 | 6.66k | struct Frame { |
939 | 6.66k | const ImageItem* item; |
940 | 6.66k | std::vector<std::shared_ptr<const ImageItem>> children; |
941 | 6.66k | size_t next = 0; |
942 | 6.66k | }; |
943 | | |
944 | 6.66k | std::vector<Frame> stack; |
945 | 6.66k | on_path.insert(root->get_id()); |
946 | 6.66k | stack.push_back(Frame{root, collect_children(root), 0}); |
947 | | |
948 | 14.9k | while (!stack.empty()) { |
949 | 8.29k | Frame& top = stack.back(); |
950 | | |
951 | 8.29k | if (top.next >= top.children.size()) { |
952 | | // All children proven acyclic: leave the DFS path and memoize the subtree. |
953 | 7.46k | heif_item_id done_id = top.item->get_id(); |
954 | 7.46k | on_path.erase(done_id); |
955 | 7.46k | verified.insert(done_id); |
956 | 7.46k | stack.pop_back(); |
957 | 7.46k | continue; |
958 | 7.46k | } |
959 | | |
960 | 830 | const ImageItem* child = top.children[top.next++].get(); |
961 | | // From here on `top` must not be used: the push_back below may reallocate |
962 | | // `stack` and invalidate the reference. |
963 | | |
964 | 830 | heif_item_id child_id = child->get_id(); |
965 | 830 | if (on_path.find(child_id) != on_path.end()) { |
966 | 3 | return {heif_error_Invalid_input, |
967 | 3 | heif_suberror_Item_reference_cycle, |
968 | 3 | "Image reference cycle"}; |
969 | 3 | } |
970 | 827 | if (verified.find(child_id) != verified.end()) { |
971 | 16 | continue; // subtree already proven acyclic; do not descend into it again |
972 | 16 | } |
973 | | |
974 | 811 | on_path.insert(child_id); |
975 | 811 | auto grandchildren = collect_children(child); |
976 | 811 | stack.push_back(Frame{child, std::move(grandchildren), 0}); |
977 | 811 | } |
978 | | |
979 | 6.65k | return Error::Ok; |
980 | 6.66k | } |
981 | | |
982 | | |
983 | | // --- MIAF derived-image dependency constraints (ISO/IEC 23000-22, clause 7.3.11) |
984 | | // |
985 | | // MIAF restricts the derivation chain to a fixed order. From base to top it is: |
986 | | // coded image(s) -> [iden] -> grid -> [iden] -> overlay -> [iden] |
987 | | // (7.3.11.1), plus: an 'iden' shall not be derived directly from another 'iden' |
988 | | // (7.3.11.2), and a grid tile that is an 'iden' must refer directly to a coded |
989 | | // image (7.3.11.4.1). So, ignoring 'iden', a chain may apply overlay above grid |
990 | | // above the coded base, each at most once. We model that with a "structural |
991 | | // rank": coded=0, grid=1, overlay=2. Walking from the top down, each derived |
992 | | // item must have rank <= the rank its position allows, and it lowers the rank |
993 | | // allowed for its own inputs (grid inputs must be coded; overlay inputs may be |
994 | | // grid or below). 'iden' is transparent to the rank but must not sit directly |
995 | | // on another 'iden'. Only the 'dimg' derivation is constrained here; auxiliary |
996 | | // images are checked as their own fresh chains. |
997 | | enum { MIAF_RANK_CODED = 0, MIAF_RANK_GRID = 1, MIAF_RANK_OVERLAY = 2 }; |
998 | | |
999 | | int miaf_structural_rank(const ImageItem* item, bool& is_iden) |
1000 | 351 | { |
1001 | 351 | uint32_t type = item->get_infe_type(); |
1002 | 351 | is_iden = (type == fourcc("iden")); |
1003 | 351 | if (type == fourcc("iovl")) { return MIAF_RANK_OVERLAY; } |
1004 | 351 | if (type == fourcc("grid")) { return MIAF_RANK_GRID; } |
1005 | 339 | return MIAF_RANK_CODED; // coded image, or 'iden' (rank unused when is_iden) |
1006 | 351 | } |
1007 | | |
1008 | | // `max_rank` is the highest structural rank allowed at this item's position; |
1009 | | // `parent_is_iden` is true when the immediate parent on the derivation path is |
1010 | | // an 'iden'. `verified` memoizes (item, max_rank, parent_is_iden) triples that |
1011 | | // already passed, keeping a shared sub-image from being re-walked per path. |
1012 | | Error check_miaf_derivation_constraints(const ImageItem* item, |
1013 | | int max_rank, bool parent_is_iden, |
1014 | | std::set<uint64_t>& verified) |
1015 | 351 | { |
1016 | 351 | heif_item_id id = item->get_id(); |
1017 | | |
1018 | 351 | bool is_iden = false; |
1019 | 351 | int rank = miaf_structural_rank(item, is_iden); |
1020 | | |
1021 | 351 | if (is_iden) { |
1022 | 2 | if (parent_is_iden) { |
1023 | 0 | return {heif_error_Invalid_input, heif_suberror_Unspecified, |
1024 | 0 | "MIAF: an 'iden' image is derived directly from another 'iden' image"}; |
1025 | 0 | } |
1026 | 2 | } |
1027 | 349 | else if (rank > max_rank) { |
1028 | 4 | return {heif_error_Invalid_input, heif_suberror_Unspecified, |
1029 | 4 | "MIAF: derived-image dependencies are not in the order allowed by ISO/IEC 23000-22"}; |
1030 | 4 | } |
1031 | | |
1032 | 347 | uint64_t key = (static_cast<uint64_t>(id) << 4) | |
1033 | 347 | (static_cast<uint64_t>(max_rank & 0x3) << 2) | |
1034 | 347 | (parent_is_iden ? 2u : 0u) | (is_iden ? 1u : 0u); |
1035 | 347 | if (!verified.insert(key).second) { |
1036 | 0 | return Error::Ok; // already verified in this context |
1037 | 0 | } |
1038 | | |
1039 | | // Rank budget passed to this item's own 'dimg' inputs. |
1040 | 347 | int child_max_rank; |
1041 | 347 | bool child_parent_is_iden; |
1042 | 347 | if (is_iden) { |
1043 | 2 | child_max_rank = max_rank; // transparent: inputs keep this position |
1044 | 2 | child_parent_is_iden = true; |
1045 | 2 | } |
1046 | 345 | else if (rank == MIAF_RANK_OVERLAY) { |
1047 | 0 | child_max_rank = MIAF_RANK_GRID; // overlay inputs: grid or below |
1048 | 0 | child_parent_is_iden = false; |
1049 | 0 | } |
1050 | 345 | else if (rank == MIAF_RANK_GRID) { |
1051 | 8 | child_max_rank = MIAF_RANK_CODED; // grid inputs: coded (or iden -> coded) |
1052 | 8 | child_parent_is_iden = false; |
1053 | 8 | } |
1054 | 337 | else { |
1055 | 337 | return Error::Ok; // coded image: leaf of the derivation chain |
1056 | 337 | } |
1057 | | |
1058 | 10 | auto file = item->get_file(); |
1059 | 10 | auto iref = file ? file->get_iref_box() : nullptr; |
1060 | 10 | if (iref) { |
1061 | 17 | for (heif_item_id child_id : iref->get_references(id, fourcc("dimg"))) { |
1062 | 17 | auto child = item->get_context()->get_image(child_id, true); |
1063 | 17 | if (child) { |
1064 | 4 | if (Error err = check_miaf_derivation_constraints(child.get(), child_max_rank, |
1065 | 4 | child_parent_is_iden, verified)) { |
1066 | 4 | return err; |
1067 | 4 | } |
1068 | 4 | } |
1069 | 17 | } |
1070 | 9 | } |
1071 | | |
1072 | | // An auxiliary (e.g. alpha) image is a separate image whose own derivation |
1073 | | // chain must independently satisfy MIAF, so check it as a fresh chain. |
1074 | 6 | if (auto alpha = item->get_alpha_channel()) { |
1075 | 0 | if (Error err = check_miaf_derivation_constraints(alpha.get(), MIAF_RANK_OVERLAY, |
1076 | 0 | /*parent_is_iden=*/false, verified)) { |
1077 | 0 | return err; |
1078 | 0 | } |
1079 | 0 | } |
1080 | | |
1081 | 6 | return Error::Ok; |
1082 | 6 | } |
1083 | | |
1084 | | } // namespace |
1085 | | |
1086 | | |
1087 | | Error ImageItem::verify_decodable() const |
1088 | 6.66k | { |
1089 | | // Always: reject a cyclic decode reference graph (both 'dimg' and 'auxl' |
1090 | | // edges) before decoding. See the declaration in image_item.h. |
1091 | 6.66k | if (Error err = check_decode_reference_cycles(this)) { |
1092 | 3 | return err; |
1093 | 3 | } |
1094 | | |
1095 | | // Optionally: enforce MIAF's restricted derived-image dependencies |
1096 | | // (ISO/IEC 23000-22, clause 7.3.11). Applied when the file declares the 'miaf' |
1097 | | // brand. |
1098 | | // |
1099 | | // TODO(v1.24.x): also apply this when a security-limits flag |
1100 | | // (always_apply_MIAF_derivation_constraints) is set, so that a malicious file |
1101 | | // cannot bypass the check simply by omitting the 'miaf' brand. That flag is a |
1102 | | // heif_security_limits API addition and therefore has to wait for v1.24.x. |
1103 | 6.65k | bool apply_miaf = false; |
1104 | 6.65k | if (auto file = get_file()) { |
1105 | 6.65k | if (auto ftyp = file->get_ftyp_box()) { |
1106 | 6.65k | apply_miaf = ftyp->has_compatible_brand(heif_brand2_miaf); |
1107 | 6.65k | } |
1108 | 6.65k | } |
1109 | | |
1110 | 6.65k | if (apply_miaf) { |
1111 | 347 | std::set<uint64_t> verified; |
1112 | 347 | if (Error err = check_miaf_derivation_constraints(this, MIAF_RANK_OVERLAY, |
1113 | 347 | /*parent_is_iden=*/false, verified)) { |
1114 | 4 | return err; |
1115 | 4 | } |
1116 | 347 | } |
1117 | | |
1118 | 6.65k | return Error::Ok; |
1119 | 6.65k | } |
1120 | | |
1121 | | |
1122 | | Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_image(const heif_decoding_options& options, |
1123 | | bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0, |
1124 | | DecodeTraversalState decode_state) const |
1125 | 6.72k | { |
1126 | | // Check for cycles before taking m_decode_mutex: a derived item that |
1127 | | // (transitively) references itself would otherwise re-enter decode_image() |
1128 | | // on the same ImageItem and self-deadlock on the non-recursive mutex. |
1129 | | // The matching insert lives inside decode_compressed_image() of derived |
1130 | | // items (grid/overlay/iden), so the current item is in decode_state only |
1131 | | // when called from one of its own descendants. |
1132 | | // |
1133 | | // Second-layer hardening, not required for correctness: the top-level decode |
1134 | | // already ran ImageItem::verify_decodable() (HeifContext::decode_image), which |
1135 | | // proves the whole reachable decode graph is acyclic before any recursion, so |
1136 | | // this per-path check can never fire on a graph that reached here. It is kept |
1137 | | // as a cheap in-decode backstop, and the same applies to the equivalent checks |
1138 | | // in decode_compressed_image() and in grid/overlay/iden. We may remove them in |
1139 | | // the future once verify_decodable() is the sole cycle guard. |
1140 | 6.72k | if (decode_state.processed_ids.contains(m_id)) { |
1141 | 0 | return Error{heif_error_Invalid_input, |
1142 | 0 | heif_suberror_Unspecified, |
1143 | 0 | "'iref' has cyclic references"}; |
1144 | 0 | } |
1145 | | |
1146 | | // Bound the total number of sub-image decodes for this top-level decode. |
1147 | | // Derived images (grid/iovl/iden) can reference the same base image through |
1148 | | // indirection, and because the cycle-detection set is per-path, a shared |
1149 | | // subtree is otherwise re-decoded once per path that reaches it, which grows |
1150 | | // as branch^depth for nested references. This is the single choke point that |
1151 | | // every item decode passes through. (GHSA-x8xm-cm2c-cfc8) |
1152 | 6.72k | if (!decode_state.count_decode()) { |
1153 | 0 | return Error{heif_error_Invalid_input, |
1154 | 0 | heif_suberror_Security_limit_exceeded, |
1155 | 0 | "Too many derived-image decode operations (possible reference amplification)"}; |
1156 | 0 | } |
1157 | | |
1158 | 6.72k | if (m_item_error) { |
1159 | 0 | return m_item_error; |
1160 | 0 | } |
1161 | | |
1162 | 6.72k | std::lock_guard<std::mutex> lock(m_decode_mutex); |
1163 | | |
1164 | | // --- check whether image size (according to 'ispe') exceeds maximum |
1165 | | |
1166 | 6.72k | if (!decode_tile_only) { |
1167 | 6.72k | auto ispe = get_property<Box_ispe>(); |
1168 | 6.72k | if (ispe) { |
1169 | 6.69k | Error err = check_for_valid_image_size(get_context()->get_security_limits(), ispe->get_width(), ispe->get_height()); |
1170 | 6.69k | if (err) { |
1171 | 8 | return err; |
1172 | 8 | } |
1173 | 6.69k | } |
1174 | 6.72k | } |
1175 | | |
1176 | | |
1177 | | // --- transform tile position |
1178 | | |
1179 | 6.72k | if (decode_tile_only && options.ignore_transformations == false) { |
1180 | 0 | if (Error error = transform_requested_tile_position_to_original_tile_position(tile_x0, tile_y0)) { |
1181 | 0 | return error; |
1182 | 0 | } |
1183 | 0 | } |
1184 | | |
1185 | | // --- decode image |
1186 | | |
1187 | 6.72k | Result<std::shared_ptr<HeifPixelImage>> decodingResult = decode_compressed_image(options, decode_tile_only, tile_x0, tile_y0, decode_state); |
1188 | 6.72k | if (!decodingResult) { |
1189 | 4.72k | return decodingResult.error(); |
1190 | 4.72k | } |
1191 | | |
1192 | 1.99k | auto img = *decodingResult; |
1193 | 1.99k | if (!img) { |
1194 | | // Safety net: no known decoding path returns a null image without an error anymore. |
1195 | 0 | return Error(heif_error_Decoder_plugin_error, heif_suberror_Unspecified, |
1196 | 0 | "Decoding returned no image"); |
1197 | 0 | } |
1198 | | |
1199 | | // --- validate the decoded image against the signaled size (pre-transform) |
1200 | | |
1201 | 1.99k | if (Error err = check_decoded_image_size(*img, decode_tile_only, tile_x0, tile_y0)) { |
1202 | 684 | return err; |
1203 | 684 | } |
1204 | | |
1205 | 1.31k | std::shared_ptr<HeifFile> file = m_heif_context->get_heif_file(); |
1206 | | |
1207 | | |
1208 | | // --- apply image transformations |
1209 | | |
1210 | 1.31k | if (options.ignore_transformations == false) { |
1211 | 1.31k | Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties(); |
1212 | 1.31k | if (!propertiesResult) { |
1213 | 0 | return propertiesResult.error(); |
1214 | 0 | } |
1215 | | |
1216 | 1.31k | const std::vector<std::shared_ptr<Box>>& properties = *propertiesResult; |
1217 | | |
1218 | 3.97k | for (const auto& property : properties) { |
1219 | 3.97k | if (auto rot = std::dynamic_pointer_cast<Box_irot>(property)) { |
1220 | 0 | auto rotateResult = img->rotate_ccw(rot->get_rotation_ccw(), m_heif_context->get_security_limits()); |
1221 | 0 | if (!rotateResult) { |
1222 | 0 | return rotateResult.error(); |
1223 | 0 | } |
1224 | | |
1225 | 0 | img = *rotateResult; |
1226 | 0 | } |
1227 | | |
1228 | | |
1229 | 3.97k | if (auto mirror = std::dynamic_pointer_cast<Box_imir>(property)) { |
1230 | 0 | auto mirrorResult = img->mirror_inplace(mirror->get_mirror_direction(), |
1231 | 0 | get_context()->get_security_limits()); |
1232 | 0 | if (!mirrorResult) { |
1233 | 0 | return mirrorResult.error(); |
1234 | 0 | } |
1235 | 0 | img = *mirrorResult; |
1236 | 0 | } |
1237 | | |
1238 | | |
1239 | 3.97k | if (!decode_tile_only) { |
1240 | | // For tiles decoding, we do not process the 'clap' because this is handled by a shift of the tiling grid. |
1241 | | |
1242 | 3.97k | if (auto clap = std::dynamic_pointer_cast<Box_clap>(property)) { |
1243 | 0 | std::shared_ptr<HeifPixelImage> clap_img; |
1244 | |
|
1245 | 0 | uint32_t img_width = img->get_width(); |
1246 | 0 | uint32_t img_height = img->get_height(); |
1247 | |
|
1248 | 0 | auto clapCrop = clap->get_crop(img_width, img_height); |
1249 | 0 | if (!clapCrop) { |
1250 | 0 | return clapCrop.error(); |
1251 | 0 | } |
1252 | | |
1253 | 0 | int left = clapCrop->left; |
1254 | 0 | int right = clapCrop->right; |
1255 | 0 | int top = clapCrop->top; |
1256 | 0 | int bottom = clapCrop->bottom; |
1257 | |
|
1258 | 0 | if (left < 0) { left = 0; } |
1259 | 0 | if (top < 0) { top = 0; } |
1260 | |
|
1261 | 0 | if ((uint32_t) right >= img_width) { right = img_width - 1; } |
1262 | 0 | if ((uint32_t) bottom >= img_height) { bottom = img_height - 1; } |
1263 | |
|
1264 | 0 | if (left > right || |
1265 | 0 | top > bottom) { |
1266 | 0 | return Error(heif_error_Invalid_input, |
1267 | 0 | heif_suberror_Invalid_clean_aperture); |
1268 | 0 | } |
1269 | | |
1270 | 0 | auto cropResult = img->crop(left, right, top, bottom, m_heif_context->get_security_limits()); |
1271 | 0 | if (!cropResult) { |
1272 | 0 | return cropResult.error(); |
1273 | 0 | } |
1274 | | |
1275 | 0 | img = *cropResult; |
1276 | 0 | } |
1277 | 3.97k | } |
1278 | | |
1279 | | |
1280 | 3.97k | if (auto iscl = std::dynamic_pointer_cast<Box_iscl>(property)) { |
1281 | 0 | return Error(heif_error_Unsupported_feature, |
1282 | 0 | heif_suberror_Unspecified, |
1283 | 0 | "Image scaling (iscl) transformative property is not yet supported"); |
1284 | 0 | } |
1285 | 3.97k | } |
1286 | 1.31k | } |
1287 | | |
1288 | | |
1289 | | // --- add alpha channel, if available |
1290 | | |
1291 | | // TODO: this if statement is probably wrong. When we have a tiled image with alpha |
1292 | | // channel, then the alpha images should be associated with their respective tiles. |
1293 | | // However, the tile images are not part of the m_all_images list. |
1294 | | // Fix this, when we have a test image available. |
1295 | | |
1296 | 1.31k | std::shared_ptr<ImageItem> alpha_image = get_alpha_channel(); |
1297 | 1.31k | if (alpha_image) { |
1298 | 51 | if (alpha_image->get_item_error()) { |
1299 | 2 | return alpha_image->get_item_error(); |
1300 | 2 | } |
1301 | | |
1302 | | // Record this item on the current decode path before following the alpha |
1303 | | // ('auxl') edge. Unlike the derived-image ('dimg') edges, whose cycle-guard |
1304 | | // insert happens inside decode_compressed_image(), the alpha edge is |
1305 | | // followed here in the base decode_image() using this frame's own |
1306 | | // decode_state. decode_compressed_image() only received a *copy* of it, so |
1307 | | // its insert of m_id is invisible here. Without adding m_id ourselves, a |
1308 | | // cycle of alpha references (auxl A->B, B->A) would re-enter decode_image() |
1309 | | // on an item whose non-recursive m_decode_mutex is still held one frame up, |
1310 | | // deadlocking the decode thread. (GHSA-8fmq-r4pf-7m57) |
1311 | 49 | decode_state.processed_ids.insert(m_id); |
1312 | | |
1313 | 49 | auto alphaDecodingResult = alpha_image->decode_image(options, decode_tile_only, tile_x0, tile_y0, decode_state); |
1314 | 49 | if (!alphaDecodingResult) { |
1315 | 10 | return alphaDecodingResult.error(); |
1316 | 10 | } |
1317 | | |
1318 | 39 | std::shared_ptr<HeifPixelImage> alpha = *alphaDecodingResult; |
1319 | | |
1320 | | // TODO: check that sizes are the same and that we have an Y channel |
1321 | | // BUT: is there any indication in the standard that the alpha channel should have the same size? |
1322 | | |
1323 | | // TODO: convert in case alpha is decoded as RGB interleaved |
1324 | | |
1325 | 39 | heif_channel channel; |
1326 | 39 | switch (alpha->get_colorspace()) { |
1327 | 22 | case heif_colorspace_YCbCr: |
1328 | 39 | case heif_colorspace_monochrome: |
1329 | 39 | channel = heif_channel_Y; |
1330 | 39 | break; |
1331 | 0 | case heif_colorspace_RGB: |
1332 | 0 | channel = heif_channel_R; |
1333 | 0 | break; |
1334 | 0 | case heif_colorspace_undefined: |
1335 | 0 | default: |
1336 | 0 | return Error(heif_error_Invalid_input, |
1337 | 0 | heif_suberror_Unsupported_color_conversion); |
1338 | 39 | } |
1339 | | |
1340 | | |
1341 | | // TODO: we should include a decoding option to control whether libheif should automatically scale the alpha channel, and if so, which scaling filter (enum: Off, NN, Bilinear, ...). |
1342 | | // It might also be that a specific output format implies that alpha is scaled (RGBA32). That would favor an enum for the scaling filter option + a bool to switch auto-filtering on. |
1343 | | // But we can only do this when libheif itself doesn't assume anymore that the alpha channel has the same resolution. |
1344 | | |
1345 | 39 | if ((alpha->get_width() != img->get_width()) || (alpha->get_height() != img->get_height())) { |
1346 | 5 | std::shared_ptr<HeifPixelImage> scaled_alpha; |
1347 | 5 | Error err = alpha->scale_nearest_neighbor(scaled_alpha, img->get_width(), img->get_height(), m_heif_context->get_security_limits()); |
1348 | 5 | if (err) { |
1349 | 0 | return err; |
1350 | 0 | } |
1351 | 5 | alpha = std::move(scaled_alpha); |
1352 | 5 | } |
1353 | 39 | if (Error err = img->transfer_channel_from_image_as(alpha, channel, heif_channel_Alpha)) { |
1354 | 0 | return err; |
1355 | 0 | } |
1356 | | |
1357 | 39 | if (is_premultiplied_alpha()) { |
1358 | 0 | img->set_premultiplied_alpha(true); |
1359 | 0 | } |
1360 | 39 | } |
1361 | | |
1362 | | |
1363 | | // --- set color profile |
1364 | | |
1365 | | // If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion. |
1366 | | // Otherwise, use the profile that is stored in the image stream itself and then set the |
1367 | | // (non-NCLX) profile later. |
1368 | 1.30k | const auto heif_nclx = get_color_profile_nclx(); |
1369 | 1.30k | if (heif_nclx.is_defined()) { |
1370 | | |
1371 | | // Since we have a HEIF colr box, we overwrite the bitstream's CICP parameter |
1372 | | // with that parameter from the colr box. |
1373 | 94 | nclx_profile consolidated_nclx = heif_nclx; |
1374 | | |
1375 | | // If the decoder plugin populated an NCLX profile from the bitstream's |
1376 | | // color signalling (e.g. HEVC SPS VUI, AV1 sequence header), compare it |
1377 | | // against the colr box. Per ISO/IEC 14496-12 and ISO/IEC 23000-22 (MIAF) |
1378 | | // the colr box overrides the bitstream, but a mismatch is a strong |
1379 | | // indication of a muxer bug. |
1380 | 94 | const auto bitstream_nclx = img->get_color_profile_nclx(); |
1381 | 94 | if (bitstream_nclx.is_defined()) { |
1382 | | |
1383 | | // Check whether there is a CICP mismatch between the HEIF colr box and the compressed bitstream |
1384 | | // If yes, output a warning. |
1385 | | |
1386 | 266 | auto cicp_mismatch = [](uint16_t bs, uint16_t cr) { |
1387 | 266 | return bs != 2 /*unspecified*/ && cr != 2 && bs != cr; |
1388 | 266 | }; |
1389 | | |
1390 | 92 | if (cicp_mismatch(bitstream_nclx.m_colour_primaries, heif_nclx.m_colour_primaries) || |
1391 | 89 | cicp_mismatch(bitstream_nclx.m_transfer_characteristics, heif_nclx.m_transfer_characteristics) || |
1392 | 85 | cicp_mismatch(bitstream_nclx.m_matrix_coefficients, heif_nclx.m_matrix_coefficients) || |
1393 | 82 | bitstream_nclx.m_full_range_flag != heif_nclx.m_full_range_flag) { |
1394 | 13 | std::stringstream msg; |
1395 | 13 | msg << "colr box NCLX (" |
1396 | 13 | << heif_nclx.m_colour_primaries << "/" |
1397 | 13 | << heif_nclx.m_transfer_characteristics << "/" |
1398 | 13 | << heif_nclx.m_matrix_coefficients << "/" |
1399 | 13 | << (heif_nclx.m_full_range_flag ? "full" : "limited") |
1400 | 13 | << ") disagrees with bitstream signalling (" |
1401 | 13 | << bitstream_nclx.m_colour_primaries << "/" |
1402 | 13 | << bitstream_nclx.m_transfer_characteristics << "/" |
1403 | 13 | << bitstream_nclx.m_matrix_coefficients << "/" |
1404 | 13 | << (bitstream_nclx.m_full_range_flag ? "full" : "limited") |
1405 | 13 | << "); colr takes precedence per ISO/IEC 14496-12 and ISO/IEC 23000-22 (MIAF)"; |
1406 | 13 | add_decoding_warning({heif_error_Invalid_input, |
1407 | 13 | heif_suberror_NCLX_colr_VUI_mismatch, |
1408 | 13 | msg.str()}); |
1409 | 13 | } |
1410 | | |
1411 | | // Fix full-range flag in images that are probably broken. |
1412 | | |
1413 | 92 | if (options.version >= 9 && options.autocorrect_broken_input) { |
1414 | | |
1415 | | // Some Sony cameras mis-tag full_range_flag=0 in colr while the bitstream VUI is correct (full_range_flag=1), see issue #1770. |
1416 | | // Rationale for the fix: if the bitstream explicitly says full_range=1, it probably does with for a reason. |
1417 | | // Thus, we keep the full-range flag. |
1418 | |
|
1419 | 0 | if (bitstream_nclx.get_full_range_flag() == true && |
1420 | 0 | heif_nclx.get_full_range_flag() == false) { |
1421 | 0 | add_decoding_warning({ |
1422 | 0 | heif_error_Invalid_input, |
1423 | 0 | heif_suberror_NCLX_colr_VUI_mismatch, |
1424 | 0 | "Autocorrecting full-range flag to ON (colr=limited, bitstream=full)" |
1425 | 0 | }); |
1426 | |
|
1427 | 0 | consolidated_nclx.set_full_range_flag(true); |
1428 | 0 | } |
1429 | 0 | } |
1430 | 92 | } |
1431 | | |
1432 | 94 | img->set_color_profile_nclx(consolidated_nclx); |
1433 | 94 | } |
1434 | | |
1435 | 1.30k | auto icc = get_color_profile_icc(); |
1436 | 1.30k | if (icc) { |
1437 | 147 | img->set_color_profile_icc(icc); |
1438 | 147 | } |
1439 | | |
1440 | | |
1441 | | // --- attach metadata to image |
1442 | | |
1443 | 1.30k | { |
1444 | 1.30k | auto ipco_box = file->get_ipco_box(); |
1445 | 1.30k | auto ipma_box = file->get_ipma_box(); |
1446 | | |
1447 | | // CLLI |
1448 | | |
1449 | 1.30k | auto clli = get_property<Box_clli>(); |
1450 | 1.30k | if (clli) { |
1451 | 0 | img->set_clli(clli->clli); |
1452 | 0 | } |
1453 | | |
1454 | | // MDCV |
1455 | | |
1456 | 1.30k | auto mdcv = get_property<Box_mdcv>(); |
1457 | 1.30k | if (mdcv) { |
1458 | 0 | img->set_mdcv(mdcv->mdcv); |
1459 | 0 | } |
1460 | | |
1461 | | // AMVE |
1462 | | |
1463 | 1.30k | auto amve = get_property<Box_amve>(); |
1464 | 1.30k | if (amve) { |
1465 | 0 | img->set_amve(amve->amve); |
1466 | 0 | } |
1467 | | |
1468 | | // NDWT |
1469 | | |
1470 | 1.30k | auto ndwt = get_property<Box_ndwt>(); |
1471 | 1.30k | if (ndwt) { |
1472 | 0 | img->set_nominal_diffuse_white_luminance(ndwt->get_diffuse_white_luminance()); |
1473 | 0 | } |
1474 | | |
1475 | | // PASP |
1476 | | |
1477 | 1.30k | auto pasp = get_property<Box_pasp>(); |
1478 | 1.30k | if (pasp) { |
1479 | 0 | img->set_pixel_ratio(pasp->hSpacing, pasp->vSpacing); |
1480 | 0 | } |
1481 | | |
1482 | | // TAI |
1483 | | |
1484 | 1.30k | auto itai = get_property<Box_itai>(); |
1485 | 1.30k | if (itai) { |
1486 | 0 | img->set_tai_timestamp(itai->get_tai_timestamp_packet()); |
1487 | 0 | } |
1488 | | |
1489 | | // GIMI content ID |
1490 | | |
1491 | 1.30k | auto gimi_content_id = get_property<Box_gimi_content_id>(); |
1492 | 1.30k | if (gimi_content_id) { |
1493 | 0 | img->set_gimi_sample_content_id(gimi_content_id->get_content_id()); |
1494 | 0 | } |
1495 | | |
1496 | | // Image projection (OMAF) |
1497 | 1.30k | auto prfr = get_property<Box_prfr>(); |
1498 | 1.30k | if (prfr) { |
1499 | 12 | img->set_omaf_image_projection(prfr->get_omaf_image_projection()); |
1500 | 12 | } |
1501 | 1.30k | } |
1502 | | |
1503 | | |
1504 | 1.30k | return img; |
1505 | 1.31k | } |
1506 | | |
1507 | | #if 0 |
1508 | | Result<std::vector<uint8_t>> ImageItem::read_bitstream_configuration_data_override(heif_item_id itemId, heif_compression_format format) const |
1509 | | { |
1510 | | auto item_codec = ImageItem::alloc_for_compression_format(const_cast<HeifContext*>(get_context()), format); |
1511 | | assert(item_codec); |
1512 | | |
1513 | | Error err = item_codec->init_decoder_from_item(itemId); |
1514 | | if (err) { |
1515 | | return err; |
1516 | | } |
1517 | | |
1518 | | return item_codec->read_bitstream_configuration_data(itemId); |
1519 | | } |
1520 | | #endif |
1521 | | |
1522 | | Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_compressed_image(const heif_decoding_options& options, |
1523 | | bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0, |
1524 | | DecodeTraversalState decode_state) const |
1525 | 6.59k | { |
1526 | 6.59k | if (decode_state.processed_ids.contains(m_id)) { |
1527 | 0 | return Error{heif_error_Invalid_input, |
1528 | 0 | heif_suberror_Unspecified, |
1529 | 0 | "'iref' has cyclic references"}; |
1530 | 0 | } |
1531 | | |
1532 | 6.59k | decode_state.processed_ids.insert(m_id); |
1533 | | |
1534 | | |
1535 | 6.59k | DataExtent extent; |
1536 | 6.59k | extent.set_from_image_item(get_file(), get_id()); |
1537 | | |
1538 | 6.59k | auto decoderResult = get_decoder(); |
1539 | 6.59k | if (!decoderResult) { |
1540 | 0 | return decoderResult.error(); |
1541 | 0 | } |
1542 | | |
1543 | 6.59k | auto decoder = *decoderResult; |
1544 | | |
1545 | 6.59k | decoder->set_data_extent(std::move(extent)); |
1546 | | |
1547 | | // Tighten max_image_size_pixels for this decode so a decoder plugin (e.g. |
1548 | | // dav1d) cannot allocate buffers far larger than the ispe-declared size |
1549 | | // when the codec bitstream lies about its dimensions. |
1550 | 6.59k | heif_security_limits tightened = tighten_image_size_limit_for_ispe( |
1551 | 6.59k | get_context()->get_security_limits(), |
1552 | 6.59k | get_ispe_width(), get_ispe_height(), |
1553 | 6.59k | max_coding_unit_size_for_codec(get_compression_format())); |
1554 | | |
1555 | 6.59k | return decoder->decode_single_frame_from_compressed_data(options, &tightened); |
1556 | 6.59k | } |
1557 | | |
1558 | | |
1559 | | Error ImageItem::check_decoded_image_size(const HeifPixelImage& img, |
1560 | | bool decode_tile_only, |
1561 | | uint32_t tile_x0, uint32_t tile_y0) const |
1562 | 1.99k | { |
1563 | 1.99k | uint32_t expected_w, expected_h; |
1564 | | |
1565 | 1.99k | if (decode_tile_only) { |
1566 | | // The decoded buffer is a single tile, sized to the signaled tile size. |
1567 | 0 | get_tile_size(expected_w, expected_h); |
1568 | 0 | } |
1569 | 1.99k | else { |
1570 | | // Pre-transform coded size from the 'ispe' property. |
1571 | 1.99k | expected_w = get_ispe_width(); |
1572 | 1.99k | expected_h = get_ispe_height(); |
1573 | 1.99k | } |
1574 | | |
1575 | | // No 'ispe' / no tile size known -> cannot validate (a missing-'ispe' warning is |
1576 | | // already emitted upstream). Skip rather than reject. |
1577 | 1.99k | if (expected_w == 0 || expected_h == 0) { |
1578 | 25 | return Error::Ok; |
1579 | 25 | } |
1580 | | |
1581 | 1.97k | if (!img.primary_planes_have_size(expected_w, expected_h)) { |
1582 | 684 | return Error{heif_error_Invalid_input, |
1583 | 684 | heif_suberror_Invalid_image_size, |
1584 | 684 | "Decoded image does not have the size signaled in the file."}; |
1585 | 684 | } |
1586 | | |
1587 | 1.28k | return Error::Ok; |
1588 | 1.97k | } |
1589 | | |
1590 | | |
1591 | | heif_image_tiling ImageItem::get_heif_image_tiling() const |
1592 | 0 | { |
1593 | | // --- Return a dummy tiling consisting of only a single tile for the whole image |
1594 | |
|
1595 | 0 | heif_image_tiling tiling{}; |
1596 | |
|
1597 | 0 | tiling.version = 1; |
1598 | 0 | tiling.num_columns = 1; |
1599 | 0 | tiling.num_rows = 1; |
1600 | | |
1601 | | // Report the coded (pre-transformation) dimensions here. The caller applies |
1602 | | // the transformative properties (irot, imir, clap) via |
1603 | | // process_image_transformations_on_tiling(), so handing it the already |
1604 | | // transformed m_width/m_height would apply them a second time. For a clap |
1605 | | // that shrinks the image to zero this double application underflowed inside |
1606 | | // Box_clap::get_crop() (GHSA-jc8f-p23p-5hjg); for irot/imir it silently |
1607 | | // produced wrong dimensions. The grid/unc/tiled overrides likewise report |
1608 | | // coded dimensions. |
1609 | 0 | uint32_t coded_width = m_width; |
1610 | 0 | uint32_t coded_height = m_height; |
1611 | 0 | if (has_ispe_resolution()) { |
1612 | 0 | coded_width = get_ispe_width(); |
1613 | 0 | coded_height = get_ispe_height(); |
1614 | 0 | } |
1615 | |
|
1616 | 0 | tiling.tile_width = coded_width; |
1617 | 0 | tiling.tile_height = coded_height; |
1618 | 0 | tiling.image_width = coded_width; |
1619 | 0 | tiling.image_height = coded_height; |
1620 | |
|
1621 | 0 | tiling.top_offset = 0; |
1622 | 0 | tiling.left_offset = 0; |
1623 | 0 | tiling.number_of_extra_dimensions = 0; |
1624 | |
|
1625 | 0 | for (uint32_t& s : tiling.extra_dimension_size) { |
1626 | 0 | s = 0; |
1627 | 0 | } |
1628 | |
|
1629 | 0 | return tiling; |
1630 | 0 | } |
1631 | | |
1632 | | |
1633 | | Result<std::vector<std::shared_ptr<Box>>> ImageItem::get_properties() const |
1634 | 1.31k | { |
1635 | 1.31k | std::vector<std::shared_ptr<Box>> properties; |
1636 | 1.31k | auto ipco_box = get_file()->get_ipco_box(); |
1637 | 1.31k | auto ipma_box = get_file()->get_ipma_box(); |
1638 | 1.31k | Error error = ipco_box->get_properties_for_item_ID(m_id, ipma_box, properties); |
1639 | 1.31k | if (error) { |
1640 | 0 | return error; |
1641 | 0 | } |
1642 | | |
1643 | 1.31k | return properties; |
1644 | 1.31k | } |
1645 | | |
1646 | | |
1647 | | bool ImageItem::has_essential_property_other_than(const std::set<uint32_t>& props) const |
1648 | 0 | { |
1649 | 0 | Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties(); |
1650 | 0 | if (!propertiesResult) { |
1651 | 0 | return false; |
1652 | 0 | } |
1653 | | |
1654 | 0 | for (const auto& property : *propertiesResult) { |
1655 | 0 | if (is_property_essential(property) && |
1656 | 0 | props.find(property->get_short_type()) == props.end()) { |
1657 | 0 | return true; |
1658 | 0 | } |
1659 | 0 | } |
1660 | | |
1661 | 0 | return false; |
1662 | 0 | } |
1663 | | |
1664 | | |
1665 | | Error ImageItem::process_image_transformations_on_tiling(heif_image_tiling& tiling) const |
1666 | 0 | { |
1667 | 0 | Result<std::vector<std::shared_ptr<Box>>> propertiesResult = get_properties(); |
1668 | 0 | if (!propertiesResult) { |
1669 | 0 | return propertiesResult.error(); |
1670 | 0 | } |
1671 | | |
1672 | 0 | const std::vector<std::shared_ptr<Box>>& properties = *propertiesResult; |
1673 | |
|
1674 | 0 | uint32_t left_excess = 0; |
1675 | 0 | uint32_t top_excess = 0; |
1676 | 0 | uint32_t right_excess; |
1677 | 0 | uint32_t bottom_excess; |
1678 | | |
1679 | | // Prevent divide by zero. |
1680 | |
|
1681 | 0 | if (tiling.tile_width != 0 && tiling.tile_height != 0) { |
1682 | 0 | right_excess = tiling.image_width % tiling.tile_width; |
1683 | 0 | bottom_excess = tiling.image_height % tiling.tile_height; |
1684 | 0 | } |
1685 | 0 | else { |
1686 | 0 | right_excess = 0; |
1687 | 0 | bottom_excess = 0; |
1688 | 0 | } |
1689 | | |
1690 | |
|
1691 | 0 | for (const auto& property : properties) { |
1692 | | |
1693 | | // --- rotation |
1694 | |
|
1695 | 0 | if (auto rot = std::dynamic_pointer_cast<Box_irot>(property)) { |
1696 | 0 | int angle = rot->get_rotation_ccw(); |
1697 | 0 | if (angle == 90 || angle == 270) { |
1698 | 0 | std::swap(tiling.tile_width, tiling.tile_height); |
1699 | 0 | std::swap(tiling.image_width, tiling.image_height); |
1700 | 0 | std::swap(tiling.num_rows, tiling.num_columns); |
1701 | 0 | } |
1702 | |
|
1703 | 0 | switch (angle) { |
1704 | 0 | case 0: |
1705 | 0 | break; |
1706 | 0 | case 180: |
1707 | 0 | std::swap(left_excess, right_excess); |
1708 | 0 | std::swap(top_excess, bottom_excess); |
1709 | 0 | break; |
1710 | 0 | case 90: { |
1711 | 0 | uint32_t old_top_excess = top_excess; |
1712 | 0 | top_excess = right_excess; |
1713 | 0 | right_excess = bottom_excess; |
1714 | 0 | bottom_excess = left_excess; |
1715 | 0 | left_excess = old_top_excess; |
1716 | 0 | break; |
1717 | 0 | } |
1718 | 0 | case 270: { |
1719 | 0 | uint32_t old_top_excess = top_excess; |
1720 | 0 | top_excess = left_excess; |
1721 | 0 | left_excess = bottom_excess; |
1722 | 0 | bottom_excess = right_excess; |
1723 | 0 | right_excess = old_top_excess; |
1724 | 0 | break; |
1725 | 0 | } |
1726 | 0 | default: |
1727 | 0 | assert(false); |
1728 | 0 | break; |
1729 | 0 | } |
1730 | 0 | } |
1731 | | |
1732 | | // --- mirror |
1733 | | |
1734 | 0 | if (auto mirror = std::dynamic_pointer_cast<Box_imir>(property)) { |
1735 | 0 | switch (mirror->get_mirror_direction()) { |
1736 | 0 | case heif_transform_mirror_direction_horizontal: |
1737 | 0 | std::swap(left_excess, right_excess); |
1738 | 0 | break; |
1739 | 0 | case heif_transform_mirror_direction_vertical: |
1740 | 0 | std::swap(top_excess, bottom_excess); |
1741 | 0 | break; |
1742 | 0 | default: |
1743 | 0 | assert(false); |
1744 | 0 | break; |
1745 | 0 | } |
1746 | 0 | } |
1747 | | |
1748 | | // --- crop |
1749 | | |
1750 | 0 | if (auto clap = std::dynamic_pointer_cast<Box_clap>(property)) { |
1751 | 0 | std::shared_ptr<HeifPixelImage> clap_img; |
1752 | |
|
1753 | 0 | auto cropResult = clap->get_crop(tiling.image_width, tiling.image_height); |
1754 | 0 | if (!cropResult) { |
1755 | 0 | return cropResult.error(); |
1756 | 0 | } |
1757 | | |
1758 | 0 | int left = cropResult->left; |
1759 | 0 | int right = cropResult->right; |
1760 | 0 | int top = cropResult->top; |
1761 | 0 | int bottom = cropResult->bottom; |
1762 | |
|
1763 | 0 | if (left < 0) { left = 0; } |
1764 | 0 | if (top < 0) { top = 0; } |
1765 | |
|
1766 | 0 | if ((uint32_t)right >= tiling.image_width) { right = tiling.image_width - 1; } |
1767 | 0 | if ((uint32_t)bottom >= tiling.image_height) { bottom = tiling.image_height - 1; } |
1768 | |
|
1769 | 0 | if (left > right || |
1770 | 0 | top > bottom) { |
1771 | 0 | return {heif_error_Invalid_input, |
1772 | 0 | heif_suberror_Invalid_clean_aperture}; |
1773 | 0 | } |
1774 | | |
1775 | 0 | left_excess += left; |
1776 | 0 | right_excess += right; |
1777 | 0 | top_excess += top; |
1778 | 0 | bottom_excess += bottom; |
1779 | 0 | } |
1780 | | |
1781 | | // --- scaling (not supported yet) |
1782 | | |
1783 | 0 | if (auto iscl = std::dynamic_pointer_cast<Box_iscl>(property)) { |
1784 | 0 | return {heif_error_Unsupported_feature, |
1785 | 0 | heif_suberror_Unspecified, |
1786 | 0 | "Image scaling (iscl) transformative property is not yet supported"}; |
1787 | 0 | } |
1788 | 0 | } |
1789 | | |
1790 | 0 | tiling.left_offset = left_excess; |
1791 | 0 | tiling.top_offset = top_excess; |
1792 | |
|
1793 | 0 | return Error::Ok; |
1794 | 0 | } |