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