/src/libultrahdr/third_party/libheif/libheif/context.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 | | #include "box.h" |
22 | | #include "error.h" |
23 | | #include "libheif/heif.h" |
24 | | #include "region.h" |
25 | | #include <cstdint> |
26 | | #include <cassert> |
27 | | #include <cstring> |
28 | | #include <algorithm> |
29 | | #include <iostream> |
30 | | #include <limits> |
31 | | #include <cmath> |
32 | | #include <deque> |
33 | | #include "image-items/image_item.h" |
34 | | #include <codecs/hevc_boxes.h> |
35 | | |
36 | | #if ENABLE_PARALLEL_TILE_DECODING |
37 | | #include <future> |
38 | | #endif |
39 | | |
40 | | #include "context.h" |
41 | | #include "file.h" |
42 | | #include "pixelimage.h" |
43 | | #include "libheif/api_structs.h" |
44 | | #include "security_limits.h" |
45 | | #include "compression.h" |
46 | | #include "color-conversion/colorconversion.h" |
47 | | #include "plugin_registry.h" |
48 | | #include "image-items/hevc.h" |
49 | | #include "image-items/vvc.h" |
50 | | #include "image-items/avif.h" |
51 | | #include "image-items/jpeg.h" |
52 | | #include "image-items/mask_image.h" |
53 | | #include "image-items/jpeg2000.h" |
54 | | #include "image-items/grid.h" |
55 | | #include "image-items/overlay.h" |
56 | | #include "image-items/tiled.h" |
57 | | |
58 | | #if WITH_UNCOMPRESSED_CODEC |
59 | | #include "image-items/unc_image.h" |
60 | | #endif |
61 | | |
62 | | |
63 | | heif_encoder::heif_encoder(const struct heif_encoder_plugin* _plugin) |
64 | 0 | : plugin(_plugin) |
65 | 0 | { |
66 | |
|
67 | 0 | } |
68 | | |
69 | | heif_encoder::~heif_encoder() |
70 | 0 | { |
71 | 0 | release(); |
72 | 0 | } |
73 | | |
74 | | void heif_encoder::release() |
75 | 0 | { |
76 | 0 | if (encoder) { |
77 | 0 | plugin->free_encoder(encoder); |
78 | 0 | encoder = nullptr; |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | | |
83 | | struct heif_error heif_encoder::alloc() |
84 | 0 | { |
85 | 0 | if (encoder == nullptr) { |
86 | 0 | struct heif_error error = plugin->new_encoder(&encoder); |
87 | | // TODO: error handling |
88 | 0 | return error; |
89 | 0 | } |
90 | | |
91 | 0 | struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, Error::kSuccess}; |
92 | 0 | return err; |
93 | 0 | } |
94 | | |
95 | | |
96 | | HeifContext::HeifContext() |
97 | 15.8k | { |
98 | 15.8k | const char* security_limits_variable = getenv("LIBHEIF_SECURITY_LIMITS"); |
99 | | |
100 | 15.8k | if (security_limits_variable && (strcmp(security_limits_variable, "off") == 0 || |
101 | 0 | strcmp(security_limits_variable, "OFF") == 0)) { |
102 | 0 | m_limits = disabled_security_limits; |
103 | 0 | } |
104 | 15.8k | else { |
105 | 15.8k | m_limits = global_security_limits; |
106 | 15.8k | } |
107 | | |
108 | 15.8k | reset_to_empty_heif(); |
109 | 15.8k | } |
110 | | |
111 | | |
112 | | HeifContext::~HeifContext() |
113 | 15.8k | { |
114 | | // Break circular references between Images (when a faulty input image has circular image references) |
115 | 15.8k | for (auto& it : m_all_images) { |
116 | 0 | std::shared_ptr<ImageItem> image = it.second; |
117 | 0 | image->clear(); |
118 | 0 | } |
119 | 15.8k | } |
120 | | |
121 | | |
122 | | static void copy_security_limits(heif_security_limits* dst, const heif_security_limits* src) |
123 | 0 | { |
124 | 0 | dst->version = 1; |
125 | 0 | dst->max_image_size_pixels = src->max_image_size_pixels; |
126 | 0 | dst->max_number_of_tiles = src->max_number_of_tiles; |
127 | 0 | dst->max_bayer_pattern_pixels = src->max_bayer_pattern_pixels; |
128 | 0 | dst->max_items = src->max_items; |
129 | |
|
130 | 0 | dst->max_color_profile_size = src->max_color_profile_size; |
131 | 0 | dst->max_memory_block_size = src->max_memory_block_size; |
132 | |
|
133 | 0 | dst->max_components = src->max_components; |
134 | |
|
135 | 0 | dst->max_iloc_extents_per_item = src->max_iloc_extents_per_item; |
136 | 0 | dst->max_size_entity_group = src->max_size_entity_group; |
137 | |
|
138 | 0 | dst->max_children_per_box = src->max_children_per_box; |
139 | 0 | } |
140 | | |
141 | | |
142 | | void HeifContext::set_security_limits(const heif_security_limits* limits) |
143 | 0 | { |
144 | 0 | copy_security_limits(&m_limits, limits); |
145 | 0 | } |
146 | | |
147 | | |
148 | | Error HeifContext::read(const std::shared_ptr<StreamReader>& reader) |
149 | 0 | { |
150 | 0 | m_heif_file = std::make_shared<HeifFile>(); |
151 | 0 | m_heif_file->set_security_limits(&m_limits); |
152 | 0 | Error err = m_heif_file->read(reader); |
153 | 0 | if (err) { |
154 | 0 | return err; |
155 | 0 | } |
156 | | |
157 | 0 | return interpret_heif_file(); |
158 | 0 | } |
159 | | |
160 | | Error HeifContext::read_from_file(const char* input_filename) |
161 | 0 | { |
162 | 0 | m_heif_file = std::make_shared<HeifFile>(); |
163 | 0 | m_heif_file->set_security_limits(&m_limits); |
164 | 0 | Error err = m_heif_file->read_from_file(input_filename); |
165 | 0 | if (err) { |
166 | 0 | return err; |
167 | 0 | } |
168 | | |
169 | 0 | return interpret_heif_file(); |
170 | 0 | } |
171 | | |
172 | | Error HeifContext::read_from_memory(const void* data, size_t size, bool copy) |
173 | 15.8k | { |
174 | 15.8k | m_heif_file = std::make_shared<HeifFile>(); |
175 | 15.8k | m_heif_file->set_security_limits(&m_limits); |
176 | 15.8k | Error err = m_heif_file->read_from_memory(data, size, copy); |
177 | 15.8k | if (err) { |
178 | 15.8k | return err; |
179 | 15.8k | } |
180 | | |
181 | 0 | return interpret_heif_file(); |
182 | 15.8k | } |
183 | | |
184 | | void HeifContext::reset_to_empty_heif() |
185 | 15.8k | { |
186 | 15.8k | m_heif_file = std::make_shared<HeifFile>(); |
187 | 15.8k | m_heif_file->set_security_limits(&m_limits); |
188 | 15.8k | m_heif_file->new_empty_file(); |
189 | | |
190 | 15.8k | m_all_images.clear(); |
191 | 15.8k | m_top_level_images.clear(); |
192 | 15.8k | m_primary_image.reset(); |
193 | 15.8k | } |
194 | | |
195 | | |
196 | | std::vector<std::shared_ptr<ImageItem>> HeifContext::get_top_level_images(bool return_error_images) |
197 | 0 | { |
198 | 0 | if (return_error_images) { |
199 | 0 | return m_top_level_images; |
200 | 0 | } |
201 | 0 | else { |
202 | 0 | std::vector<std::shared_ptr<ImageItem>> filtered; |
203 | 0 | for (auto& item : m_top_level_images) { |
204 | 0 | if (!item->get_item_error()) { |
205 | 0 | filtered.push_back(item); |
206 | 0 | } |
207 | 0 | } |
208 | |
|
209 | 0 | return filtered; |
210 | 0 | } |
211 | 0 | } |
212 | | |
213 | | |
214 | | std::shared_ptr<ImageItem> HeifContext::get_image(heif_item_id id, bool return_error_images) |
215 | 0 | { |
216 | 0 | auto iter = m_all_images.find(id); |
217 | 0 | if (iter == m_all_images.end()) { |
218 | 0 | return nullptr; |
219 | 0 | } |
220 | 0 | else { |
221 | 0 | if (iter->second->get_item_error() && !return_error_images) { |
222 | 0 | return nullptr; |
223 | 0 | } |
224 | 0 | else { |
225 | 0 | return iter->second; |
226 | 0 | } |
227 | 0 | } |
228 | 0 | } |
229 | | |
230 | | |
231 | | std::shared_ptr<ImageItem> HeifContext::get_primary_image(bool return_error_image) |
232 | 0 | { |
233 | 0 | if (!return_error_image && m_primary_image->get_item_error()) |
234 | 0 | return nullptr; |
235 | 0 | else |
236 | 0 | return m_primary_image; |
237 | 0 | } |
238 | | |
239 | | |
240 | | bool HeifContext::is_image(heif_item_id ID) const |
241 | 0 | { |
242 | 0 | return m_all_images.find(ID) != m_all_images.end(); |
243 | 0 | } |
244 | | |
245 | | |
246 | | std::shared_ptr<RegionItem> HeifContext::add_region_item(uint32_t reference_width, uint32_t reference_height) |
247 | 0 | { |
248 | 0 | std::shared_ptr<Box_infe> box = m_heif_file->add_new_infe_box(fourcc("rgan")); |
249 | 0 | box->set_hidden_item(true); |
250 | |
|
251 | 0 | auto regionItem = std::make_shared<RegionItem>(box->get_item_ID(), reference_width, reference_height); |
252 | 0 | add_region_item(regionItem); |
253 | |
|
254 | 0 | return regionItem; |
255 | 0 | } |
256 | | |
257 | | void HeifContext::add_region_referenced_mask_ref(heif_item_id region_item_id, heif_item_id mask_item_id) |
258 | 0 | { |
259 | 0 | m_heif_file->add_iref_reference(region_item_id, fourcc("mask"), {mask_item_id}); |
260 | 0 | } |
261 | | |
262 | | void HeifContext::write(StreamWriter& writer) |
263 | 0 | { |
264 | | // --- serialize regions |
265 | |
|
266 | 0 | for (auto& image : m_all_images) { |
267 | 0 | for (auto region : image.second->get_region_item_ids()) { |
268 | 0 | m_heif_file->add_iref_reference(region, |
269 | 0 | fourcc("cdsc"), {image.first}); |
270 | 0 | } |
271 | 0 | } |
272 | |
|
273 | 0 | for (auto& region : m_region_items) { |
274 | 0 | std::vector<uint8_t> data_array; |
275 | 0 | Error err = region->encode(data_array); |
276 | | // TODO: err |
277 | |
|
278 | 0 | m_heif_file->append_iloc_data(region->item_id, data_array, 0); |
279 | 0 | } |
280 | | |
281 | | // --- post-process images |
282 | |
|
283 | 0 | for (auto& img : m_all_images) { |
284 | 0 | img.second->process_before_write(); |
285 | 0 | } |
286 | | |
287 | | // --- sort item properties |
288 | |
|
289 | 0 | m_heif_file->get_ipma_box()->sort_properties(m_heif_file->get_ipco_box()); |
290 | | |
291 | | // --- write to file |
292 | |
|
293 | 0 | m_heif_file->write(writer); |
294 | 0 | } |
295 | | |
296 | | std::string HeifContext::debug_dump_boxes() const |
297 | 0 | { |
298 | 0 | return m_heif_file->debug_dump_boxes(); |
299 | 0 | } |
300 | | |
301 | | |
302 | | static bool item_type_is_image(uint32_t item_type, const std::string& content_type) |
303 | 0 | { |
304 | 0 | return (item_type == fourcc("hvc1") || |
305 | 0 | item_type == fourcc("av01") || |
306 | 0 | item_type == fourcc("grid") || |
307 | 0 | item_type == fourcc("tili") || |
308 | 0 | item_type == fourcc("iden") || |
309 | 0 | item_type == fourcc("iovl") || |
310 | 0 | item_type == fourcc("avc1") || |
311 | 0 | item_type == fourcc("unci") || |
312 | 0 | item_type == fourcc("vvc1") || |
313 | 0 | item_type == fourcc("jpeg") || |
314 | 0 | (item_type == fourcc("mime") && content_type == "image/jpeg") || |
315 | 0 | item_type == fourcc("j2k1") || |
316 | 0 | item_type == fourcc("mski")); |
317 | 0 | } |
318 | | |
319 | | |
320 | | void HeifContext::remove_top_level_image(const std::shared_ptr<ImageItem>& image) |
321 | 0 | { |
322 | 0 | std::vector<std::shared_ptr<ImageItem>> new_list; |
323 | |
|
324 | 0 | for (const auto& img : m_top_level_images) { |
325 | 0 | if (img != image) { |
326 | 0 | new_list.push_back(img); |
327 | 0 | } |
328 | 0 | } |
329 | |
|
330 | 0 | m_top_level_images = std::move(new_list); |
331 | 0 | } |
332 | | |
333 | | |
334 | | Error HeifContext::interpret_heif_file() |
335 | 0 | { |
336 | 0 | m_all_images.clear(); |
337 | 0 | m_top_level_images.clear(); |
338 | 0 | m_primary_image.reset(); |
339 | | |
340 | | // --- reference all non-hidden images |
341 | |
|
342 | 0 | std::vector<heif_item_id> image_IDs = m_heif_file->get_item_IDs(); |
343 | 0 | #if WITH_EXPERIMENTAL_GAIN_MAP |
344 | 0 | bool check_tmap_item = m_heif_file->get_ftyp_box()->has_compatible_brand(heif_brand2_tmap); |
345 | 0 | #endif |
346 | |
|
347 | 0 | for (heif_item_id id : image_IDs) { |
348 | 0 | auto infe_box = m_heif_file->get_infe_box(id); |
349 | 0 | if (!infe_box) { |
350 | | // TODO(farindk): Should we return an error instead of skipping the invalid id? |
351 | 0 | continue; |
352 | 0 | } |
353 | | |
354 | 0 | auto image = ImageItem::alloc_for_infe_box(this, infe_box); |
355 | 0 | if (!image) { |
356 | | // It is no image item, skip it. |
357 | 0 | continue; |
358 | 0 | } |
359 | | |
360 | 0 | m_all_images.insert(std::make_pair(id, image)); |
361 | |
|
362 | 0 | if (!infe_box->is_hidden_item()) { |
363 | 0 | if (id == m_heif_file->get_primary_image_ID()) { |
364 | 0 | image->set_primary(true); |
365 | 0 | m_primary_image = image; |
366 | 0 | } |
367 | |
|
368 | 0 | m_top_level_images.push_back(image); |
369 | 0 | } |
370 | |
|
371 | 0 | std::vector<std::shared_ptr<Box>> properties; |
372 | 0 | Error err = m_heif_file->get_properties(id, properties); |
373 | 0 | if (err) { |
374 | 0 | return err; |
375 | 0 | } |
376 | | |
377 | 0 | image->set_properties(properties); |
378 | |
|
379 | 0 | err = image->on_load_file(); |
380 | 0 | if (err) { |
381 | 0 | return err; |
382 | 0 | } |
383 | 0 | } |
384 | | |
385 | 0 | if (!m_primary_image) { |
386 | 0 | return Error(heif_error_Invalid_input, |
387 | 0 | heif_suberror_Nonexisting_item_referenced, |
388 | 0 | "'pitm' box references an unsupported or non-existing image"); |
389 | 0 | } |
390 | | |
391 | | |
392 | | // --- process image properties |
393 | | |
394 | 0 | for (auto& pair : m_all_images) { |
395 | 0 | auto& image = pair.second; |
396 | |
|
397 | 0 | if (image->get_item_error()) { |
398 | 0 | continue; |
399 | 0 | } |
400 | | |
401 | 0 | std::vector<std::shared_ptr<Box>> properties; |
402 | |
|
403 | 0 | Error err = m_heif_file->get_properties(pair.first, properties); |
404 | 0 | if (err) { |
405 | 0 | return err; |
406 | 0 | } |
407 | | |
408 | | |
409 | | // --- are there any 'essential' properties that we did not parse? |
410 | | |
411 | 0 | for (const auto& prop : properties) { |
412 | 0 | if (std::dynamic_pointer_cast<Box_other>(prop) && |
413 | 0 | get_heif_file()->get_ipco_box()->is_property_essential_for_item(pair.first, prop, get_heif_file()->get_ipma_box())) { |
414 | |
|
415 | 0 | std::stringstream sstr; |
416 | 0 | sstr << "could not parse item property '" << prop->get_type_string() << "'"; |
417 | 0 | return {heif_error_Unsupported_feature, heif_suberror_Unsupported_essential_property, sstr.str()}; |
418 | 0 | } |
419 | 0 | } |
420 | | |
421 | | |
422 | | // --- Are there any parse errors in optional properties? Attach the errors as warnings to the images. |
423 | | |
424 | 0 | bool ignore_nonfatal_parse_errors = false; // TODO: this should be a user option. Where should we put this (heif_decoding_options, or while creating the context) ? |
425 | |
|
426 | 0 | for (const auto& prop : properties) { |
427 | 0 | if (auto errorbox = std::dynamic_pointer_cast<Box_Error>(prop)) { |
428 | 0 | parse_error_fatality fatality = errorbox->get_parse_error_fatality(); |
429 | |
|
430 | 0 | if (fatality == parse_error_fatality::optional || |
431 | 0 | (fatality == parse_error_fatality::ignorable && ignore_nonfatal_parse_errors)) { |
432 | 0 | image->add_decoding_warning(errorbox->get_error()); |
433 | 0 | } |
434 | 0 | else { |
435 | 0 | return errorbox->get_error(); |
436 | 0 | } |
437 | 0 | } |
438 | 0 | } |
439 | | |
440 | | |
441 | | // --- extract image resolution |
442 | | |
443 | 0 | bool ispe_read = false; |
444 | 0 | for (const auto& prop : properties) { |
445 | 0 | auto ispe = std::dynamic_pointer_cast<Box_ispe>(prop); |
446 | 0 | if (ispe) { |
447 | 0 | uint32_t width = ispe->get_width(); |
448 | 0 | uint32_t height = ispe->get_height(); |
449 | |
|
450 | 0 | Error sizeError = check_for_valid_image_size(get_security_limits(), width, height); |
451 | 0 | if (sizeError) { |
452 | 0 | return sizeError; |
453 | 0 | } |
454 | | |
455 | 0 | image->set_resolution(width, height); |
456 | 0 | ispe_read = true; |
457 | 0 | } |
458 | 0 | } |
459 | | |
460 | | // Note: usually, we would like to check here if an `ispe` property exists as this is mandatory. |
461 | | // We want to do this if decoding_options.strict_decoding is set, but we cannot because we have no decoding_options |
462 | | // when parsing the file structure. |
463 | | |
464 | 0 | if (!ispe_read) { |
465 | 0 | image->add_decoding_warning({heif_error_Invalid_input, heif_suberror_No_ispe_property}); |
466 | 0 | } |
467 | | |
468 | |
|
469 | 0 | for (const auto& prop : properties) { |
470 | 0 | auto colr = std::dynamic_pointer_cast<Box_colr>(prop); |
471 | 0 | if (colr) { |
472 | 0 | auto profile = colr->get_color_profile(); |
473 | 0 | image->set_color_profile(profile); |
474 | 0 | continue; |
475 | 0 | } |
476 | | |
477 | 0 | auto cmin = std::dynamic_pointer_cast<Box_cmin>(prop); |
478 | 0 | if (cmin) { |
479 | 0 | if (!ispe_read) { |
480 | 0 | return {heif_error_Invalid_input, heif_suberror_No_ispe_property}; |
481 | 0 | } |
482 | | |
483 | 0 | image->set_intrinsic_matrix(cmin->get_intrinsic_matrix()); |
484 | 0 | } |
485 | | |
486 | 0 | auto cmex = std::dynamic_pointer_cast<Box_cmex>(prop); |
487 | 0 | if (cmex) { |
488 | 0 | image->set_extrinsic_matrix(cmex->get_extrinsic_matrix()); |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | | |
493 | 0 | for (const auto& prop : properties) { |
494 | 0 | auto clap = std::dynamic_pointer_cast<Box_clap>(prop); |
495 | 0 | if (clap) { |
496 | 0 | image->set_resolution(clap->get_width_rounded(), |
497 | 0 | clap->get_height_rounded()); |
498 | |
|
499 | 0 | if (image->has_intrinsic_matrix()) { |
500 | 0 | image->get_intrinsic_matrix().apply_clap(clap.get(), image->get_width(), image->get_height()); |
501 | 0 | } |
502 | 0 | } |
503 | |
|
504 | 0 | auto imir = std::dynamic_pointer_cast<Box_imir>(prop); |
505 | 0 | if (imir) { |
506 | 0 | if (!ispe_read) { |
507 | 0 | return {heif_error_Invalid_input, heif_suberror_No_ispe_property}; |
508 | 0 | } |
509 | | |
510 | 0 | image->get_intrinsic_matrix().apply_imir(imir.get(), image->get_width(), image->get_height()); |
511 | 0 | } |
512 | | |
513 | 0 | auto irot = std::dynamic_pointer_cast<Box_irot>(prop); |
514 | 0 | if (irot) { |
515 | 0 | if (irot->get_rotation_ccw() == 90 || |
516 | 0 | irot->get_rotation_ccw() == 270) { |
517 | 0 | if (!ispe_read) { |
518 | 0 | return {heif_error_Invalid_input, heif_suberror_No_ispe_property}; |
519 | 0 | } |
520 | | |
521 | | // swap width and height |
522 | 0 | image->set_resolution(image->get_height(), |
523 | 0 | image->get_width()); |
524 | 0 | } |
525 | | |
526 | | // TODO: apply irot to camera extrinsic matrix |
527 | 0 | } |
528 | 0 | } |
529 | 0 | } |
530 | | |
531 | | |
532 | | // --- remove auxiliary from top-level images and assign to their respective image |
533 | | |
534 | 0 | auto iref_box = m_heif_file->get_iref_box(); |
535 | 0 | if (iref_box) { |
536 | | // m_top_level_images.clear(); |
537 | |
|
538 | 0 | for (auto& pair : m_all_images) { |
539 | 0 | auto& image = pair.second; |
540 | |
|
541 | 0 | std::vector<Box_iref::Reference> references = iref_box->get_references_from(image->get_id()); |
542 | |
|
543 | 0 | for (const Box_iref::Reference& ref : references) { |
544 | 0 | uint32_t type = ref.header.get_short_type(); |
545 | |
|
546 | 0 | if (type == fourcc("thmb")) { |
547 | | // --- this is a thumbnail image, attach to the main image |
548 | |
|
549 | 0 | std::vector<heif_item_id> refs = ref.to_item_ID; |
550 | 0 | for (heif_item_id ref: refs) { |
551 | 0 | image->set_is_thumbnail(); |
552 | |
|
553 | 0 | auto master_iter = m_all_images.find(ref); |
554 | 0 | if (master_iter == m_all_images.end()) { |
555 | 0 | return Error(heif_error_Invalid_input, |
556 | 0 | heif_suberror_Nonexisting_item_referenced, |
557 | 0 | "Thumbnail references a non-existing image"); |
558 | 0 | } |
559 | | |
560 | 0 | if (master_iter->second->is_thumbnail()) { |
561 | 0 | return Error(heif_error_Invalid_input, |
562 | 0 | heif_suberror_Nonexisting_item_referenced, |
563 | 0 | "Thumbnail references another thumbnail"); |
564 | 0 | } |
565 | | |
566 | 0 | if (image.get() == master_iter->second.get()) { |
567 | 0 | return Error(heif_error_Invalid_input, |
568 | 0 | heif_suberror_Nonexisting_item_referenced, |
569 | 0 | "Recursive thumbnail image detected"); |
570 | 0 | } |
571 | 0 | master_iter->second->add_thumbnail(image); |
572 | 0 | } |
573 | 0 | remove_top_level_image(image); |
574 | 0 | } |
575 | 0 | else if (type == fourcc("auxl")) { |
576 | | |
577 | | // --- this is an auxiliary image |
578 | | // check whether it is an alpha channel and attach to the main image if yes |
579 | |
|
580 | 0 | std::shared_ptr<Box_auxC> auxC_property = image->get_property<Box_auxC>(); |
581 | 0 | if (!auxC_property) { |
582 | 0 | std::stringstream sstr; |
583 | 0 | sstr << "No auxC property for image " << image->get_id(); |
584 | 0 | return Error(heif_error_Invalid_input, |
585 | 0 | heif_suberror_Auxiliary_image_type_unspecified, |
586 | 0 | sstr.str()); |
587 | 0 | } |
588 | | |
589 | 0 | std::vector<heif_item_id> refs = ref.to_item_ID; |
590 | | |
591 | | // alpha channel |
592 | |
|
593 | 0 | if (auxC_property->get_aux_type() == "urn:mpeg:avc:2015:auxid:1" || // HEIF (avc) |
594 | 0 | auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:1" || // HEIF (h265) |
595 | 0 | auxC_property->get_aux_type() == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha") { // MIAF |
596 | |
|
597 | 0 | for (heif_item_id ref: refs) { |
598 | 0 | auto master_iter = m_all_images.find(ref); |
599 | 0 | if (master_iter == m_all_images.end()) { |
600 | |
|
601 | 0 | if (!m_heif_file->has_item_with_id(ref)) { |
602 | 0 | return Error(heif_error_Invalid_input, |
603 | 0 | heif_suberror_Nonexisting_item_referenced, |
604 | 0 | "Non-existing alpha image referenced"); |
605 | 0 | } |
606 | | |
607 | 0 | continue; |
608 | 0 | } |
609 | | |
610 | 0 | auto master_img = master_iter->second; |
611 | |
|
612 | 0 | if (image.get() == master_img.get()) { |
613 | 0 | return Error(heif_error_Invalid_input, |
614 | 0 | heif_suberror_Nonexisting_item_referenced, |
615 | 0 | "Recursive alpha image detected"); |
616 | 0 | } |
617 | | |
618 | 0 | image->set_is_alpha_channel(); |
619 | 0 | master_img->set_alpha_channel(image); |
620 | 0 | } |
621 | 0 | } |
622 | | |
623 | | |
624 | | // depth channel |
625 | | |
626 | 0 | if (auxC_property->get_aux_type() == "urn:mpeg:hevc:2015:auxid:2" || // HEIF |
627 | 0 | auxC_property->get_aux_type() == "urn:mpeg:mpegB:cicp:systems:auxiliary:depth") { // AVIF |
628 | 0 | image->set_is_depth_channel(); |
629 | |
|
630 | 0 | for (heif_item_id ref: refs) { |
631 | 0 | auto master_iter = m_all_images.find(ref); |
632 | 0 | if (master_iter == m_all_images.end()) { |
633 | |
|
634 | 0 | if (!m_heif_file->has_item_with_id(ref)) { |
635 | 0 | return Error(heif_error_Invalid_input, |
636 | 0 | heif_suberror_Nonexisting_item_referenced, |
637 | 0 | "Non-existing depth image referenced"); |
638 | 0 | } |
639 | | |
640 | 0 | continue; |
641 | 0 | } |
642 | 0 | if (image.get() == master_iter->second.get()) { |
643 | 0 | return Error(heif_error_Invalid_input, |
644 | 0 | heif_suberror_Nonexisting_item_referenced, |
645 | 0 | "Recursive depth image detected"); |
646 | 0 | } |
647 | 0 | master_iter->second->set_depth_channel(image); |
648 | |
|
649 | 0 | const auto& subtypes = auxC_property->get_subtypes(); |
650 | |
|
651 | 0 | if (!subtypes.empty()) { |
652 | 0 | std::vector<std::shared_ptr<SEIMessage>> sei_messages; |
653 | 0 | Error err = decode_hevc_aux_sei_messages(subtypes, sei_messages); |
654 | 0 | if (err) { |
655 | 0 | return err; |
656 | 0 | } |
657 | | |
658 | 0 | for (auto& msg : sei_messages) { |
659 | 0 | auto depth_msg = std::dynamic_pointer_cast<SEIMessage_depth_representation_info>(msg); |
660 | 0 | if (depth_msg) { |
661 | 0 | image->set_depth_representation_info(*depth_msg); |
662 | 0 | } |
663 | 0 | } |
664 | 0 | } |
665 | 0 | } |
666 | 0 | } |
667 | | |
668 | | |
669 | | // --- generic aux image |
670 | | |
671 | 0 | image->set_is_aux_image(auxC_property->get_aux_type()); |
672 | |
|
673 | 0 | for (heif_item_id ref: refs) { |
674 | 0 | auto master_iter = m_all_images.find(ref); |
675 | 0 | if (master_iter == m_all_images.end()) { |
676 | |
|
677 | 0 | if (!m_heif_file->has_item_with_id(ref)) { |
678 | 0 | return Error(heif_error_Invalid_input, |
679 | 0 | heif_suberror_Nonexisting_item_referenced, |
680 | 0 | "Non-existing aux image referenced"); |
681 | 0 | } |
682 | | |
683 | 0 | continue; |
684 | 0 | } |
685 | 0 | if (image.get() == master_iter->second.get()) { |
686 | 0 | return Error(heif_error_Invalid_input, |
687 | 0 | heif_suberror_Nonexisting_item_referenced, |
688 | 0 | "Recursive aux image detected"); |
689 | 0 | } |
690 | | |
691 | 0 | master_iter->second->add_aux_image(image); |
692 | |
|
693 | 0 | remove_top_level_image(image); |
694 | 0 | } |
695 | 0 | } |
696 | 0 | else { |
697 | | // 'image' is a normal image, keep it as a top-level image |
698 | 0 | } |
699 | 0 | } |
700 | 0 | } |
701 | 0 | } |
702 | | |
703 | | |
704 | | // --- check that HEVC images have an hvcC property |
705 | | |
706 | 0 | for (auto& pair : m_all_images) { |
707 | 0 | auto& image = pair.second; |
708 | |
|
709 | 0 | if (image->get_item_error()) { |
710 | 0 | continue; |
711 | 0 | } |
712 | | |
713 | 0 | std::shared_ptr<Box_infe> infe = m_heif_file->get_infe_box(image->get_id()); |
714 | 0 | if (infe->get_item_type_4cc() == fourcc("hvc1")) { |
715 | |
|
716 | 0 | auto ipma = m_heif_file->get_ipma_box(); |
717 | 0 | auto ipco = m_heif_file->get_ipco_box(); |
718 | |
|
719 | 0 | if (!ipco->get_property_for_item_ID(image->get_id(), ipma, fourcc("hvcC"))) { |
720 | 0 | return Error(heif_error_Invalid_input, |
721 | 0 | heif_suberror_No_hvcC_box, |
722 | 0 | "No hvcC property in hvc1 type image"); |
723 | 0 | } |
724 | 0 | } |
725 | 0 | if (infe->get_item_type_4cc() == fourcc("vvc1")) { |
726 | |
|
727 | 0 | auto ipma = m_heif_file->get_ipma_box(); |
728 | 0 | auto ipco = m_heif_file->get_ipco_box(); |
729 | |
|
730 | 0 | if (!ipco->get_property_for_item_ID(image->get_id(), ipma, fourcc("vvcC"))) { |
731 | 0 | return Error(heif_error_Invalid_input, |
732 | 0 | heif_suberror_No_vvcC_box, |
733 | 0 | "No vvcC property in vvc1 type image"); |
734 | 0 | } |
735 | 0 | } |
736 | 0 | } |
737 | | |
738 | | |
739 | | // --- assign color profile from grid tiles to main image when main image has no profile assigned |
740 | | |
741 | 0 | for (auto& pair : m_all_images) { |
742 | 0 | auto& image = pair.second; |
743 | 0 | auto id = pair.first; |
744 | |
|
745 | 0 | if (image->get_item_error()) { |
746 | 0 | continue; |
747 | 0 | } |
748 | | |
749 | 0 | auto infe_box = m_heif_file->get_infe_box(id); |
750 | 0 | if (!infe_box) { |
751 | 0 | continue; |
752 | 0 | } |
753 | | |
754 | 0 | if (!iref_box) { |
755 | 0 | break; |
756 | 0 | } |
757 | | |
758 | 0 | if (infe_box->get_item_type_4cc() == fourcc("grid")) { |
759 | 0 | std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg")); |
760 | |
|
761 | 0 | if (image_references.empty()) { |
762 | 0 | continue; // TODO: can this every happen? |
763 | 0 | } |
764 | | |
765 | 0 | auto tileId = image_references.front(); |
766 | |
|
767 | 0 | auto iter = m_all_images.find(tileId); |
768 | 0 | if (iter == m_all_images.end()) { |
769 | 0 | continue; // invalid grid entry |
770 | 0 | } |
771 | | |
772 | 0 | auto tile_img = iter->second; |
773 | 0 | if (image->get_color_profile_icc() == nullptr && tile_img->get_color_profile_icc()) { |
774 | 0 | image->set_color_profile(tile_img->get_color_profile_icc()); |
775 | 0 | } |
776 | |
|
777 | 0 | if (image->get_color_profile_nclx() == nullptr && tile_img->get_color_profile_nclx()) { |
778 | 0 | image->set_color_profile(tile_img->get_color_profile_nclx()); |
779 | 0 | } |
780 | 0 | } |
781 | 0 | } |
782 | | |
783 | | |
784 | | // --- read metadata and assign to image |
785 | |
|
786 | 0 | for (heif_item_id id : image_IDs) { |
787 | 0 | uint32_t item_type = m_heif_file->get_item_type_4cc(id); |
788 | 0 | std::string content_type = m_heif_file->get_content_type(id); |
789 | | |
790 | | // 'rgan': skip region annotations, handled next |
791 | | // 'iden': iden images are no metadata |
792 | 0 | if (item_type_is_image(item_type, content_type) || item_type == fourcc("rgan")) { |
793 | 0 | continue; |
794 | 0 | } |
795 | | |
796 | 0 | std::string item_uri_type = m_heif_file->get_item_uri_type(id); |
797 | | |
798 | | // we now assign all kinds of metadata to the image, not only 'Exif' and 'XMP' |
799 | |
|
800 | 0 | std::shared_ptr<ImageMetadata> metadata = std::make_shared<ImageMetadata>(); |
801 | 0 | metadata->item_id = id; |
802 | 0 | metadata->item_type = fourcc_to_string(item_type); |
803 | 0 | metadata->content_type = content_type; |
804 | 0 | metadata->item_uri_type = std::move(item_uri_type); |
805 | |
|
806 | 0 | Error err = m_heif_file->get_uncompressed_item_data(id, &(metadata->m_data)); |
807 | 0 | if (err) { |
808 | 0 | if (item_type == fourcc("Exif") || item_type == fourcc("mime")) { |
809 | | // these item types should have data |
810 | 0 | return err; |
811 | 0 | } |
812 | 0 | #if WITH_EXPERIMENTAL_GAIN_MAP |
813 | 0 | else if (check_tmap_item && item_type == fourcc("tmap")) { |
814 | | // this also should have data |
815 | 0 | return err; |
816 | 0 | } |
817 | 0 | #endif |
818 | 0 | else { |
819 | | // anything else is probably something that we don't understand yet |
820 | 0 | continue; |
821 | 0 | } |
822 | 0 | } |
823 | | |
824 | | |
825 | | // --- assign metadata to the image |
826 | | |
827 | 0 | if (iref_box) { |
828 | 0 | std::vector<heif_item_id> references = iref_box->get_references(id, fourcc("cdsc")); |
829 | 0 | for (heif_item_id exif_image_id : references) { |
830 | 0 | auto img_iter = m_all_images.find(exif_image_id); |
831 | 0 | if (img_iter == m_all_images.end()) { |
832 | 0 | if (!m_heif_file->has_item_with_id(exif_image_id)) { |
833 | 0 | return Error(heif_error_Invalid_input, |
834 | 0 | heif_suberror_Nonexisting_item_referenced, |
835 | 0 | "Metadata assigned to non-existing image"); |
836 | 0 | } |
837 | | |
838 | 0 | continue; |
839 | 0 | } |
840 | 0 | img_iter->second->add_metadata(metadata); |
841 | 0 | } |
842 | 0 | #if WITH_EXPERIMENTAL_GAIN_MAP |
843 | 0 | if (check_tmap_item && item_type == fourcc("tmap")) { |
844 | 0 | std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg")); |
845 | | // "'tmap' item MUST be associated with 2 references to images" |
846 | | // "'tmap' item first entry is expected to be referencing primary image" |
847 | | // "'tmap' item reference entries MUST not be duplicate" |
848 | | // "'tmap' item references MUST be pointing to valid image items" |
849 | 0 | if ((int)image_references.size() == 2 && |
850 | 0 | image_references[0] == m_heif_file->get_primary_image_ID() && |
851 | 0 | image_references[1] != image_references[0] && |
852 | 0 | m_all_images.find(image_references[0]) != m_all_images.end() && |
853 | 0 | m_all_images.find(image_references[1]) != m_all_images.end()) { |
854 | 0 | std::shared_ptr<ImageItem> baseItem = m_all_images.find(image_references[0])->second; |
855 | 0 | std::shared_ptr<ImageItem> gainmapItem = m_all_images.find(image_references[1])->second; |
856 | |
|
857 | 0 | baseItem->set_gain_map(gainmapItem); |
858 | |
|
859 | 0 | baseItem->add_metadata(metadata); // gain map metadata |
860 | |
|
861 | 0 | auto ipma = m_heif_file->get_ipma_box(); |
862 | 0 | auto ipco = m_heif_file->get_ipco_box(); |
863 | 0 | auto derived_image_colr = ipco->get_property_for_item_ID(id, ipma, fourcc("colr")); |
864 | 0 | auto colr = std::dynamic_pointer_cast<Box_colr>(derived_image_colr); |
865 | 0 | auto nclx = |
866 | 0 | std::dynamic_pointer_cast<const color_profile_nclx>(colr->get_color_profile()); |
867 | 0 | if (nclx) { |
868 | 0 | baseItem->set_derived_img_color_profile(nclx); |
869 | 0 | } |
870 | 0 | auto raw = std::dynamic_pointer_cast<const color_profile_raw>(colr->get_color_profile()); |
871 | 0 | if (raw) { |
872 | 0 | baseItem->set_derived_img_color_profile(raw); |
873 | 0 | } |
874 | 0 | } |
875 | 0 | } |
876 | 0 | #endif |
877 | 0 | } |
878 | 0 | } |
879 | | |
880 | | // --- set premultiplied alpha flag |
881 | | |
882 | 0 | for (heif_item_id id : image_IDs) { |
883 | 0 | if (iref_box) { |
884 | 0 | std::vector<heif_item_id> references = iref_box->get_references(id, fourcc("prem")); |
885 | 0 | for (heif_item_id ref : references) { |
886 | 0 | (void)ref; |
887 | |
|
888 | 0 | heif_item_id color_image_id = id; |
889 | 0 | auto img_iter = m_all_images.find(color_image_id); |
890 | 0 | if (img_iter == m_all_images.end()) { |
891 | 0 | return Error(heif_error_Invalid_input, |
892 | 0 | heif_suberror_Nonexisting_item_referenced, |
893 | 0 | "`prem` link assigned to non-existing image"); |
894 | 0 | } |
895 | | |
896 | 0 | img_iter->second->set_is_premultiplied_alpha(true); |
897 | 0 | } |
898 | 0 | } |
899 | 0 | } |
900 | | |
901 | | // --- read region item and assign to image(s) |
902 | | |
903 | 0 | for (heif_item_id id : image_IDs) { |
904 | 0 | uint32_t item_type = m_heif_file->get_item_type_4cc(id); |
905 | 0 | if (item_type != fourcc("rgan")) { |
906 | 0 | continue; |
907 | 0 | } |
908 | | |
909 | 0 | std::shared_ptr<RegionItem> region_item = std::make_shared<RegionItem>(); |
910 | 0 | region_item->item_id = id; |
911 | 0 | std::vector<uint8_t> region_data; |
912 | 0 | Error err = m_heif_file->get_uncompressed_item_data(id, ®ion_data); |
913 | 0 | if (err) { |
914 | 0 | return err; |
915 | 0 | } |
916 | 0 | region_item->parse(region_data); |
917 | 0 | if (iref_box) { |
918 | 0 | std::vector<Box_iref::Reference> references = iref_box->get_references_from(id); |
919 | 0 | for (const auto& ref : references) { |
920 | 0 | if (ref.header.get_short_type() == fourcc("cdsc")) { |
921 | 0 | std::vector<uint32_t> refs = ref.to_item_ID; |
922 | 0 | for (uint32_t ref : refs) { |
923 | 0 | uint32_t image_id = ref; |
924 | 0 | auto img_iter = m_all_images.find(image_id); |
925 | 0 | if (img_iter == m_all_images.end()) { |
926 | 0 | return Error(heif_error_Invalid_input, |
927 | 0 | heif_suberror_Nonexisting_item_referenced, |
928 | 0 | "Region item assigned to non-existing image"); |
929 | 0 | } |
930 | 0 | img_iter->second->add_region_item_id(id); |
931 | 0 | m_region_items.push_back(region_item); |
932 | 0 | } |
933 | 0 | } |
934 | | |
935 | | /* When the geometry 'mask' of a region is represented by a mask stored in |
936 | | * another image item the image item containing the mask shall be identified |
937 | | * by an item reference of type 'mask' from the region item to the image item |
938 | | * containing the mask. */ |
939 | 0 | if (ref.header.get_short_type() == fourcc("mask")) { |
940 | 0 | std::vector<uint32_t> refs = ref.to_item_ID; |
941 | 0 | size_t mask_index = 0; |
942 | 0 | for (int j = 0; j < region_item->get_number_of_regions(); j++) { |
943 | 0 | if (region_item->get_regions()[j]->getRegionType() == heif_region_type_referenced_mask) { |
944 | 0 | std::shared_ptr<RegionGeometry_ReferencedMask> mask_geometry = std::dynamic_pointer_cast<RegionGeometry_ReferencedMask>(region_item->get_regions()[j]); |
945 | |
|
946 | 0 | if (mask_index >= refs.size()) { |
947 | 0 | return Error(heif_error_Invalid_input, |
948 | 0 | heif_suberror_Unspecified, |
949 | 0 | "Region mask reference with non-existing mask image reference"); |
950 | 0 | } |
951 | | |
952 | 0 | uint32_t mask_image_id = refs[mask_index]; |
953 | 0 | if (!is_image(mask_image_id)) { |
954 | 0 | return Error(heif_error_Invalid_input, |
955 | 0 | heif_suberror_Unspecified, |
956 | 0 | "Region mask referenced item is not an image"); |
957 | 0 | } |
958 | | |
959 | 0 | auto mask_image = get_image(mask_image_id, true); |
960 | 0 | if (auto error = mask_image->get_item_error()) { |
961 | 0 | return error; |
962 | 0 | } |
963 | | |
964 | 0 | mask_geometry->referenced_item = mask_image_id; |
965 | 0 | if (mask_geometry->width == 0) { |
966 | 0 | mask_geometry->width = mask_image->get_ispe_width(); |
967 | 0 | } |
968 | 0 | if (mask_geometry->height == 0) { |
969 | 0 | mask_geometry->height = mask_image->get_ispe_height(); |
970 | 0 | } |
971 | 0 | mask_index += 1; |
972 | 0 | remove_top_level_image(mask_image); |
973 | 0 | } |
974 | 0 | } |
975 | 0 | } |
976 | 0 | } |
977 | 0 | } |
978 | 0 | } |
979 | | |
980 | 0 | return Error::Ok; |
981 | 0 | } |
982 | | |
983 | | |
984 | | bool HeifContext::has_alpha(heif_item_id ID) const |
985 | 0 | { |
986 | 0 | auto imgIter = m_all_images.find(ID); |
987 | 0 | if (imgIter == m_all_images.end()) { |
988 | 0 | return false; |
989 | 0 | } |
990 | | |
991 | 0 | auto img = imgIter->second; |
992 | | |
993 | | // --- has the image an auxiliary alpha image? |
994 | |
|
995 | 0 | if (img->get_alpha_channel() != nullptr) { |
996 | 0 | return true; |
997 | 0 | } |
998 | | |
999 | 0 | if (img->has_coded_alpha_channel()) { |
1000 | 0 | return true; |
1001 | 0 | } |
1002 | | |
1003 | 0 | heif_colorspace colorspace; |
1004 | 0 | heif_chroma chroma; |
1005 | 0 | Error err = img->get_coded_image_colorspace(&colorspace, &chroma); |
1006 | 0 | if (err) { |
1007 | 0 | return false; |
1008 | 0 | } |
1009 | | |
1010 | 0 | if (chroma == heif_chroma_interleaved_RGBA || |
1011 | 0 | chroma == heif_chroma_interleaved_RRGGBBAA_BE || |
1012 | 0 | chroma == heif_chroma_interleaved_RRGGBBAA_LE) { |
1013 | 0 | return true; |
1014 | 0 | } |
1015 | | |
1016 | | // --- if the image is a 'grid', check if there is alpha in any of the tiles |
1017 | | |
1018 | | // TODO: move this into ImageItem |
1019 | | |
1020 | 0 | uint32_t image_type = m_heif_file->get_item_type_4cc(ID); |
1021 | 0 | if (image_type == fourcc("grid")) { |
1022 | 0 | std::vector<uint8_t> grid_data; |
1023 | 0 | Error error = m_heif_file->get_uncompressed_item_data(ID, &grid_data); |
1024 | 0 | if (error) { |
1025 | 0 | return false; |
1026 | 0 | } |
1027 | | |
1028 | 0 | ImageGrid grid; |
1029 | 0 | err = grid.parse(grid_data); |
1030 | 0 | if (err) { |
1031 | 0 | return false; |
1032 | 0 | } |
1033 | | |
1034 | | |
1035 | 0 | auto iref_box = m_heif_file->get_iref_box(); |
1036 | |
|
1037 | 0 | if (!iref_box) { |
1038 | 0 | return false; |
1039 | 0 | } |
1040 | | |
1041 | 0 | std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg")); |
1042 | |
|
1043 | 0 | if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) { |
1044 | 0 | return false; |
1045 | 0 | } |
1046 | | |
1047 | | |
1048 | | // --- check that all image IDs are valid images |
1049 | | |
1050 | 0 | for (heif_item_id tile_id : image_references) { |
1051 | 0 | if (!is_image(tile_id)) { |
1052 | 0 | return false; |
1053 | 0 | } |
1054 | 0 | } |
1055 | | |
1056 | | // --- check whether at least one tile has an alpha channel |
1057 | | |
1058 | 0 | bool has_alpha = false; |
1059 | |
|
1060 | 0 | for (heif_item_id tile_id : image_references) { |
1061 | 0 | auto iter = m_all_images.find(tile_id); |
1062 | 0 | if (iter == m_all_images.end()) { |
1063 | 0 | return false; |
1064 | 0 | } |
1065 | | |
1066 | 0 | const std::shared_ptr<ImageItem> tileImg = iter->second; |
1067 | |
|
1068 | 0 | has_alpha |= tileImg->get_alpha_channel() != nullptr; |
1069 | 0 | } |
1070 | | |
1071 | 0 | return has_alpha; |
1072 | 0 | } |
1073 | 0 | else { |
1074 | | // TODO: what about overlays ? |
1075 | 0 | return false; |
1076 | 0 | } |
1077 | 0 | } |
1078 | | |
1079 | | |
1080 | | Error HeifContext::get_id_of_non_virtual_child_image(heif_item_id id, heif_item_id& out) const |
1081 | 0 | { |
1082 | 0 | uint32_t image_type = m_heif_file->get_item_type_4cc(id); |
1083 | 0 | if (image_type == fourcc("grid") || |
1084 | 0 | image_type == fourcc("iden") || |
1085 | 0 | image_type == fourcc("iovl")) { |
1086 | 0 | auto iref_box = m_heif_file->get_iref_box(); |
1087 | 0 | if (!iref_box) { |
1088 | 0 | return Error(heif_error_Invalid_input, |
1089 | 0 | heif_suberror_No_item_data, |
1090 | 0 | "Derived image does not reference any other image items"); |
1091 | 0 | } |
1092 | | |
1093 | 0 | std::vector<heif_item_id> image_references = iref_box->get_references(id, fourcc("dimg")); |
1094 | | |
1095 | | // TODO: check whether this really can be recursive (e.g. overlay of grid images) |
1096 | |
|
1097 | 0 | if (image_references.empty() || image_references[0] == id) { |
1098 | 0 | return Error(heif_error_Invalid_input, |
1099 | 0 | heif_suberror_No_item_data, |
1100 | 0 | "Derived image does not reference any other image items"); |
1101 | 0 | } |
1102 | 0 | else { |
1103 | 0 | return get_id_of_non_virtual_child_image(image_references[0], out); |
1104 | 0 | } |
1105 | 0 | } |
1106 | 0 | else { |
1107 | 0 | if (m_all_images.find(id) == m_all_images.end()) { |
1108 | 0 | std::stringstream sstr; |
1109 | 0 | sstr << "Image item " << id << " referenced, but it does not exist\n"; |
1110 | |
|
1111 | 0 | return Error(heif_error_Invalid_input, |
1112 | 0 | heif_suberror_Nonexisting_item_referenced, |
1113 | 0 | sstr.str()); |
1114 | 0 | } |
1115 | 0 | else if (dynamic_cast<ImageItem_Error*>(m_all_images.find(id)->second.get())) { |
1116 | | // Should er return an error here or leave it to the follow-up code to detect that? |
1117 | 0 | } |
1118 | | |
1119 | 0 | out = id; |
1120 | 0 | return Error::Ok; |
1121 | 0 | } |
1122 | 0 | } |
1123 | | |
1124 | | |
1125 | | Result<std::shared_ptr<HeifPixelImage>> HeifContext::decode_image(heif_item_id ID, |
1126 | | heif_colorspace out_colorspace, |
1127 | | heif_chroma out_chroma, |
1128 | | const struct heif_decoding_options& options, |
1129 | | bool decode_only_tile, uint32_t tx, uint32_t ty) const |
1130 | 0 | { |
1131 | 0 | std::shared_ptr<ImageItem> imgitem; |
1132 | 0 | if (m_all_images.find(ID) != m_all_images.end()) { |
1133 | 0 | imgitem = m_all_images.find(ID)->second; |
1134 | 0 | } |
1135 | | |
1136 | | // Note: this may happen, for example when an 'iden' image references a non-existing image item. |
1137 | 0 | if (imgitem == nullptr) { |
1138 | 0 | return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced); |
1139 | 0 | } |
1140 | | |
1141 | | |
1142 | 0 | auto decodingResult = imgitem->decode_image(options, decode_only_tile, tx, ty); |
1143 | 0 | if (decodingResult.error) { |
1144 | 0 | return decodingResult.error; |
1145 | 0 | } |
1146 | | |
1147 | 0 | std::shared_ptr<HeifPixelImage> img = decodingResult.value; |
1148 | | |
1149 | | |
1150 | | // --- convert to output chroma format |
1151 | |
|
1152 | 0 | heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ? |
1153 | 0 | img->get_colorspace() : |
1154 | 0 | out_colorspace); |
1155 | |
|
1156 | 0 | heif_chroma target_chroma = (out_chroma == heif_chroma_undefined ? |
1157 | 0 | img->get_chroma_format() : out_chroma); |
1158 | |
|
1159 | 0 | bool different_chroma = (target_chroma != img->get_chroma_format()); |
1160 | 0 | bool different_colorspace = (target_colorspace != img->get_colorspace()); |
1161 | |
|
1162 | 0 | uint8_t img_bpp = img->get_visual_image_bits_per_pixel(); |
1163 | 0 | uint8_t converted_output_bpp = (options.convert_hdr_to_8bit && img_bpp > 8) ? 8 : 0 /* keep input depth */; |
1164 | |
|
1165 | 0 | if (different_chroma || |
1166 | 0 | different_colorspace || |
1167 | 0 | converted_output_bpp) { |
1168 | |
|
1169 | 0 | auto img_result = convert_colorspace(img, target_colorspace, target_chroma, nullptr, |
1170 | 0 | converted_output_bpp, options.color_conversion_options, get_security_limits()); |
1171 | 0 | if (img_result.error) { |
1172 | 0 | return img_result.error; |
1173 | 0 | } |
1174 | 0 | else { |
1175 | 0 | img = *img_result; |
1176 | 0 | } |
1177 | 0 | } |
1178 | | |
1179 | 0 | img->add_warnings(imgitem->get_decoding_warnings()); |
1180 | |
|
1181 | 0 | return img; |
1182 | 0 | } |
1183 | | |
1184 | | |
1185 | | static Result<std::shared_ptr<HeifPixelImage>> |
1186 | | create_alpha_image_from_image_alpha_channel(const std::shared_ptr<HeifPixelImage>& image, |
1187 | | const heif_security_limits* limits) |
1188 | 0 | { |
1189 | | // --- generate alpha image |
1190 | |
|
1191 | 0 | std::shared_ptr<HeifPixelImage> alpha_image = std::make_shared<HeifPixelImage>(); |
1192 | 0 | alpha_image->create(image->get_width(), image->get_height(), |
1193 | 0 | heif_colorspace_monochrome, heif_chroma_monochrome); |
1194 | |
|
1195 | 0 | if (image->has_channel(heif_channel_Alpha)) { |
1196 | 0 | alpha_image->copy_new_plane_from(image, heif_channel_Alpha, heif_channel_Y, limits); |
1197 | 0 | } |
1198 | 0 | else if (image->get_chroma_format() == heif_chroma_interleaved_RGBA) { |
1199 | 0 | if (auto err = alpha_image->extract_alpha_from_RGBA(image, limits)) { |
1200 | 0 | return err; |
1201 | 0 | } |
1202 | 0 | } |
1203 | | // TODO: 16 bit |
1204 | | |
1205 | | // --- set nclx profile with full-range flag |
1206 | | |
1207 | 0 | auto nclx = std::make_shared<color_profile_nclx>(); |
1208 | 0 | nclx->set_undefined(); |
1209 | 0 | nclx->set_full_range_flag(true); // this is the default, but just to be sure in case the defaults change |
1210 | 0 | alpha_image->set_color_profile_nclx(nclx); |
1211 | |
|
1212 | 0 | return alpha_image; |
1213 | 0 | } |
1214 | | |
1215 | | |
1216 | | #if WITH_EXPERIMENTAL_GAIN_MAP |
1217 | 0 | Error HeifContext::add_tmap_item(const std::vector<uint8_t>& data, heif_item_id& item_id) { |
1218 | 0 | auto tmap_infe = m_heif_file->add_new_infe_box(fourcc("tmap")); // gain map metadata |
1219 | 0 | tmap_infe->set_item_name("GMap"); |
1220 | 0 | item_id = tmap_infe->get_item_ID(); |
1221 | |
|
1222 | 0 | m_heif_file->append_iloc_data(item_id, data, 0); |
1223 | |
|
1224 | 0 | return Error::Ok; |
1225 | 0 | } |
1226 | | |
1227 | | |
1228 | | Error HeifContext::link_gain_map(const std::shared_ptr<ImageItem>& primary_image, |
1229 | | const std::shared_ptr<ImageItem>& gain_map_image, |
1230 | 0 | const heif_item_id tmap_id) { |
1231 | 0 | m_heif_file->add_iref_reference(tmap_id, fourcc("dimg"), |
1232 | 0 | {primary_image->get_id(), gain_map_image->get_id()}); |
1233 | |
|
1234 | 0 | return Error::Ok; |
1235 | 0 | } |
1236 | | #endif |
1237 | | |
1238 | | |
1239 | | Result<std::shared_ptr<ImageItem>> HeifContext::encode_image(const std::shared_ptr<HeifPixelImage>& pixel_image, |
1240 | | struct heif_encoder* encoder, |
1241 | | const struct heif_encoding_options& in_options, |
1242 | | enum heif_image_input_class input_class) |
1243 | 0 | { |
1244 | 0 | std::shared_ptr<ImageItem> output_image_item = ImageItem::alloc_for_compression_format(this, encoder->plugin->compression_format); |
1245 | | |
1246 | |
|
1247 | | #if 0 |
1248 | | // TODO: the hdlr box is not the right place for comments |
1249 | | // m_heif_file->set_hdlr_library_info(encoder->plugin->get_plugin_name()); |
1250 | | |
1251 | | case heif_compression_mask: { |
1252 | | error = encode_image_as_mask(pixel_image, |
1253 | | encoder, |
1254 | | options, |
1255 | | input_class, |
1256 | | out_image); |
1257 | | } |
1258 | | break; |
1259 | | |
1260 | | default: |
1261 | | return Error(heif_error_Encoder_plugin_error, heif_suberror_Unsupported_codec); |
1262 | | } |
1263 | | #endif |
1264 | | |
1265 | | |
1266 | | // --- check whether we have to convert the image color space |
1267 | | |
1268 | | // The reason for doing the color conversion here is that the input might be an RGBA image and the color conversion |
1269 | | // will extract the alpha plane anyway. We can reuse that plane below instead of having to do a new conversion. |
1270 | |
|
1271 | 0 | heif_encoding_options options = in_options; |
1272 | |
|
1273 | 0 | if (const auto* nclx = output_image_item->get_forced_output_nclx()) { |
1274 | 0 | options.output_nclx_profile = const_cast<heif_color_profile_nclx*>(nclx); |
1275 | 0 | } |
1276 | |
|
1277 | 0 | Result<std::shared_ptr<HeifPixelImage>> srcImageResult = output_image_item->convert_colorspace_for_encoding(pixel_image, |
1278 | 0 | encoder, |
1279 | 0 | options); |
1280 | 0 | if (srcImageResult.error) { |
1281 | 0 | return srcImageResult.error; |
1282 | 0 | } |
1283 | | |
1284 | 0 | std::shared_ptr<HeifPixelImage> colorConvertedImage = srcImageResult.value; |
1285 | | |
1286 | |
|
1287 | 0 | Error err = output_image_item->encode_to_item(this, |
1288 | 0 | colorConvertedImage, |
1289 | 0 | encoder, options, input_class); |
1290 | 0 | if (err) { |
1291 | 0 | return err; |
1292 | 0 | } |
1293 | | |
1294 | 0 | insert_image_item(output_image_item->get_id(), output_image_item); |
1295 | | |
1296 | | |
1297 | | // --- if there is an alpha channel, add it as an additional image |
1298 | |
|
1299 | 0 | if (options.save_alpha_channel && |
1300 | 0 | colorConvertedImage->has_alpha() && |
1301 | 0 | output_image_item->get_auxC_alpha_channel_type() != nullptr) { // does not need a separate alpha aux image |
1302 | | |
1303 | | // --- generate alpha image |
1304 | | // TODO: can we directly code a monochrome image instead of the dummy color channels? |
1305 | |
|
1306 | 0 | std::shared_ptr<HeifPixelImage> alpha_image; |
1307 | 0 | auto alpha_image_result = create_alpha_image_from_image_alpha_channel(colorConvertedImage, get_security_limits()); |
1308 | 0 | if (!alpha_image_result) { |
1309 | 0 | return alpha_image_result.error; |
1310 | 0 | } |
1311 | | |
1312 | 0 | alpha_image = *alpha_image_result; |
1313 | | |
1314 | | |
1315 | | // --- encode the alpha image |
1316 | |
|
1317 | 0 | auto alphaEncodingResult = encode_image(alpha_image, encoder, options, |
1318 | 0 | heif_image_input_class_alpha); |
1319 | 0 | if (alphaEncodingResult.error) { |
1320 | 0 | return alphaEncodingResult.error; |
1321 | 0 | } |
1322 | | |
1323 | 0 | std::shared_ptr<ImageItem> heif_alpha_image = *alphaEncodingResult; |
1324 | |
|
1325 | 0 | m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {output_image_item->get_id()}); |
1326 | 0 | m_heif_file->set_auxC_property(heif_alpha_image->get_id(), output_image_item->get_auxC_alpha_channel_type()); |
1327 | |
|
1328 | 0 | if (pixel_image->is_premultiplied_alpha()) { |
1329 | 0 | m_heif_file->add_iref_reference(output_image_item->get_id(), fourcc("prem"), {heif_alpha_image->get_id()}); |
1330 | 0 | } |
1331 | 0 | } |
1332 | | |
1333 | 0 | std::vector<std::shared_ptr<Box>> properties; |
1334 | 0 | err = m_heif_file->get_properties(output_image_item->get_id(), properties); |
1335 | 0 | if (err) { |
1336 | 0 | return err; |
1337 | 0 | } |
1338 | 0 | output_image_item->set_properties(properties); |
1339 | |
|
1340 | 0 | m_heif_file->set_brand(encoder->plugin->compression_format, |
1341 | 0 | output_image_item->is_miaf_compatible()); |
1342 | |
|
1343 | 0 | return output_image_item; |
1344 | 0 | } |
1345 | | |
1346 | | |
1347 | | void HeifContext::set_primary_image(const std::shared_ptr<ImageItem>& image) |
1348 | 0 | { |
1349 | | // update heif context |
1350 | |
|
1351 | 0 | if (m_primary_image) { |
1352 | 0 | m_primary_image->set_primary(false); |
1353 | 0 | } |
1354 | |
|
1355 | 0 | image->set_primary(true); |
1356 | 0 | m_primary_image = image; |
1357 | | |
1358 | | |
1359 | | // update pitm box in HeifFile |
1360 | |
|
1361 | 0 | m_heif_file->set_primary_item_id(image->get_id()); |
1362 | 0 | } |
1363 | | |
1364 | | |
1365 | | Error HeifContext::assign_thumbnail(const std::shared_ptr<ImageItem>& master_image, |
1366 | | const std::shared_ptr<ImageItem>& thumbnail_image) |
1367 | 0 | { |
1368 | 0 | m_heif_file->add_iref_reference(thumbnail_image->get_id(), |
1369 | 0 | fourcc("thmb"), {master_image->get_id()}); |
1370 | |
|
1371 | 0 | return Error::Ok; |
1372 | 0 | } |
1373 | | |
1374 | | |
1375 | | Result<std::shared_ptr<ImageItem>> HeifContext::encode_thumbnail(const std::shared_ptr<HeifPixelImage>& image, |
1376 | | struct heif_encoder* encoder, |
1377 | | const struct heif_encoding_options& options, |
1378 | | int bbox_size) |
1379 | 0 | { |
1380 | 0 | int orig_width = image->get_width(); |
1381 | 0 | int orig_height = image->get_height(); |
1382 | |
|
1383 | 0 | int thumb_width, thumb_height; |
1384 | |
|
1385 | 0 | if (orig_width <= bbox_size && orig_height <= bbox_size) { |
1386 | | // original image is smaller than thumbnail size -> do not encode any thumbnail |
1387 | |
|
1388 | 0 | return Error::Ok; |
1389 | 0 | } |
1390 | 0 | else if (orig_width > orig_height) { |
1391 | 0 | thumb_height = orig_height * bbox_size / orig_width; |
1392 | 0 | thumb_width = bbox_size; |
1393 | 0 | } |
1394 | 0 | else { |
1395 | 0 | thumb_width = orig_width * bbox_size / orig_height; |
1396 | 0 | thumb_height = bbox_size; |
1397 | 0 | } |
1398 | | |
1399 | | |
1400 | | // round size to even width and height |
1401 | | |
1402 | 0 | thumb_width &= ~1; |
1403 | 0 | thumb_height &= ~1; |
1404 | | |
1405 | |
|
1406 | 0 | std::shared_ptr<HeifPixelImage> thumbnail_image; |
1407 | 0 | Error error = image->scale_nearest_neighbor(thumbnail_image, thumb_width, thumb_height, get_security_limits()); |
1408 | 0 | if (error) { |
1409 | 0 | return error; |
1410 | 0 | } |
1411 | | |
1412 | 0 | auto encodingResult = encode_image(thumbnail_image, |
1413 | 0 | encoder, options, |
1414 | 0 | heif_image_input_class_thumbnail); |
1415 | 0 | if (encodingResult.error) { |
1416 | 0 | return encodingResult.error; |
1417 | 0 | } |
1418 | | |
1419 | 0 | return *encodingResult; |
1420 | 0 | } |
1421 | | |
1422 | | |
1423 | | Error HeifContext::add_exif_metadata(const std::shared_ptr<ImageItem>& master_image, const void* data, int size) |
1424 | 0 | { |
1425 | | // find location of TIFF header |
1426 | 0 | uint32_t offset = 0; |
1427 | 0 | const char* tiffmagic1 = "MM\0*"; |
1428 | 0 | const char* tiffmagic2 = "II*\0"; |
1429 | 0 | while (offset + 4 < (unsigned int) size) { |
1430 | 0 | if (!memcmp((uint8_t*) data + offset, tiffmagic1, 4)) break; |
1431 | 0 | if (!memcmp((uint8_t*) data + offset, tiffmagic2, 4)) break; |
1432 | 0 | offset++; |
1433 | 0 | } |
1434 | 0 | if (offset >= (unsigned int) size) { |
1435 | 0 | return Error(heif_error_Usage_error, |
1436 | 0 | heif_suberror_Invalid_parameter_value, |
1437 | 0 | "Could not find location of TIFF header in Exif metadata."); |
1438 | 0 | } |
1439 | | |
1440 | | |
1441 | 0 | std::vector<uint8_t> data_array; |
1442 | 0 | data_array.resize(size + 4); |
1443 | 0 | data_array[0] = (uint8_t) ((offset >> 24) & 0xFF); |
1444 | 0 | data_array[1] = (uint8_t) ((offset >> 16) & 0xFF); |
1445 | 0 | data_array[2] = (uint8_t) ((offset >> 8) & 0xFF); |
1446 | 0 | data_array[3] = (uint8_t) ((offset) & 0xFF); |
1447 | 0 | memcpy(data_array.data() + 4, data, size); |
1448 | | |
1449 | |
|
1450 | 0 | return add_generic_metadata(master_image, |
1451 | 0 | data_array.data(), (int) data_array.size(), |
1452 | 0 | fourcc("Exif"), nullptr, nullptr, heif_metadata_compression_off, nullptr); |
1453 | 0 | } |
1454 | | |
1455 | | |
1456 | | Error HeifContext::add_XMP_metadata(const std::shared_ptr<ImageItem>& master_image, const void* data, int size, |
1457 | | heif_metadata_compression compression) |
1458 | 0 | { |
1459 | 0 | return add_generic_metadata(master_image, data, size, fourcc("mime"), "application/rdf+xml", nullptr, compression, nullptr); |
1460 | 0 | } |
1461 | | |
1462 | | |
1463 | | Error HeifContext::add_generic_metadata(const std::shared_ptr<ImageItem>& master_image, const void* data, int size, |
1464 | | uint32_t item_type, const char* content_type, const char* item_uri_type, heif_metadata_compression compression, |
1465 | | heif_item_id* out_item_id) |
1466 | 0 | { |
1467 | | // create an infe box describing what kind of data we are storing (this also creates a new ID) |
1468 | |
|
1469 | 0 | auto metadata_infe_box = m_heif_file->add_new_infe_box(item_type); |
1470 | 0 | metadata_infe_box->set_hidden_item(true); |
1471 | 0 | if (content_type != nullptr) { |
1472 | 0 | metadata_infe_box->set_content_type(content_type); |
1473 | 0 | } |
1474 | |
|
1475 | 0 | heif_item_id metadata_id = metadata_infe_box->get_item_ID(); |
1476 | 0 | if (out_item_id) { |
1477 | 0 | *out_item_id = metadata_id; |
1478 | 0 | } |
1479 | | |
1480 | | |
1481 | | // we assign this data to the image |
1482 | |
|
1483 | 0 | m_heif_file->add_iref_reference(metadata_id, |
1484 | 0 | fourcc("cdsc"), {master_image->get_id()}); |
1485 | | |
1486 | | |
1487 | | // --- metadata compression |
1488 | |
|
1489 | 0 | if (compression == heif_metadata_compression_auto) { |
1490 | 0 | compression = heif_metadata_compression_off; // currently, we don't use header compression by default |
1491 | 0 | } |
1492 | | |
1493 | | // only set metadata compression for MIME type data which has 'content_encoding' field |
1494 | 0 | if (compression != heif_metadata_compression_off && |
1495 | 0 | item_type != fourcc("mime")) { |
1496 | | // TODO: error, compression not supported |
1497 | 0 | } |
1498 | | |
1499 | |
|
1500 | 0 | std::vector<uint8_t> data_array; |
1501 | 0 | if (compression == heif_metadata_compression_zlib) { |
1502 | | #if HAVE_ZLIB |
1503 | | data_array = compress_zlib((const uint8_t*) data, size); |
1504 | | metadata_infe_box->set_content_encoding("compress_zlib"); |
1505 | | #else |
1506 | 0 | return Error(heif_error_Unsupported_feature, |
1507 | 0 | heif_suberror_Unsupported_header_compression_method); |
1508 | 0 | #endif |
1509 | 0 | } |
1510 | 0 | else if (compression == heif_metadata_compression_deflate) { |
1511 | | #if HAVE_ZLIB |
1512 | | data_array = compress_zlib((const uint8_t*) data, size); |
1513 | | metadata_infe_box->set_content_encoding("deflate"); |
1514 | | #else |
1515 | 0 | return Error(heif_error_Unsupported_feature, |
1516 | 0 | heif_suberror_Unsupported_header_compression_method); |
1517 | 0 | #endif |
1518 | 0 | } |
1519 | 0 | else { |
1520 | | // uncompressed data, plain copy |
1521 | |
|
1522 | 0 | data_array.resize(size); |
1523 | 0 | memcpy(data_array.data(), data, size); |
1524 | 0 | } |
1525 | | |
1526 | | // copy the data into the file, store the pointer to it in an iloc box entry |
1527 | | |
1528 | 0 | m_heif_file->append_iloc_data(metadata_id, data_array, 0); |
1529 | |
|
1530 | 0 | return Error::Ok; |
1531 | 0 | } |
1532 | | |
1533 | | |
1534 | | heif_property_id HeifContext::add_property(heif_item_id targetItem, std::shared_ptr<Box> property, bool essential) |
1535 | 0 | { |
1536 | 0 | heif_property_id id = m_heif_file->add_property(targetItem, property, essential); |
1537 | |
|
1538 | 0 | return id; |
1539 | 0 | } |
1540 | | |
1541 | | |
1542 | | Result<heif_item_id> HeifContext::add_pyramid_group(const std::vector<heif_item_id>& layer_item_ids) |
1543 | 0 | { |
1544 | 0 | struct pymd_entry |
1545 | 0 | { |
1546 | 0 | std::shared_ptr<ImageItem> item; |
1547 | 0 | uint32_t width = 0; |
1548 | 0 | }; |
1549 | | |
1550 | | // --- sort all images by size |
1551 | |
|
1552 | 0 | std::vector<pymd_entry> pymd_entries; |
1553 | 0 | for (auto id : layer_item_ids) { |
1554 | 0 | auto image_item = get_image(id, true); |
1555 | 0 | if (auto error = image_item->get_item_error()) { |
1556 | 0 | return error; |
1557 | 0 | } |
1558 | | |
1559 | 0 | pymd_entry entry; |
1560 | 0 | entry.item = image_item; |
1561 | 0 | entry.width = image_item->get_width(); |
1562 | 0 | pymd_entries.emplace_back(entry); |
1563 | 0 | } |
1564 | | |
1565 | 0 | std::sort(pymd_entries.begin(), pymd_entries.end(), [](const pymd_entry& a, const pymd_entry& b) { |
1566 | 0 | return a.width < b.width; |
1567 | 0 | }); |
1568 | | |
1569 | | |
1570 | | // --- generate pymd box |
1571 | |
|
1572 | 0 | auto pymd = std::make_shared<Box_pymd>(); |
1573 | 0 | std::vector<Box_pymd::LayerInfo> layers; |
1574 | 0 | std::vector<heif_item_id> ids; |
1575 | |
|
1576 | 0 | auto base_item = pymd_entries.back().item; |
1577 | |
|
1578 | 0 | uint32_t tile_w=0, tile_h=0; |
1579 | 0 | base_item->get_tile_size(tile_w, tile_h); |
1580 | |
|
1581 | 0 | uint32_t last_width=0, last_height=0; |
1582 | |
|
1583 | 0 | for (const auto& entry : pymd_entries) { |
1584 | 0 | auto layer_item = entry.item; |
1585 | |
|
1586 | 0 | if (false) { |
1587 | | // according to pymd definition, we should check that all layers have the same tile size |
1588 | 0 | uint32_t item_tile_w = 0, item_tile_h = 0; |
1589 | 0 | base_item->get_tile_size(item_tile_w, item_tile_h); |
1590 | 0 | if (item_tile_w != tile_w || item_tile_h != tile_h) { |
1591 | | // TODO: add warning that tile sizes are not the same |
1592 | 0 | } |
1593 | 0 | } |
1594 | |
|
1595 | 0 | heif_image_tiling tiling = layer_item->get_heif_image_tiling(); |
1596 | |
|
1597 | 0 | if (tiling.image_width < last_width || tiling.image_height < last_height) { |
1598 | 0 | return Error{ |
1599 | 0 | heif_error_Invalid_input, |
1600 | 0 | heif_suberror_Invalid_parameter_value, |
1601 | 0 | "Multi-resolution pyramid images have to be provided ordered from smallest to largest." |
1602 | 0 | }; |
1603 | 0 | } |
1604 | | |
1605 | 0 | last_width = tiling.image_width; |
1606 | 0 | last_height = tiling.image_height; |
1607 | |
|
1608 | 0 | Box_pymd::LayerInfo layer{}; |
1609 | 0 | layer.layer_binning = (uint16_t)(base_item->get_width() / tiling.image_width); |
1610 | 0 | layer.tiles_in_layer_row_minus1 = static_cast<uint16_t>(tiling.num_rows - 1); |
1611 | 0 | layer.tiles_in_layer_column_minus1 = static_cast<uint16_t>(tiling.num_columns - 1); |
1612 | 0 | layers.push_back(layer); |
1613 | 0 | ids.push_back(layer_item->get_id()); |
1614 | 0 | } |
1615 | | |
1616 | 0 | heif_item_id group_id = m_heif_file->get_unused_item_id(); |
1617 | |
|
1618 | 0 | pymd->set_group_id(group_id); |
1619 | 0 | pymd->set_layers((uint16_t)tile_w, (uint16_t)tile_h, layers, ids); |
1620 | |
|
1621 | 0 | m_heif_file->add_entity_group_box(pymd); |
1622 | | |
1623 | | // add back-references to base image |
1624 | |
|
1625 | 0 | for (size_t i = 0; i < ids.size() - 1; i++) { |
1626 | 0 | m_heif_file->add_iref_reference(ids[i], fourcc("base"), {ids.back()}); |
1627 | 0 | } |
1628 | |
|
1629 | 0 | return {group_id}; |
1630 | 0 | } |