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