/src/libheif/libheif/image-items/grid.cc
Line | Count | Source |
1 | | /* |
2 | | * HEIF 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 "grid.h" |
22 | | #include "context.h" |
23 | | #include "file.h" |
24 | | #include <cstring> |
25 | | #include <deque> |
26 | | #include <future> |
27 | | #include <mutex> |
28 | | #include <set> |
29 | | #include <algorithm> |
30 | | #include <utility> |
31 | | #include "api_structs.h" |
32 | | #include "security_limits.h" |
33 | | |
34 | | |
35 | | Error ImageGrid::parse(const std::vector<uint8_t>& data) |
36 | 1.58k | { |
37 | 1.58k | if (data.size() < 8) { |
38 | 20 | return {heif_error_Invalid_input, |
39 | 20 | heif_suberror_Invalid_grid_data, |
40 | 20 | "Less than 8 bytes of data"}; |
41 | 20 | } |
42 | | |
43 | 1.56k | uint8_t version = data[0]; |
44 | 1.56k | if (version != 0) { |
45 | 74 | std::stringstream sstr; |
46 | 74 | sstr << "Grid image version " << ((int) version) << " is not supported"; |
47 | 74 | return {heif_error_Unsupported_feature, |
48 | 74 | heif_suberror_Unsupported_data_version, |
49 | 74 | sstr.str()}; |
50 | 74 | } |
51 | | |
52 | 1.49k | uint8_t flags = data[1]; |
53 | 1.49k | int field_size = ((flags & 1) ? 32 : 16); |
54 | | |
55 | 1.49k | m_rows = static_cast<uint16_t>(data[2] + 1); |
56 | 1.49k | m_columns = static_cast<uint16_t>(data[3] + 1); |
57 | | |
58 | 1.49k | if (field_size == 32) { |
59 | 45 | if (data.size() < 12) { |
60 | 23 | return {heif_error_Invalid_input, |
61 | 23 | heif_suberror_Invalid_grid_data, |
62 | 23 | "Grid image data incomplete"}; |
63 | 23 | } |
64 | | |
65 | 22 | m_output_width = four_bytes_to_uint32(data[4], data[5], data[6], data[7]); |
66 | 22 | m_output_height = four_bytes_to_uint32(data[8], data[9], data[10], data[11]); |
67 | 22 | } |
68 | 1.44k | else { |
69 | 1.44k | m_output_width = two_bytes_to_uint16(data[4], data[5]); |
70 | 1.44k | m_output_height = two_bytes_to_uint16(data[6], data[7]); |
71 | 1.44k | } |
72 | | |
73 | 1.46k | return Error::Ok; |
74 | 1.49k | } |
75 | | |
76 | | |
77 | | std::vector<uint8_t> ImageGrid::write() const |
78 | 0 | { |
79 | 0 | int field_size; |
80 | |
|
81 | 0 | if (m_output_width > 0xFFFF || |
82 | 0 | m_output_height > 0xFFFF) { |
83 | 0 | field_size = 32; |
84 | 0 | } |
85 | 0 | else { |
86 | 0 | field_size = 16; |
87 | 0 | } |
88 | |
|
89 | 0 | std::vector<uint8_t> data(field_size == 16 ? 8 : 12); |
90 | |
|
91 | 0 | data[0] = 0; // version |
92 | |
|
93 | 0 | uint8_t flags = 0; |
94 | 0 | if (field_size == 32) { |
95 | 0 | flags |= 1; |
96 | 0 | } |
97 | |
|
98 | 0 | data[1] = flags; |
99 | 0 | data[2] = (uint8_t) (m_rows - 1); |
100 | 0 | data[3] = (uint8_t) (m_columns - 1); |
101 | |
|
102 | 0 | if (field_size == 32) { |
103 | 0 | data[4] = (uint8_t) ((m_output_width >> 24) & 0xFF); |
104 | 0 | data[5] = (uint8_t) ((m_output_width >> 16) & 0xFF); |
105 | 0 | data[6] = (uint8_t) ((m_output_width >> 8) & 0xFF); |
106 | 0 | data[7] = (uint8_t) ((m_output_width) & 0xFF); |
107 | |
|
108 | 0 | data[8] = (uint8_t) ((m_output_height >> 24) & 0xFF); |
109 | 0 | data[9] = (uint8_t) ((m_output_height >> 16) & 0xFF); |
110 | 0 | data[10] = (uint8_t) ((m_output_height >> 8) & 0xFF); |
111 | 0 | data[11] = (uint8_t) ((m_output_height) & 0xFF); |
112 | 0 | } |
113 | 0 | else { |
114 | 0 | data[4] = (uint8_t) ((m_output_width >> 8) & 0xFF); |
115 | 0 | data[5] = (uint8_t) ((m_output_width) & 0xFF); |
116 | |
|
117 | 0 | data[6] = (uint8_t) ((m_output_height >> 8) & 0xFF); |
118 | 0 | data[7] = (uint8_t) ((m_output_height) & 0xFF); |
119 | 0 | } |
120 | |
|
121 | 0 | return data; |
122 | 0 | } |
123 | | |
124 | | |
125 | | std::string ImageGrid::dump() const |
126 | 0 | { |
127 | 0 | std::ostringstream sstr; |
128 | |
|
129 | 0 | sstr << "rows: " << m_rows << "\n" |
130 | 0 | << "columns: " << m_columns << "\n" |
131 | 0 | << "output width: " << m_output_width << "\n" |
132 | 0 | << "output height: " << m_output_height << "\n"; |
133 | |
|
134 | 0 | return sstr.str(); |
135 | 0 | } |
136 | | |
137 | | |
138 | | ImageItem_Grid::ImageItem_Grid(HeifContext* ctx) |
139 | 0 | : ImageItem(ctx) |
140 | 0 | { |
141 | 0 | m_tile_encoding_options = heif_encoding_options_alloc(); |
142 | 0 | } |
143 | | |
144 | | |
145 | | ImageItem_Grid::ImageItem_Grid(HeifContext* ctx, heif_item_id id) |
146 | 2.42k | : ImageItem(ctx, id) |
147 | 2.42k | { |
148 | 2.42k | m_tile_encoding_options = heif_encoding_options_alloc(); |
149 | 2.42k | } |
150 | | |
151 | | |
152 | | ImageItem_Grid::~ImageItem_Grid() |
153 | 2.42k | { |
154 | 2.42k | heif_encoding_options_free(m_tile_encoding_options); |
155 | 2.42k | } |
156 | | |
157 | | |
158 | | Error ImageItem_Grid::initialize_decoder() |
159 | 2.19k | { |
160 | 2.19k | Error err = read_grid_spec(); |
161 | 2.19k | if (err) { |
162 | 868 | return err; |
163 | 868 | } |
164 | | |
165 | 1.32k | return Error::Ok; |
166 | 2.19k | } |
167 | | |
168 | | |
169 | | Error ImageItem_Grid::read_grid_spec() |
170 | 2.19k | { |
171 | 2.19k | auto heif_file = get_context()->get_heif_file(); |
172 | | |
173 | 2.19k | auto gridDataResult = heif_file->get_uncompressed_item_data(get_id()); |
174 | 2.19k | if (!gridDataResult) { |
175 | 606 | return gridDataResult.error(); |
176 | 606 | } |
177 | | |
178 | 1.58k | Error err = m_grid_spec.parse(*gridDataResult); |
179 | 1.58k | if (err) { |
180 | 117 | return err; |
181 | 117 | } |
182 | | |
183 | | //std::cout << grid.dump(); |
184 | | |
185 | | |
186 | 1.46k | auto iref_box = heif_file->get_iref_box(); |
187 | | |
188 | 1.46k | if (!iref_box) { |
189 | 31 | return {heif_error_Invalid_input, |
190 | 31 | heif_suberror_No_iref_box, |
191 | 31 | "No iref box available, but needed for grid image"}; |
192 | 31 | } |
193 | | |
194 | 1.43k | m_grid_tile_ids = iref_box->get_references(get_id(), fourcc("dimg")); |
195 | | |
196 | 1.43k | if ((int) m_grid_tile_ids.size() != m_grid_spec.get_rows() * m_grid_spec.get_columns()) { |
197 | 114 | std::stringstream sstr; |
198 | 114 | sstr << "Tiled image with " << m_grid_spec.get_rows() << "x" << m_grid_spec.get_columns() << "=" |
199 | 114 | << (m_grid_spec.get_rows() * m_grid_spec.get_columns()) << " tiles, but only " |
200 | 114 | << m_grid_tile_ids.size() << " tile images in file"; |
201 | | |
202 | 114 | return {heif_error_Invalid_input, |
203 | 114 | heif_suberror_Missing_grid_images, |
204 | 114 | sstr.str()}; |
205 | 114 | } |
206 | | |
207 | 1.32k | return Error::Ok; |
208 | 1.43k | } |
209 | | |
210 | | |
211 | | Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_compressed_image(const heif_decoding_options& options, |
212 | | bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0, |
213 | | DecodeTraversalState decode_state) const |
214 | 37.2k | { |
215 | 37.2k | if (decode_state.processed_ids.contains(get_id())) { |
216 | 5 | return Error{heif_error_Invalid_input, |
217 | 5 | heif_suberror_Unspecified, |
218 | 5 | "'iref' has cyclic references"}; |
219 | 5 | } |
220 | | |
221 | 37.2k | decode_state.processed_ids.insert(get_id()); |
222 | | |
223 | | |
224 | 37.2k | if (decode_tile_only) { |
225 | 36.1k | return decode_grid_tile(options, tile_x0, tile_y0, decode_state); |
226 | 36.1k | } |
227 | 1.11k | else { |
228 | 1.11k | return decode_full_grid_image(options, decode_state); |
229 | 1.11k | } |
230 | 37.2k | } |
231 | | |
232 | | // Note: ImageItem_Grid does not override check_decoded_image_size(). The composed |
233 | | // grid image is built to the grid-header size by construction (decode_and_paste_tile_image |
234 | | // creates the canvas at get_grid_spec() size), so checking it against that same size |
235 | | // would be tautological. The base default checks the composed image against 'ispe', |
236 | | // which is the meaningful cross-check (grid-header size vs signaled size). |
237 | | |
238 | | #if ENABLE_PARALLEL_TILE_DECODING |
239 | 62 | static void wait_for_jobs(std::deque<std::future<Error> >* jobs) { |
240 | 62 | if (jobs->empty()) { |
241 | 0 | return; |
242 | 0 | } |
243 | | |
244 | 248 | while (!jobs->empty()) { |
245 | 186 | jobs->front().get(); |
246 | 186 | jobs->pop_front(); |
247 | 186 | } |
248 | 62 | } |
249 | | #endif |
250 | | |
251 | | Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(const heif_decoding_options& options, const DecodeTraversalState& decode_state) const |
252 | 1.11k | { |
253 | 1.11k | std::shared_ptr<HeifPixelImage> img; // the decoded image |
254 | | |
255 | 1.11k | const ImageGrid& grid = get_grid_spec(); |
256 | | |
257 | | |
258 | | // --- check that all image IDs are valid images |
259 | | |
260 | 1.11k | const std::vector<heif_item_id>& image_references = get_grid_tiles(); |
261 | | |
262 | 47.8k | for (heif_item_id tile_id : image_references) { |
263 | 47.8k | if (!get_context()->is_image(tile_id)) { |
264 | 85 | std::stringstream sstr; |
265 | 85 | sstr << "Tile image ID=" << tile_id << " is not a proper image."; |
266 | | |
267 | 85 | return Error(heif_error_Invalid_input, |
268 | 85 | heif_suberror_Missing_grid_images, |
269 | 85 | sstr.str()); |
270 | 85 | } |
271 | 47.8k | } |
272 | | |
273 | | //auto pixi = get_file()->get_property<Box_pixi>(get_id()); |
274 | | |
275 | 1.03k | const uint32_t w = grid.get_width(); |
276 | 1.03k | const uint32_t h = grid.get_height(); |
277 | | |
278 | 1.03k | Error err = check_for_valid_image_size(get_context()->get_security_limits(), w, h); |
279 | 1.03k | if (err) { |
280 | 12 | return err; |
281 | 12 | } |
282 | | |
283 | 1.02k | uint32_t y0 = 0; |
284 | 1.02k | int reference_idx = 0; |
285 | | |
286 | 1.02k | #if ENABLE_PARALLEL_TILE_DECODING |
287 | | // remember which tile to put where into the image |
288 | 1.02k | struct tile_data |
289 | 1.02k | { |
290 | 1.02k | heif_item_id tileID; |
291 | 1.02k | uint32_t x_origin, y_origin; |
292 | 1.02k | }; |
293 | | |
294 | 1.02k | std::deque<tile_data> tiles; |
295 | 1.02k | if (get_context()->get_max_decoding_threads() > 0) |
296 | 1.02k | tiles.resize(static_cast<size_t>(grid.get_rows()) * static_cast<size_t>(grid.get_columns())); |
297 | | |
298 | 1.02k | std::deque<std::future<Error> > errs; |
299 | 1.02k | #endif |
300 | | |
301 | 1.02k | uint32_t tile_width = 0; |
302 | 1.02k | uint32_t tile_height = 0; |
303 | | |
304 | 1.02k | if (options.start_progress) { |
305 | 0 | options.start_progress(heif_progress_step_total, grid.get_rows() * grid.get_columns(), options.progress_user_data); |
306 | 0 | } |
307 | 1.02k | if (options.on_progress) { |
308 | 0 | options.on_progress(heif_progress_step_total, 0, options.progress_user_data); |
309 | 0 | } |
310 | | |
311 | 1.02k | int progress_counter = 0; |
312 | 1.02k | bool cancelled = false; |
313 | 1.02k | std::shared_ptr<std::vector<Error> > warnings(new std::vector<Error>()); |
314 | | |
315 | 6.81k | for (uint32_t y = 0; y < grid.get_rows() && !cancelled; y++) { |
316 | 5.84k | uint32_t x0 = 0; |
317 | | |
318 | 52.0k | for (uint32_t x = 0; x < grid.get_columns() && !cancelled; x++) { |
319 | | |
320 | 46.2k | heif_item_id tileID = image_references[reference_idx]; |
321 | | |
322 | 46.2k | std::shared_ptr<const ImageItem> tileImg = get_context()->get_image(tileID, true); |
323 | 46.2k | if (!tileImg) { |
324 | 0 | if (!options.strict_decoding && reference_idx != 0) { |
325 | | // Skip missing tiles (unless it's the first one). |
326 | 0 | warnings->push_back(Error{ |
327 | 0 | heif_error_Invalid_input, |
328 | 0 | heif_suberror_Missing_grid_images, |
329 | 0 | }); |
330 | 0 | reference_idx++; |
331 | 0 | x0 += tile_width; |
332 | 0 | continue; |
333 | 0 | } |
334 | | |
335 | 0 | return Error{heif_error_Invalid_input, |
336 | 0 | heif_suberror_Missing_grid_images, |
337 | 0 | "Nonexistent grid image referenced"}; |
338 | 0 | } |
339 | 46.2k | if (auto error = tileImg->get_item_error()) { |
340 | 18.8k | if (!options.strict_decoding && reference_idx != 0) { |
341 | | // Skip missing tiles (unless it's the first one). |
342 | 18.8k | warnings->push_back(error); |
343 | 18.8k | reference_idx++; |
344 | 18.8k | x0 += tile_width; |
345 | 18.8k | continue; |
346 | 18.8k | } |
347 | | |
348 | 18 | return error; |
349 | 18.8k | } |
350 | | |
351 | 27.3k | uint32_t src_width = tileImg->get_width(); |
352 | 27.3k | uint32_t src_height = tileImg->get_height(); |
353 | 27.3k | err = check_for_valid_image_size(get_context()->get_security_limits(), src_width, src_height); |
354 | 27.3k | if (err) { |
355 | 21 | return err; |
356 | 21 | } |
357 | | |
358 | | // Integer division would let e.g. 9 tiles of 11px each "cover" a 107px canvas |
359 | | // (107/9 == 11), leaving an 8-pixel gap inside the visible image area. |
360 | 27.3k | if (static_cast<uint64_t>(src_width) * grid.get_columns() < grid.get_width() || |
361 | 27.3k | static_cast<uint64_t>(src_height) * grid.get_rows() < grid.get_height()) { |
362 | 9 | return Error{heif_error_Invalid_input, |
363 | 9 | heif_suberror_Invalid_grid_data, |
364 | 9 | "Grid tiles do not cover whole image"}; |
365 | 9 | } |
366 | | |
367 | 27.3k | if (x == 0 && y == 0) { |
368 | | // remember size of first tile and compare all other tiles against this |
369 | 978 | tile_width = src_width; |
370 | 978 | tile_height = src_height; |
371 | 978 | } |
372 | 26.3k | else if (src_width != tile_width || src_height != tile_height) { |
373 | 3 | return Error{heif_error_Invalid_input, |
374 | 3 | heif_suberror_Invalid_grid_data, |
375 | 3 | "Grid tiles have different sizes"}; |
376 | 3 | } |
377 | | |
378 | 27.3k | #if ENABLE_PARALLEL_TILE_DECODING |
379 | 27.3k | if (get_context()->get_max_decoding_threads() > 0) |
380 | 27.3k | tiles[x + y * grid.get_columns()] = tile_data{tileID, x0, y0}; |
381 | 0 | else |
382 | | #else |
383 | | if (1) |
384 | | #endif |
385 | 0 | { |
386 | 0 | if (options.cancel_decoding) { |
387 | 0 | if (options.cancel_decoding(options.progress_user_data)) { |
388 | 0 | cancelled = true; |
389 | 0 | } |
390 | 0 | } |
391 | |
|
392 | 0 | err = decode_and_paste_tile_image(tileID, x0, y0, img, options, progress_counter, warnings, decode_state); |
393 | 0 | if (err) { |
394 | 0 | return err; |
395 | 0 | } |
396 | 0 | } |
397 | | |
398 | 27.3k | x0 += src_width; |
399 | | |
400 | 27.3k | reference_idx++; |
401 | 27.3k | } |
402 | | |
403 | 5.79k | y0 += tile_height; |
404 | 5.79k | } |
405 | | |
406 | 969 | #if ENABLE_PARALLEL_TILE_DECODING |
407 | 969 | if (get_context()->get_max_decoding_threads() > 0) { |
408 | | // Process all tiles in a set of background threads. |
409 | | // Do not start more than the maximum number of threads. |
410 | | |
411 | 44.7k | while (!tiles.empty() && !cancelled) { |
412 | | |
413 | | // If maximum number of threads running, wait until first thread finishes |
414 | | |
415 | 43.8k | if (errs.size() >= (size_t) get_context()->get_max_decoding_threads()) { |
416 | 39.9k | Error e = errs.front().get(); |
417 | 39.9k | errs.pop_front(); |
418 | 39.9k | if (e) { |
419 | 62 | wait_for_jobs(&errs); |
420 | 62 | return e; |
421 | 62 | } |
422 | 39.9k | } |
423 | | |
424 | | |
425 | 43.7k | if (options.cancel_decoding) { |
426 | 0 | if (options.cancel_decoding(options.progress_user_data)) { |
427 | 0 | cancelled = true; |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | | |
432 | | // Start a new decoding thread |
433 | | |
434 | 43.7k | tile_data data = tiles.front(); |
435 | 43.7k | tiles.pop_front(); |
436 | | |
437 | 43.7k | errs.push_back(std::async(std::launch::async, |
438 | 43.7k | &ImageItem_Grid::decode_and_paste_tile_image, this, |
439 | 43.7k | data.tileID, data.x_origin, data.y_origin, std::ref(img), options, |
440 | 43.7k | std::ref(progress_counter), warnings, decode_state)); |
441 | 43.7k | } |
442 | | |
443 | | // check for decoding errors in remaining tiles |
444 | | |
445 | 4.51k | while (!errs.empty()) { |
446 | 3.60k | Error e = errs.front().get(); |
447 | 3.60k | errs.pop_front(); |
448 | 3.60k | if (e) { |
449 | 0 | wait_for_jobs(&errs); |
450 | 0 | return e; |
451 | 0 | } |
452 | 3.60k | } |
453 | 907 | } |
454 | 907 | #endif |
455 | | |
456 | 907 | if (options.end_progress) { |
457 | 0 | options.end_progress(heif_progress_step_total, options.progress_user_data); |
458 | 0 | } |
459 | | |
460 | 907 | if (cancelled) { |
461 | 0 | return Error{heif_error_Canceled, heif_suberror_Unspecified, "Decoding the image was canceled"}; |
462 | 0 | } |
463 | | |
464 | 907 | if (!img) { |
465 | | // No tile could be decoded and the output canvas was never created. |
466 | | // Returning the null image would only produce a meaningless generic error |
467 | | // further up. Return the first per-tile error instead, which names the |
468 | | // actual cause, for example that no decoder plugin is installed (#1876). |
469 | 436 | if (!warnings->empty()) { |
470 | 436 | return warnings->front(); |
471 | 436 | } |
472 | | |
473 | 0 | return Error{heif_error_Invalid_input, |
474 | 0 | heif_suberror_Invalid_grid_data, |
475 | 0 | "Grid image without tiles"}; |
476 | 436 | } |
477 | | |
478 | 471 | img->add_warnings(*warnings.get()); |
479 | | |
480 | 471 | return img; |
481 | 907 | } |
482 | | |
483 | 43.6k | static Error progress_and_return_ok(const heif_decoding_options& options, int& progress_counter) { |
484 | 43.6k | if (options.on_progress) { |
485 | 0 | #if ENABLE_PARALLEL_TILE_DECODING |
486 | 0 | static std::mutex progressMutex; |
487 | 0 | std::lock_guard<std::mutex> lock(progressMutex); |
488 | 0 | #endif |
489 | |
|
490 | 0 | options.on_progress(heif_progress_step_total, ++progress_counter, options.progress_user_data); |
491 | 0 | } |
492 | 43.6k | return Error::Ok; |
493 | 43.6k | } |
494 | | |
495 | | Error ImageItem_Grid::decode_and_paste_tile_image(heif_item_id tileID, uint32_t x0, uint32_t y0, |
496 | | std::shared_ptr<HeifPixelImage>& inout_image, |
497 | | const heif_decoding_options& options, |
498 | | int& progress_counter, |
499 | | const std::shared_ptr<std::vector<Error> >& warnings, |
500 | | DecodeTraversalState decode_state) const |
501 | 43.7k | { |
502 | 43.7k | std::shared_ptr<HeifPixelImage> tile_img; |
503 | 43.7k | #if ENABLE_PARALLEL_TILE_DECODING |
504 | 43.7k | static std::mutex warningsMutex; |
505 | 43.7k | #endif |
506 | | |
507 | 43.7k | auto tileItem = get_context()->get_image(tileID, true); |
508 | 43.7k | if (!tileItem && !options.strict_decoding) { |
509 | | // We ignore missing images. The un-pasted canvas region stays zero from calloc(). |
510 | 17.2k | #if ENABLE_PARALLEL_TILE_DECODING |
511 | 17.2k | std::lock_guard<std::mutex> lock(warningsMutex); |
512 | 17.2k | #endif |
513 | 17.2k | warnings->emplace_back( |
514 | 17.2k | heif_error_Invalid_input, |
515 | 17.2k | heif_suberror_Missing_grid_images, |
516 | 17.2k | "Missing grid image" |
517 | 17.2k | ); |
518 | 17.2k | return progress_and_return_ok(options, progress_counter); |
519 | 17.2k | } |
520 | | |
521 | 43.7k | assert(tileItem); |
522 | 26.5k | if (auto error = tileItem->get_item_error()) { |
523 | 118 | return error; |
524 | 118 | } |
525 | | |
526 | 26.4k | auto decodeResult = tileItem->decode_image(options, false, 0, 0, std::move(decode_state)); |
527 | 26.4k | if (!decodeResult) { |
528 | 24.8k | if (!options.strict_decoding) { |
529 | | // We ignore broken tiles. The un-pasted canvas region stays zero from calloc(). |
530 | 24.8k | #if ENABLE_PARALLEL_TILE_DECODING |
531 | 24.8k | std::lock_guard<std::mutex> lock(warningsMutex); |
532 | 24.8k | #endif |
533 | 24.8k | warnings->push_back(decodeResult.error()); |
534 | 24.8k | return progress_and_return_ok(options, progress_counter); |
535 | 24.8k | } |
536 | | |
537 | 18.4E | return decodeResult.error(); |
538 | 24.8k | } |
539 | | |
540 | 1.54k | tile_img = *decodeResult; |
541 | | |
542 | 1.54k | uint32_t w = get_grid_spec().get_width(); |
543 | 1.54k | uint32_t h = get_grid_spec().get_height(); |
544 | | |
545 | | // --- generate the image canvas for combining all the tiles |
546 | | |
547 | 1.54k | { |
548 | 1.54k | #if ENABLE_PARALLEL_TILE_DECODING |
549 | | // All threads have to take this mutex, even those that will only read `inout_image`. |
550 | | // Otherwise, the image initialization would not be synchronized to them (they could, |
551 | | // for example, see the image pointer before the image content initialization is visible). |
552 | 1.54k | static std::mutex createImageMutex; |
553 | 1.54k | std::lock_guard<std::mutex> lock(createImageMutex); |
554 | 1.54k | #endif |
555 | | |
556 | 1.54k | if (!inout_image) { |
557 | 493 | auto grid_image = std::make_shared<HeifPixelImage>(); |
558 | 493 | auto err = grid_image->create_clone_image_at_new_size(tile_img, w, h, get_context()->get_security_limits()); |
559 | 493 | if (err) { |
560 | 0 | return err; |
561 | 0 | } |
562 | | |
563 | | // Fill alpha plane with opaque in case not all tiles have alpha planes |
564 | | |
565 | 493 | if (grid_image->has_channel(heif_channel_Alpha)) { |
566 | 0 | uint16_t alpha_bpp = grid_image->get_bits_per_pixel(heif_channel_Alpha); |
567 | 0 | assert(alpha_bpp <= 16); |
568 | | |
569 | 0 | auto alpha_default_value = static_cast<uint16_t>((1UL << alpha_bpp) - 1UL); |
570 | 0 | grid_image->fill_channel(heif_channel_Alpha, alpha_default_value); |
571 | 0 | } |
572 | | |
573 | 493 | grid_image->copy_metadata_from(*tile_img); |
574 | | |
575 | 493 | inout_image = grid_image; |
576 | 493 | } |
577 | 1.54k | } |
578 | | |
579 | | // --- copy tile into output image |
580 | | |
581 | 1.54k | heif_chroma chroma = inout_image->get_chroma_format(); |
582 | | |
583 | 1.54k | if (chroma != tile_img->get_chroma_format()) { |
584 | 0 | return {heif_error_Invalid_input, |
585 | 0 | heif_suberror_Wrong_tile_image_chroma_format, |
586 | 0 | "Image tile has different chroma format than combined image"}; |
587 | 0 | } |
588 | | |
589 | | |
590 | 1.54k | inout_image->copy_image_to(tile_img, x0, y0); |
591 | | |
592 | 1.54k | return progress_and_return_ok(options, progress_counter); |
593 | 1.54k | } |
594 | | |
595 | | |
596 | | Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty, |
597 | | DecodeTraversalState decode_state) const |
598 | 36.1k | { |
599 | 36.1k | uint32_t idx = ty * m_grid_spec.get_columns() + tx; |
600 | | |
601 | 36.1k | if (idx >= m_grid_tile_ids.size()) { |
602 | 0 | return Error{heif_error_Invalid_input, |
603 | 0 | heif_suberror_Missing_grid_images, |
604 | 0 | "Grid tile coordinate out of range"}; |
605 | 0 | } |
606 | | |
607 | 36.1k | heif_item_id tile_id = m_grid_tile_ids[idx]; |
608 | 36.1k | std::shared_ptr<const ImageItem> tile_item = get_context()->get_image(tile_id, true); |
609 | 36.1k | if (!tile_item) { |
610 | 11.7k | return Error{heif_error_Invalid_input, |
611 | 11.7k | heif_suberror_Missing_grid_images, |
612 | 11.7k | "Grid tile references a non-existent item"}; |
613 | 11.7k | } |
614 | 24.4k | if (auto error = tile_item->get_item_error()) { |
615 | 3.23k | return error; |
616 | 3.23k | } |
617 | | |
618 | 21.1k | return tile_item->decode_compressed_image(options, false, 0, 0, std::move(decode_state)); |
619 | 24.4k | } |
620 | | |
621 | | |
622 | | void ImageItem_Grid::set_grid_tile_id(uint32_t tile_x, uint32_t tile_y, heif_item_id id) |
623 | 0 | { |
624 | 0 | uint32_t idx = tile_y * m_grid_spec.get_columns() + tile_x; |
625 | 0 | m_grid_tile_ids[idx] = id; |
626 | 0 | } |
627 | | |
628 | | |
629 | | heif_image_tiling ImageItem_Grid::get_heif_image_tiling() const |
630 | 62.2k | { |
631 | 62.2k | heif_image_tiling tiling{}; |
632 | | |
633 | 62.2k | const ImageGrid& gridspec = get_grid_spec(); |
634 | 62.2k | tiling.num_columns = gridspec.get_columns(); |
635 | 62.2k | tiling.num_rows = gridspec.get_rows(); |
636 | | |
637 | 62.2k | tiling.image_width = gridspec.get_width(); |
638 | 62.2k | tiling.image_height = gridspec.get_height(); |
639 | 62.2k | tiling.number_of_extra_dimensions = 0; |
640 | | |
641 | 62.2k | auto tile_ids = get_grid_tiles(); |
642 | 62.2k | if (!tile_ids.empty() && tile_ids[0] != 0) { |
643 | 61.7k | heif_item_id tile0_id = tile_ids[0]; |
644 | 61.7k | auto tile0 = get_context()->get_image(tile0_id, true); |
645 | 61.7k | if (tile0 == nullptr || tile0->get_item_error()) { |
646 | 46.5k | return tiling; |
647 | 46.5k | } |
648 | | |
649 | 15.1k | tiling.tile_width = tile0->get_width(); |
650 | 15.1k | tiling.tile_height = tile0->get_height(); |
651 | 15.1k | } |
652 | 497 | else { |
653 | 497 | tiling.tile_width = 0; |
654 | 497 | tiling.tile_height = 0; |
655 | 497 | } |
656 | | |
657 | 15.6k | return tiling; |
658 | 62.2k | } |
659 | | |
660 | | |
661 | | void ImageItem_Grid::get_tile_size(uint32_t& w, uint32_t& h) const |
662 | 70 | { |
663 | 70 | const auto& tile_ids = get_grid_tiles(); |
664 | 70 | if (tile_ids.empty() || tile_ids[0] == 0) { |
665 | 13 | w = h = 0; |
666 | 13 | return; |
667 | 13 | } |
668 | | |
669 | 57 | auto tile = get_context()->get_image(tile_ids[0], true); |
670 | 57 | if (tile == nullptr || tile->get_item_error()) { |
671 | 14 | w = h = 0; |
672 | 14 | return; |
673 | 14 | } |
674 | | |
675 | 43 | w = tile->get_width(); |
676 | 43 | h = tile->get_height(); |
677 | 43 | } |
678 | | |
679 | | |
680 | | |
681 | | int ImageItem_Grid::get_luma_bits_per_pixel() const |
682 | 786 | { |
683 | 786 | auto child_result = get_context()->find_first_coded_image_id(get_id()); |
684 | 786 | if (child_result.is_error()) { |
685 | 9 | return -1; |
686 | 9 | } |
687 | | |
688 | 777 | auto image = get_context()->get_image(*child_result, true); |
689 | 777 | if (!image) { |
690 | 0 | return -1; |
691 | 0 | } |
692 | | |
693 | 777 | return image->get_luma_bits_per_pixel(); |
694 | 777 | } |
695 | | |
696 | | |
697 | | int ImageItem_Grid::get_chroma_bits_per_pixel() const |
698 | 754 | { |
699 | 754 | auto child_result = get_context()->find_first_coded_image_id(get_id()); |
700 | 754 | if (child_result.is_error()) { |
701 | 0 | return -1; |
702 | 0 | } |
703 | | |
704 | 754 | auto image = get_context()->get_image(*child_result, true); |
705 | 754 | return image->get_chroma_bits_per_pixel(); |
706 | 754 | } |
707 | | |
708 | | Result<std::shared_ptr<Decoder>> ImageItem_Grid::get_decoder() const |
709 | 3.71k | { |
710 | 3.71k | auto child_result = get_context()->find_first_coded_image_id(get_id()); |
711 | 3.71k | if (child_result.is_error()) { |
712 | 1.77k | return child_result.error(); |
713 | 1.77k | } |
714 | | |
715 | 1.93k | auto image = get_context()->get_image(*child_result, true); |
716 | 1.93k | if (!image) { |
717 | 0 | return Error{heif_error_Invalid_input, |
718 | 0 | heif_suberror_Nonexisting_item_referenced}; |
719 | 0 | } |
720 | 1.93k | else if (auto err = image->get_item_error()) { |
721 | 98 | return err; |
722 | 98 | } |
723 | | |
724 | 1.84k | return image->get_decoder(); |
725 | 1.93k | } |
726 | | |
727 | | |
728 | | void ImageItem_Grid::populate_component_descriptions() |
729 | 3.51k | { |
730 | 3.51k | if (!get_component_descriptions().empty()) { |
731 | 711 | return; |
732 | 711 | } |
733 | | |
734 | 2.80k | if (m_grid_tile_ids.empty()) { |
735 | 2.19k | ImageItem::populate_component_descriptions(); |
736 | 2.19k | return; |
737 | 2.19k | } |
738 | | |
739 | 613 | auto child = get_context()->get_image(m_grid_tile_ids[0], true); |
740 | 613 | if (!child) { |
741 | 496 | ImageItem::populate_component_descriptions(); |
742 | 496 | return; |
743 | 496 | } |
744 | | |
745 | | // Try child-delegation first (correct for unci children with float/signed/ |
746 | | // complex datatypes). If the child has no descriptions yet (e.g. it's a |
747 | | // visual codec without an initialized decoder), fall back to the base |
748 | | // populate which queries this item's own colorspace/bpp accessors (which |
749 | | // already delegate to the child). |
750 | 117 | if (!populate_descriptions_from_child(*child, child->get_width(), child->get_height())) { |
751 | 117 | ImageItem::populate_component_descriptions(); |
752 | 117 | } |
753 | 117 | } |
754 | | |
755 | | |
756 | | Result<std::shared_ptr<ImageItem_Grid>> ImageItem_Grid::add_new_grid_item(HeifContext* ctx, |
757 | | uint32_t output_width, |
758 | | uint32_t output_height, |
759 | | uint16_t tile_rows, |
760 | | uint16_t tile_columns, |
761 | | const heif_encoding_options* encoding_options) |
762 | 0 | { |
763 | 0 | std::shared_ptr<ImageItem_Grid> grid_image; |
764 | 0 | if (tile_rows > 0xFFFF / tile_columns) { |
765 | 0 | return Error{heif_error_Usage_error, |
766 | 0 | heif_suberror_Unspecified, |
767 | 0 | "Too many tiles (maximum: 65535)"}; |
768 | 0 | } |
769 | | |
770 | | // Create ImageGrid |
771 | | |
772 | 0 | ImageGrid grid; |
773 | 0 | grid.set_num_tiles(tile_columns, tile_rows); |
774 | 0 | grid.set_output_size(output_width, output_height); // TODO: MIAF restricts the output size to be a multiple of the chroma subsampling (7.3.11.4.2) |
775 | 0 | std::vector<uint8_t> grid_data = grid.write(); |
776 | | |
777 | | // Create Grid Item |
778 | |
|
779 | 0 | std::shared_ptr<HeifFile> file = ctx->get_heif_file(); |
780 | 0 | auto grid_id_result = file->add_new_image(fourcc("grid")); |
781 | 0 | if (!grid_id_result) { |
782 | 0 | return grid_id_result.error(); |
783 | 0 | } |
784 | 0 | heif_item_id grid_id = *grid_id_result; |
785 | 0 | grid_image = std::make_shared<ImageItem_Grid>(ctx, grid_id); |
786 | 0 | grid_image->set_tile_encoding_options(encoding_options); |
787 | 0 | grid_image->set_grid_spec(grid); |
788 | 0 | grid_image->set_resolution(output_width, output_height); |
789 | 0 | grid_image->m_grid_orientation = encoding_options->image_orientation; |
790 | |
|
791 | 0 | ctx->insert_image_item(grid_id, grid_image); |
792 | 0 | const int construction_method = 1; // 0=mdat 1=idat |
793 | 0 | file->append_iloc_data(grid_id, grid_data, construction_method); |
794 | | |
795 | | // generate dummy grid item IDs (0) |
796 | 0 | std::vector<heif_item_id> tile_ids; |
797 | 0 | tile_ids.resize(static_cast<size_t>(tile_rows) * static_cast<size_t>(tile_columns)); |
798 | | |
799 | | // Connect tiles to grid |
800 | 0 | file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids); |
801 | | |
802 | | // Add ISPE property |
803 | 0 | file->add_ispe_property(grid_id, output_width, output_height, false); |
804 | | |
805 | | // PIXI property will be added when the first tile is set |
806 | | |
807 | | // Set Brands |
808 | | //m_heif_file->set_brand(encoder->plugin->compression_format, |
809 | | // grid_image->is_miaf_compatible()); |
810 | |
|
811 | 0 | return grid_image; |
812 | 0 | } |
813 | | |
814 | | void ImageItem_Grid::set_tile_encoding_options(const heif_encoding_options* options) |
815 | 0 | { |
816 | 0 | heif_encoding_options_copy(m_tile_encoding_options, options); |
817 | | |
818 | | // do not propagate image transformation to tiles |
819 | 0 | m_tile_encoding_options->image_orientation = heif_orientation_normal; |
820 | 0 | } |
821 | | |
822 | | |
823 | | Error ImageItem_Grid::add_image_tile(uint32_t tile_x, uint32_t tile_y, |
824 | | const std::shared_ptr<HeifPixelImage>& image, |
825 | | heif_encoder* encoder) |
826 | 0 | { |
827 | 0 | auto encodingResult = get_context()->encode_image(image, |
828 | 0 | encoder, |
829 | 0 | *m_tile_encoding_options, |
830 | 0 | heif_image_input_class_normal); |
831 | 0 | if (!encodingResult) { |
832 | 0 | return encodingResult.error(); |
833 | 0 | } |
834 | | |
835 | 0 | std::shared_ptr<ImageItem> encoded_image = *encodingResult; |
836 | |
|
837 | 0 | auto file = get_file(); |
838 | 0 | file->get_infe_box(encoded_image->get_id())->set_hidden_item(true); // grid tiles are hidden items |
839 | | |
840 | | // Assign tile to grid |
841 | 0 | heif_image_tiling tiling = get_heif_image_tiling(); |
842 | 0 | file->set_iref_reference(get_id(), fourcc("dimg"), tile_y * tiling.num_columns + tile_x, encoded_image->get_id()); |
843 | |
|
844 | 0 | set_grid_tile_id(tile_x, tile_y, encoded_image->get_id()); |
845 | | |
846 | | // Add PIXI property (copy from first tile) |
847 | 0 | auto pixi = encoded_image->get_property<Box_pixi>(); |
848 | 0 | add_property(pixi, true); |
849 | | |
850 | | // copy over extra properties to grid item |
851 | |
|
852 | 0 | if (tile_x == 0 && tile_y == 0) { |
853 | 0 | auto property_boxes = encoded_image->generate_property_boxes(false); |
854 | 0 | for (auto& property : property_boxes) { |
855 | 0 | add_property(property, is_property_essential(property)); |
856 | 0 | } |
857 | | |
858 | | // add color profile similar to first tile image |
859 | | // TODO: this shouldn't be necessary. The colr profiles should be in the ImageDescription above. |
860 | 0 | auto colr_boxes = add_color_profile(image, *m_tile_encoding_options, |
861 | 0 | heif_image_input_class_normal, |
862 | 0 | m_tile_encoding_options->output_nclx_profile); |
863 | 0 | for (auto& property : colr_boxes) { |
864 | 0 | add_property(property, is_property_essential(property)); |
865 | 0 | } |
866 | | |
867 | | // Add transformative properties |
868 | |
|
869 | 0 | get_context()->get_heif_file()->add_orientation_properties(get_id(), m_grid_orientation); |
870 | 0 | } |
871 | |
|
872 | 0 | return Error::Ok; |
873 | 0 | } |
874 | | |
875 | | |
876 | | Result<std::shared_ptr<ImageItem_Grid>> ImageItem_Grid::add_and_encode_full_grid(HeifContext* ctx, |
877 | | const std::vector<std::shared_ptr<HeifPixelImage>>& tiles, |
878 | | uint16_t rows, |
879 | | uint16_t columns, |
880 | | heif_encoder* encoder, |
881 | | const heif_encoding_options& options) |
882 | 0 | { |
883 | 0 | std::shared_ptr<ImageItem_Grid> griditem; |
884 | | |
885 | | // Create ImageGrid |
886 | |
|
887 | 0 | ImageGrid grid; |
888 | 0 | grid.set_num_tiles(columns, rows); |
889 | 0 | uint32_t tile_width = tiles[0]->get_width(); |
890 | 0 | uint32_t tile_height = tiles[0]->get_height(); |
891 | 0 | grid.set_output_size(tile_width * columns, tile_height * rows); |
892 | 0 | std::vector<uint8_t> grid_data = grid.write(); |
893 | |
|
894 | 0 | auto file = ctx->get_heif_file(); |
895 | | |
896 | | // Encode Tiles |
897 | |
|
898 | 0 | std::vector<heif_item_id> tile_ids; |
899 | |
|
900 | 0 | std::shared_ptr<Box_pixi> pixi_property; |
901 | |
|
902 | 0 | for (int i=0; i<rows*columns; i++) { |
903 | 0 | std::shared_ptr<ImageItem> out_tile; |
904 | 0 | auto encodingResult = ctx->encode_image(tiles[i], |
905 | 0 | encoder, |
906 | 0 | options, |
907 | 0 | heif_image_input_class_normal); |
908 | 0 | if (!encodingResult) { |
909 | 0 | return encodingResult.error(); |
910 | 0 | } |
911 | 0 | else { |
912 | 0 | out_tile = *encodingResult; |
913 | 0 | } |
914 | | |
915 | 0 | heif_item_id tile_id = out_tile->get_id(); |
916 | 0 | file->get_infe_box(tile_id)->set_hidden_item(true); // only show the full grid |
917 | 0 | tile_ids.push_back(out_tile->get_id()); |
918 | |
|
919 | 0 | if (!pixi_property) { |
920 | 0 | pixi_property = out_tile->get_property<Box_pixi>(); |
921 | 0 | } |
922 | 0 | } |
923 | | |
924 | | // Create Grid Item |
925 | | |
926 | 0 | auto grid_id_result = file->add_new_image(fourcc("grid")); |
927 | 0 | if (!grid_id_result) { |
928 | 0 | return grid_id_result.error(); |
929 | 0 | } |
930 | 0 | heif_item_id grid_id = *grid_id_result; |
931 | 0 | griditem = std::make_shared<ImageItem_Grid>(ctx, grid_id); |
932 | 0 | ctx->insert_image_item(grid_id, griditem); |
933 | 0 | const int construction_method = 1; // 0=mdat 1=idat |
934 | 0 | file->append_iloc_data(grid_id, grid_data, construction_method); |
935 | | |
936 | | // Connect tiles to grid |
937 | |
|
938 | 0 | file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids); |
939 | | |
940 | | // Add ISPE property |
941 | |
|
942 | 0 | uint32_t image_width = tile_width * columns; |
943 | 0 | uint32_t image_height = tile_height * rows; |
944 | |
|
945 | 0 | auto ispe = std::make_shared<Box_ispe>(); |
946 | 0 | ispe->set_size(image_width, image_height); |
947 | 0 | griditem->add_property(ispe, false); |
948 | | |
949 | | // Add PIXI property (copy from first tile) |
950 | |
|
951 | 0 | griditem->add_property(pixi_property, true); |
952 | | |
953 | | // copy over extra properties to grid item |
954 | |
|
955 | 0 | auto property_boxes = tiles[0]->generate_property_boxes(true); |
956 | 0 | for (auto& property : property_boxes) { |
957 | 0 | griditem->add_property(property, griditem->is_property_essential(property)); |
958 | 0 | } |
959 | | |
960 | | // Set Brands |
961 | | |
962 | | //file->set_brand(encoder->plugin->compression_format, |
963 | | // griditem->is_miaf_compatible()); |
964 | |
|
965 | 0 | return griditem; |
966 | 0 | } |
967 | | |
968 | | heif_brand2 ImageItem_Grid::get_compatible_brand() const |
969 | 0 | { |
970 | 0 | if (m_grid_tile_ids.empty()) { return 0; } |
971 | | |
972 | 0 | heif_item_id child_id = m_grid_tile_ids[0]; |
973 | 0 | auto child = get_context()->get_image(child_id, false); |
974 | 0 | if (!child) { return 0; } |
975 | | |
976 | 0 | return child->get_compatible_brand(); |
977 | 0 | } |