/src/graphicsmagick/coders/heif.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2023-2026 GraphicsMagick Group |
3 | | % |
4 | | % This program is covered by multiple licenses, which are described in |
5 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
6 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
7 | | % |
8 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
9 | | % % |
10 | | % % |
11 | | % H H EEEEE I FFFFF % |
12 | | % H H E I F % |
13 | | % HHHHH EEEEE I FFFF % |
14 | | % H H E I F % |
15 | | % H H EEEEE I F % |
16 | | % % |
17 | | % Read HEIF/HEIC/AVIF image formats using libheif. % |
18 | | % % |
19 | | % Initial version contributed by Tobias Mark, 2021. % |
20 | | % Since developed by Bob Friesenhahn. % |
21 | | % % |
22 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
23 | | * Status: Support for reading a single image. |
24 | | * Ubuntu 22.04 LTS comes with 1.17.6 (Dec 20, 2023)! |
25 | | * Debian 12 Backports provides 1.19.7 (March 3, 2025) |
26 | | * Maybe limit support to version 1.17.6 (Dec 20, 2023) or later? |
27 | | * But 1.20.0 (July 1, 2025) introduces support for sequences! |
28 | | * Meanwhile, 1.14.0 is required to compile. |
29 | | * |
30 | | * Nokia technical publication on HEIF: https://nokiatech.github.io/heif/technical.html |
31 | | * |
32 | | * Yet to be done: |
33 | | * |
34 | | * o Read/render HEIF tiles directly. |
35 | | * o Possibly support reading directly from file (heif_context_read_from_file()) |
36 | | * rather than an in-memory BLOB. |
37 | | * o Support selecting to read HEIF depth image. |
38 | | * o Use HEIF transform properties to evaluate image orientation (requires 1.17). |
39 | | * o Provide user control over chroma upsampling options. |
40 | | * o Support reading sequences (requires 1.20). |
41 | | * |
42 | | * o Write HEIF (HEIC, AVIF, more?) |
43 | | * |
44 | | * Definitions influencing the reader: |
45 | | * |
46 | | * heif:ignore-transformations=yes/no (default no) |
47 | | * |
48 | | * Determines if libheif will automatically rotate the image if it is not |
49 | | * right-side up. |
50 | | * |
51 | | * heif:preserve-colorspace=yes/no (default no) |
52 | | * |
53 | | * If image is stored in YCbCr, then return original YCbCr, but without the |
54 | | * benefit of applying an associated HEIF color profile. |
55 | | * |
56 | | * heif:interleaved-rgb-decode=yes/no (default no) |
57 | | * |
58 | | * RGB decoding can be done using individual planes (e.g. R, G, B, A), or |
59 | | * in an interleaved RGB(A) format. This option controls libheif's image |
60 | | * decoding, but should not produce any change in the final result. |
61 | | */ |
62 | | |
63 | | #include "magick/studio.h" |
64 | | #include "magick/attribute.h" |
65 | | #include "magick/blob.h" |
66 | | #include "magick/colormap.h" |
67 | | #include "magick/log.h" |
68 | | #include "magick/constitute.h" |
69 | | #include "magick/magick.h" |
70 | | #include "magick/monitor.h" |
71 | | #include "magick/pixel_cache.h" |
72 | | #include "magick/profile.h" |
73 | | #include "magick/resource.h" |
74 | | #include "magick/utility.h" |
75 | | #include "magick/resource.h" |
76 | | #include "magick/static.h" |
77 | | |
78 | | #if defined(HasHEIF) |
79 | | |
80 | | /* Set to 1 to enable the currently non-functional progress monitor callbacks */ |
81 | | #define HEIF_ENABLE_PROGRESS_MONITOR 0 |
82 | | |
83 | | #if defined(HAVE_LIBHEIF_HEIF_SEQUENCES_H) |
84 | | /* Set to 1 to enable use of <libheif/heif_sequences.h> APIs */ |
85 | | #define HEIF_SUPPORT_SEQUENCES 1 |
86 | | #endif /* if defined(HAVE_LIBHEIF_HEIF_SEQUENCES_H) */ |
87 | | |
88 | | /* Set to 1 to enable RGB(A) decode in interleaved format */ |
89 | | #define HEIF_SUPPORT_INTERLEAVED 1 |
90 | | |
91 | | /* |
92 | | Libheif added LIBHEIF_MAKE_VERSION in 1.14.0 (October 31, 2021). |
93 | | Duplicate its definition so we can detect versions lower than |
94 | | 1.14.0. |
95 | | */ |
96 | | #if !defined(LIBHEIF_HAVE_VERSION) |
97 | | #define LIBHEIF_MAKE_VERSION(h, m, l) (((h) << 24 | (m) << 16 | (l) << 8) | 0) |
98 | | #define LIBHEIF_HAVE_VERSION(h, m, l) (LIBHEIF_NUMERIC_VERSION >= LIBHEIF_MAKE_VERSION(h, m, l)) |
99 | | #endif /* if !defined(LIBHEIF_HAVE_VERSION) */ |
100 | | |
101 | | #if LIBHEIF_HAVE_VERSION(1,14,0) |
102 | | # error "Libheif must be at least 1.14.0!" |
103 | | #endif |
104 | | |
105 | | /* FIXME: It would be nice to use MagickSizeStrToInt64(), but it does |
106 | | not provide error status */ |
107 | | #define MagickAttributeToU32(image_info, magick, name, out_val) \ |
108 | 972k | { \ |
109 | 972k | const char *string_value; \ |
110 | 972k | if ((string_value = AccessDefinition(image_info,magick,name))) \ |
111 | 972k | { \ |
112 | 0 | unsigned int ui_value; \ |
113 | 0 | if (MagickAtoUIChk(string_value, &ui_value) != MagickFail) \ |
114 | 0 | { \ |
115 | 0 | out_val = ui_value; \ |
116 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), \ |
117 | 0 | "security limits: %s = " \ |
118 | 0 | "%" MAGICK_UINTMAX_F "u", name, \ |
119 | 0 | (magick_uintmax_t) out_val); \ |
120 | 0 | } \ |
121 | 0 | } \ |
122 | 972k | } |
123 | | |
124 | | /* Rely on the compiler to remove unused code */ |
125 | | |
126 | | /* FIXME: It would be nice to use MagickSizeStrToInt64(), but it does |
127 | | not provide error status */ |
128 | | #define MagickAttributeToU64(image_info, magick, name, out_val) \ |
129 | 389k | { \ |
130 | 389k | const char *string_value; \ |
131 | 389k | if ((string_value = AccessDefinition(image_info,magick,name))) \ |
132 | 389k | { \ |
133 | 0 | if (sizeof(long) == 64) \ |
134 | 0 | { \ |
135 | 0 | unsigned long ul_value; \ |
136 | 0 | if (MagickAtoULChk(string_value, &ul_value) != MagickFail) \ |
137 | 0 | { \ |
138 | 0 | out_val = ul_value; \ |
139 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), \ |
140 | 0 | "security limits: %s = " \ |
141 | 0 | "%" MAGICK_UINTMAX_F "u", name, \ |
142 | 0 | (magick_uintmax_t) out_val); \ |
143 | 0 | } \ |
144 | 0 | } \ |
145 | 0 | else \ |
146 | 0 | { \ |
147 | 0 | unsigned long long ull_value; \ |
148 | 0 | if (MagickAtoULLChk(string_value, &ull_value) != MagickFail) \ |
149 | 0 | { \ |
150 | 0 | out_val = ull_value; \ |
151 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), \ |
152 | 0 | "security limits: %s = " \ |
153 | 0 | "%" MAGICK_UINTMAX_F "u", name, \ |
154 | 0 | (magick_uintmax_t) out_val); \ |
155 | 0 | } \ |
156 | 0 | } \ |
157 | 0 | } \ |
158 | 389k | } |
159 | | |
160 | | #include <libheif/heif.h> |
161 | | #if defined(HEIF_SUPPORT_SEQUENCES) && HEIF_SUPPORT_SEQUENCES |
162 | | #include <libheif/heif_sequences.h> |
163 | | #endif /* if defined(HEIF_SUPPORT_SEQUENCES) */ |
164 | | static MagickBool heif_initialized = MagickFalse; |
165 | | |
166 | | /* |
167 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
168 | | % % |
169 | | % % |
170 | | % % |
171 | | % I s H E I F % |
172 | | % % |
173 | | % % |
174 | | % % |
175 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
176 | | % |
177 | | % Method IsHEIF returns True if the image format type, identified by the |
178 | | % magick string, is supported by this HEIF reader. |
179 | | % |
180 | | % The format of the IsHEIF method is: |
181 | | % |
182 | | % MagickBool IsHEIF(const unsigned char *magick,const size_t length) |
183 | | % |
184 | | % A description of each parameter follows: |
185 | | % |
186 | | % o status: Method IsHEIF returns MagickTrue if the image format type is HEIF. |
187 | | % |
188 | | % o magick: This string is generally the first few bytes of an image file |
189 | | % or blob. |
190 | | % |
191 | | % o length: Specifies the length of the magick string. |
192 | | % |
193 | | % |
194 | | */ |
195 | | static MagickBool IsHEIF(const unsigned char *magick,const size_t length) |
196 | 97.2k | { |
197 | 97.2k | enum heif_filetype_result |
198 | 97.2k | heif_filetype; |
199 | | |
200 | 97.2k | MagickBool |
201 | 97.2k | status = MagickFalse; |
202 | | |
203 | 97.2k | if ((length < 12) || (length > (size_t) INT_MAX)) |
204 | 88 | { |
205 | 88 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
206 | 88 | "Data is too short (%zu bytes)", length); |
207 | 88 | return status; |
208 | 88 | } |
209 | | |
210 | 97.1k | heif_filetype = heif_check_filetype(magick, (int) length); |
211 | 97.1k | switch (heif_filetype) |
212 | 97.1k | { |
213 | 52.3k | case heif_filetype_yes_supported: |
214 | 52.3k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
215 | 52.3k | "Data is HEIF (supported by libheif)"); |
216 | 52.3k | status = MagickTrue; |
217 | 52.3k | break; |
218 | 41.7k | case heif_filetype_yes_unsupported: |
219 | 41.7k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
220 | 41.7k | "Data is HEIF (unsupported by libheif)"); |
221 | 41.7k | status = MagickTrue; |
222 | 41.7k | break; |
223 | 2.89k | case heif_filetype_maybe: |
224 | 2.89k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
225 | 2.89k | "Data may be HEIF"); |
226 | 2.89k | status = MagickTrue; |
227 | 2.89k | break; |
228 | 166 | case heif_filetype_no: |
229 | 166 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
230 | 166 | "Data not HEIF"); |
231 | 166 | status = MagickFalse; |
232 | 166 | break; |
233 | 0 | default: |
234 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
235 | 0 | "Unexpected enum value %d", |
236 | 0 | (int) heif_filetype); |
237 | 0 | status = MagickFalse; |
238 | 97.1k | } |
239 | 97.1k | return status; |
240 | 97.1k | } |
241 | | /* |
242 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
243 | | % % |
244 | | % % |
245 | | % % |
246 | | % R e a d H E I F I m a g e % |
247 | | % % |
248 | | % % |
249 | | % % |
250 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
251 | | % |
252 | | % ReadHEIFImage() reads an image in the HEIF image format. |
253 | | % |
254 | | % The format of the ReadHEIFImage method is: |
255 | | % |
256 | | % Image *ReadHEIFImage(const ImageInfo *image_info, |
257 | | % ExceptionInfo *exception) |
258 | | % |
259 | | % A description of each parameter follows: |
260 | | % |
261 | | % o image_info: the image info. |
262 | | % |
263 | | % o exception: return any errors or warnings in this structure. |
264 | | % |
265 | | */ |
266 | | |
267 | | #define NATIVE_PLANAR_DECODE 1 |
268 | | |
269 | | #define HEIFReadCleanup() \ |
270 | 97.2k | do \ |
271 | 97.2k | { \ |
272 | 97.2k | MagickFreeResourceLimitedMemory(heif_item_id *,item_ids); \ |
273 | 97.2k | if (heif_image) \ |
274 | 97.2k | heif_image_release(heif_image); \ |
275 | 97.2k | if (heif_image_handle) \ |
276 | 97.2k | heif_image_handle_release(heif_image_handle); \ |
277 | 97.2k | if (heif) \ |
278 | 97.2k | heif_context_free(heif); \ |
279 | 97.2k | if (decode_options) \ |
280 | 97.2k | heif_decoding_options_free(decode_options); \ |
281 | 97.2k | if (in_buf_allocated) \ |
282 | 97.2k | MagickFreeResourceLimitedMemory(unsigned char *,in_buf); \ |
283 | 97.2k | } while (0); |
284 | | |
285 | | #define ThrowHEIFReaderException(code_,reason_,image_) \ |
286 | 2.83k | do \ |
287 | 52.7k | { \ |
288 | 52.7k | HEIFReadCleanup(); \ |
289 | 52.7k | ThrowReaderException(code_,reason_,image_); \ |
290 | 0 | } while (0); |
291 | | |
292 | | /* |
293 | | Read metadata (Exif and XMP) |
294 | | */ |
295 | | static MagickPassFail ReadMetadata(struct heif_image_handle *heif_image_handle, |
296 | | Image *image, const MagickBool ignore_transformations) |
297 | 45.6k | { |
298 | 45.6k | int |
299 | 45.6k | count, |
300 | 45.6k | i; |
301 | | |
302 | 45.6k | heif_item_id |
303 | 45.6k | *ids; |
304 | | |
305 | 45.6k | struct heif_error |
306 | 45.6k | err; |
307 | | |
308 | | /* Get number of metadata blocks attached to image */ |
309 | 45.6k | count=heif_image_handle_get_number_of_metadata_blocks(heif_image_handle, |
310 | 45.6k | /*type_filter*/ NULL); |
311 | 45.6k | if (count==0) |
312 | 41.6k | return MagickPass; |
313 | | |
314 | 4.03k | ids=MagickAllocateResourceLimitedArray(heif_item_id *,count,sizeof(*ids)); |
315 | 4.03k | if (ids == (heif_item_id *) NULL) |
316 | 4.03k | ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,(char *) NULL); |
317 | | |
318 | | /* Get list of metadata block ids */ |
319 | 4.03k | count=heif_image_handle_get_list_of_metadata_block_IDs(heif_image_handle, NULL, |
320 | 4.03k | ids,count); |
321 | | |
322 | | /* For each block id ... */ |
323 | 9.81k | for (i=0; i < count; i++) |
324 | 5.78k | { |
325 | 5.78k | const char |
326 | 5.78k | *content_type, |
327 | 5.78k | *profile_name; |
328 | | |
329 | 5.78k | size_t |
330 | 5.78k | exif_pad = 0, |
331 | 5.78k | profile_size; |
332 | | |
333 | 5.78k | unsigned char |
334 | 5.78k | *profile; |
335 | | |
336 | | /* Access string indicating the type of the metadata (e.g. "Exif") */ |
337 | 5.78k | profile_name=heif_image_handle_get_metadata_type(heif_image_handle,ids[i]); |
338 | | |
339 | | /* Access string indicating the content type */ |
340 | 5.78k | content_type=heif_image_handle_get_metadata_content_type(heif_image_handle,ids[i]); |
341 | | |
342 | | /* Get the size of the raw metadata, as stored in the HEIF file */ |
343 | 5.78k | profile_size=heif_image_handle_get_metadata_size(heif_image_handle,ids[i]); |
344 | | |
345 | 5.78k | if (image->logging) |
346 | 5.78k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
347 | 5.78k | "Profile \"%s\" with content type \"%s\"" |
348 | 5.78k | " and size %" MAGICK_SIZE_T_F "u bytes", |
349 | 5.78k | profile_name ? profile_name : "(null)", |
350 | 5.78k | content_type ? content_type : "(null)", |
351 | 5.78k | (MAGICK_SIZE_T) profile_size); |
352 | | |
353 | 5.78k | if (NULL != profile_name && profile_size > 0) |
354 | 4.42k | { |
355 | 4.42k | if (strncmp(profile_name,"Exif",4) == 0) |
356 | 4.10k | exif_pad=2; |
357 | | |
358 | | /* Allocate memory for profile */ |
359 | 4.42k | profile=MagickAllocateResourceLimitedArray(unsigned char*,profile_size+exif_pad, |
360 | 4.42k | sizeof(*profile)); |
361 | 4.42k | if (profile == (unsigned char*) NULL) |
362 | 0 | { |
363 | 0 | MagickFreeResourceLimitedMemory(heif_item_id *,ids); |
364 | 0 | ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,(char *) NULL); |
365 | 0 | } |
366 | | |
367 | | /* |
368 | | Copy metadata into 'profile' buffer. For Exif data, you |
369 | | probably have to skip the first four bytes of the data, |
370 | | since they indicate the offset to the start of the TIFF |
371 | | header of the Exif data. |
372 | | */ |
373 | 4.42k | err=heif_image_handle_get_metadata(heif_image_handle,ids[i],profile+exif_pad); |
374 | | |
375 | 4.42k | if (err.code != heif_error_Ok) |
376 | 0 | { |
377 | 0 | if (image->logging) |
378 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
379 | 0 | "heif_image_handle_get_metadata() " |
380 | 0 | "reports error %d, suberror %d, message \"%s\"", |
381 | 0 | err.code, err.subcode, err.message); |
382 | 0 | MagickFreeResourceLimitedMemory(unsigned char *,profile); |
383 | 0 | MagickFreeResourceLimitedMemory(heif_item_id *,ids); |
384 | 0 | ThrowBinaryException(CorruptImageError, |
385 | 0 | AnErrorHasOccurredReadingFromFile,image->filename); |
386 | 0 | } |
387 | | |
388 | 4.42k | if (strncmp(profile_name,"Exif",4) == 0 && profile_size > 4) |
389 | 4.09k | { |
390 | | /* Parse and skip offset to TIFF header */ |
391 | 4.09k | unsigned char *p = profile; |
392 | 4.09k | magick_uint32_t offset; |
393 | | |
394 | | /* Big-endian offset decoding */ |
395 | 4.09k | offset = (magick_uint32_t) p[exif_pad+0] << 24 | |
396 | 4.09k | (magick_uint32_t) p[exif_pad+1] << 16 | |
397 | 4.09k | (magick_uint32_t) p[exif_pad+2] << 8 | |
398 | 4.09k | (magick_uint32_t) p[exif_pad+3]; |
399 | | |
400 | | /* |
401 | | If the TIFF header offset is not zero, then need to |
402 | | move the TIFF data forward to the correct offset. |
403 | | */ |
404 | 4.09k | profile_size -= 4; |
405 | 4.09k | if (offset > 0 && offset < profile_size) |
406 | 149 | { |
407 | 149 | profile_size -= offset; |
408 | | |
409 | | /* Strip any EOI marker if payload starts with a JPEG marker */ |
410 | 149 | if (profile_size > 2 && |
411 | 144 | (memcmp(p+exif_pad+4,"\xff\xd8",2) == 0 || |
412 | 139 | memcmp(p+exif_pad+4,"\xff\xe1",2) == 0) && |
413 | 8 | memcmp(p+exif_pad+4+profile_size-2,"\xff\xd9",2) == 0) |
414 | 0 | profile_size -= 2; |
415 | | |
416 | 149 | (void) memmove(p+exif_pad+4,p+exif_pad+4+offset,profile_size); |
417 | 149 | } |
418 | | |
419 | 4.09k | p[0]='E'; |
420 | 4.09k | p[1]='x'; |
421 | 4.09k | p[2]='i'; |
422 | 4.09k | p[3]='f'; |
423 | 4.09k | p[4]='\0'; |
424 | 4.09k | p[5]='\0'; |
425 | | |
426 | 4.09k | SetImageProfile(image,"EXIF",profile,profile_size+exif_pad+4); |
427 | | |
428 | | /* |
429 | | Retrieve image orientation from EXIF and store in |
430 | | image. |
431 | | */ |
432 | 4.09k | { |
433 | 4.09k | const ImageAttribute |
434 | 4.09k | *attribute; |
435 | | |
436 | 4.09k | attribute = GetImageAttribute(image,"EXIF:Orientation"); |
437 | 4.09k | if ((attribute != (const ImageAttribute *) NULL) && |
438 | 173 | (attribute->value != (char *) NULL)) |
439 | 173 | { |
440 | 173 | int |
441 | 173 | orientation; |
442 | | |
443 | 173 | orientation=MagickAtoI(attribute->value); |
444 | 173 | if ((orientation > UndefinedOrientation) && |
445 | 56 | (orientation <= LeftBottomOrientation)) |
446 | 47 | image->orientation=(OrientationType) orientation; |
447 | 173 | } |
448 | 4.09k | } |
449 | 4.09k | } |
450 | 332 | else |
451 | 332 | { |
452 | 332 | if (NULL != content_type && strncmp(content_type,"application/rdf+xml",19) == 0) |
453 | 99 | SetImageProfile(image,"XMP",profile,profile_size); |
454 | 332 | } |
455 | | /* |
456 | | Only apply Exif orientation if ignore-transformations is true |
457 | | since HEIF native transformations will handle orientation otherwise |
458 | | */ |
459 | 4.42k | if (strncmp(profile_name,"Exif",4) == 0) |
460 | 4.10k | { |
461 | 4.10k | if (ignore_transformations) |
462 | 0 | { |
463 | 0 | const ImageAttribute *attribute = GetImageAttribute(image,"EXIF:Orientation"); |
464 | 0 | if (attribute && attribute->value) |
465 | 0 | { |
466 | 0 | SetImageAttribute(image,"EXIF:Orientation","1"); |
467 | 0 | image->orientation = UndefinedOrientation; |
468 | 0 | } |
469 | 0 | } |
470 | 4.10k | } |
471 | 4.42k | MagickFreeResourceLimitedMemory(unsigned char *,profile); |
472 | 4.42k | } |
473 | 5.78k | } |
474 | 4.03k | MagickFreeResourceLimitedMemory(heif_item_id *,ids); |
475 | 4.03k | return MagickPass; |
476 | 4.03k | } |
477 | | |
478 | | |
479 | | /* |
480 | | Read Color Profile |
481 | | */ |
482 | | static MagickPassFail ReadColorProfile(struct heif_image_handle *heif_image_handle, |
483 | | Image *image) |
484 | 45.6k | { |
485 | 45.6k | struct heif_error |
486 | 45.6k | err; |
487 | | |
488 | 45.6k | enum heif_color_profile_type |
489 | 45.6k | profile_type; /* 4 chars encoded into enum by 'heif_fourcc()' */ |
490 | | |
491 | 45.6k | unsigned char |
492 | 45.6k | *profile; |
493 | | |
494 | 45.6k | profile_type = heif_image_handle_get_color_profile_type(heif_image_handle); |
495 | | |
496 | 45.6k | if (heif_color_profile_type_not_present == profile_type) |
497 | 27.3k | return MagickPass; |
498 | | |
499 | 18.3k | if (image->logging && (heif_color_profile_type_not_present !=profile_type)) |
500 | 18.3k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
501 | 18.3k | "Found color profile of type \"%c%c%c%c\"", |
502 | 18.3k | ((char) ((unsigned int) profile_type >> 24) & 0xff), |
503 | 18.3k | ((char) ((unsigned int) profile_type >> 16) & 0xff), |
504 | 18.3k | ((char) ((unsigned int) profile_type >> 8) & 0xff), |
505 | 18.3k | ((char) ((unsigned int) profile_type) & 0xff)); |
506 | | |
507 | 18.3k | if (heif_color_profile_type_prof == profile_type) |
508 | 5.13k | { |
509 | 5.13k | size_t profile_size; |
510 | | |
511 | 5.13k | profile_size = heif_image_handle_get_raw_color_profile_size(heif_image_handle); |
512 | | |
513 | 5.13k | if (image->logging) |
514 | 5.13k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
515 | 5.13k | "Reading ICC profile with size %" MAGICK_SIZE_T_F "u bytes...", |
516 | 5.13k | (MAGICK_SIZE_T) profile_size); |
517 | | |
518 | 5.13k | if (profile_size > 0) |
519 | 5.13k | { |
520 | | /* Allocate 'profile' buffer for profile */ |
521 | 5.13k | profile=MagickAllocateResourceLimitedArray(unsigned char*,profile_size, |
522 | 5.13k | sizeof(*profile)); |
523 | | |
524 | 5.13k | if (profile == (unsigned char*) NULL) |
525 | 5.13k | ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,(char *) NULL); |
526 | | |
527 | | /* Copy ICC profile to 'profile' buffer */ |
528 | 5.13k | err = heif_image_handle_get_raw_color_profile(heif_image_handle, |
529 | 5.13k | profile); |
530 | 5.13k | if (err.code != heif_error_Ok) |
531 | 0 | { |
532 | 0 | if (image->logging) |
533 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
534 | 0 | "heif_image_handle_get_raw_color_profile() " |
535 | 0 | "reports error %d, suberror %d, message \"%s\"", |
536 | 0 | err.code, err.subcode, err.message); |
537 | 0 | MagickFreeResourceLimitedMemory(unsigned char *,profile); |
538 | 0 | ThrowBinaryException(CorruptImageError, |
539 | 0 | AnErrorHasOccurredReadingFromFile,image->filename); |
540 | |
|
541 | 0 | } |
542 | 5.13k | SetImageProfile(image,"ICM",profile,profile_size); |
543 | 5.13k | MagickFreeResourceLimitedMemory(unsigned char *,profile); |
544 | 5.13k | if (image->logging) |
545 | 5.13k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
546 | 5.13k | " done reading ICC profile"); |
547 | 5.13k | } |
548 | 5.13k | } |
549 | 18.3k | return MagickPass; |
550 | 18.3k | } |
551 | | |
552 | | /* |
553 | | This progress monitor implementation is tentative since it is not invoked |
554 | | |
555 | | According to libheif issue 161 |
556 | | (https://github.com/strukturag/libheif/issues/161) progress monitor |
557 | | does not actually work since the decoders it depends on do not |
558 | | support it. |
559 | | |
560 | | Libheif issue 546 (https://github.com/strukturag/libheif/pull/546) |
561 | | suggests changing the return type of on_progress and start_progress |
562 | | to "bool" so that one can implement cancellation support. |
563 | | */ |
564 | | typedef struct ProgressUserData_ |
565 | | { |
566 | | ExceptionInfo *exception; |
567 | | Image *image; |
568 | | enum heif_progress_step step; |
569 | | unsigned long int progress; |
570 | | unsigned long int max_progress; |
571 | | |
572 | | } ProgressUserData; |
573 | | |
574 | | #if HEIF_ENABLE_PROGRESS_MONITOR |
575 | | /* Called when progress monitor starts. The 'max_progress' parameter indicates the maximum value of progress */ |
576 | | static void start_progress(enum heif_progress_step step, int max_progress, void* progress_user_data) |
577 | | { |
578 | | ProgressUserData *context= (ProgressUserData *) progress_user_data; |
579 | | Image *image=context->image; |
580 | | context->step = step; |
581 | | context->progress = 0; |
582 | | context->max_progress = max_progress; |
583 | | if (context->image->logging) |
584 | | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
585 | | "start_progress: step=%d, max_progress=%d",step, max_progress); |
586 | | MagickMonitorFormatted(context->progress,context->max_progress,&image->exception, |
587 | | "[%s] Loading image: %lux%lu... ", |
588 | | image->filename, |
589 | | image->columns,image->rows); |
590 | | } |
591 | | |
592 | | /* Called for each step of progress. The 'progress' parameter represents the progress within the span of 'max_progress' */ |
593 | | static void on_progress(enum heif_progress_step step, int progress, void* progress_user_data) |
594 | | { |
595 | | ProgressUserData *context = (ProgressUserData *) progress_user_data; |
596 | | Image *image=context->image; |
597 | | context->step = step; |
598 | | context->progress = progress; |
599 | | if (context->image->logging) |
600 | | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
601 | | "on_progress: step=%d, progress=%d",step, progress); |
602 | | MagickMonitorFormatted(context->progress,context->max_progress,&image->exception, |
603 | | "[%s] Loading image: %lux%lu... ", |
604 | | image->filename, |
605 | | image->columns,image->rows); |
606 | | } |
607 | | |
608 | | /* Called when progress monitor stops */ |
609 | | static void end_progress(enum heif_progress_step step, void* progress_user_data) |
610 | | { |
611 | | ProgressUserData *context = (ProgressUserData *) progress_user_data; |
612 | | context->step = step; |
613 | | if (context->image->logging) |
614 | | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
615 | | "end_progress: step=%d",step); |
616 | | } |
617 | | |
618 | | #endif /* if HEIF_ENABLE_PROGRESS_MONITOR */ |
619 | | |
620 | | /* |
621 | | Apply security limits |
622 | | */ |
623 | | static MagickPassFail apply_security_limits(const ImageInfo *image_info, |
624 | | struct heif_context *heif_context) |
625 | 97.2k | { |
626 | 97.2k | #if LIBHEIF_NUMERIC_VERSION >= LIBHEIF_MAKE_VERSION(1,19,0) |
627 | 97.2k | struct heif_security_limits |
628 | 97.2k | *security_limits; |
629 | | |
630 | 97.2k | magick_uintmax_t |
631 | 97.2k | memory_limit, |
632 | 97.2k | width_limit, |
633 | 97.2k | height_limit, |
634 | 97.2k | pixels_limit; |
635 | | |
636 | 97.2k | security_limits=heif_context_get_security_limits(heif_context); |
637 | | |
638 | 97.2k | memory_limit = (magick_uintmax_t) GetMagickResourceLimit(MemoryResource); |
639 | 97.2k | security_limits->max_memory_block_size = (uint64_t) memory_limit; |
640 | 97.2k | security_limits->max_total_memory = (uint64_t) memory_limit; |
641 | 97.2k | width_limit = (magick_uintmax_t) GetMagickResourceLimit(WidthResource); |
642 | 97.2k | if (width_limit > INT_MAX) |
643 | 0 | width_limit = INT_MAX; |
644 | 97.2k | height_limit = (magick_uintmax_t) GetMagickResourceLimit(HeightResource); |
645 | 97.2k | if (height_limit > INT_MAX) |
646 | 0 | height_limit = INT_MAX; |
647 | 97.2k | pixels_limit = (magick_uintmax_t) GetMagickResourceLimit(PixelsResource); |
648 | 97.2k | if ((width_limit < pixels_limit) && (height_limit < pixels_limit) && |
649 | 97.2k | (width_limit*height_limit < pixels_limit)) |
650 | 25.6k | pixels_limit = width_limit*height_limit; |
651 | | |
652 | 97.2k | security_limits->max_image_size_pixels = (uint64_t) pixels_limit; |
653 | | |
654 | | /* |
655 | | Version 1 interface has: |
656 | | |
657 | | uint64_t max_image_size_pixels; |
658 | | uint64_t max_number_of_tiles; |
659 | | uint32_t max_bayer_pattern_pixels; |
660 | | uint32_t max_items; |
661 | | |
662 | | uint32_t max_color_profile_size; |
663 | | uint64_t max_memory_block_size; |
664 | | |
665 | | uint32_t max_components; |
666 | | |
667 | | uint32_t max_iloc_extents_per_item; |
668 | | uint32_t max_size_entity_group; |
669 | | |
670 | | uint32_t max_children_per_box; // for all boxes that are not covered by other limits |
671 | | |
672 | | and the version 2 interface adds: |
673 | | |
674 | | uint64_t max_total_memory; |
675 | | uint32_t max_sample_description_box_entries; |
676 | | uint32_t max_sample_group_description_box_entries; |
677 | | |
678 | | */ |
679 | | /* version 1 interface */ |
680 | 97.2k | MagickAttributeToU64(image_info,"heif","max-image-size-pixels", |
681 | 97.2k | security_limits->max_image_size_pixels); |
682 | | |
683 | 97.2k | MagickAttributeToU64(image_info,"heif","max-number-of-tiles", |
684 | 97.2k | security_limits->max_number_of_tiles); |
685 | | |
686 | 97.2k | MagickAttributeToU32(image_info,"heif","max-bayer-pattern-pixels", |
687 | 97.2k | security_limits->max_bayer_pattern_pixels); |
688 | | |
689 | 97.2k | MagickAttributeToU32(image_info,"heif","max-items", |
690 | 97.2k | security_limits->max_items); |
691 | | |
692 | 97.2k | MagickAttributeToU32(image_info,"heif","max-color-profile-size", |
693 | 97.2k | security_limits->max_color_profile_size); |
694 | | |
695 | 97.2k | MagickAttributeToU64(image_info,"heif","max-memory-block-size", |
696 | 97.2k | security_limits->max_memory_block_size); |
697 | | |
698 | 97.2k | MagickAttributeToU32(image_info,"heif","max-components", |
699 | 97.2k | security_limits->max_components); |
700 | | |
701 | 97.2k | MagickAttributeToU32(image_info,"heif","max-iloc-extents-per-item", |
702 | 97.2k | security_limits->max_iloc_extents_per_item); |
703 | | |
704 | 97.2k | MagickAttributeToU32(image_info,"heif","max-size-entity-group", |
705 | 97.2k | security_limits->max_size_entity_group); |
706 | | |
707 | 97.2k | MagickAttributeToU32(image_info,"heif","max-children-per-box", |
708 | 97.2k | security_limits->max_children_per_box); |
709 | | |
710 | | /* version 2 interface */ |
711 | | |
712 | | /* Added in 1.13.0, September 2, 2022 */ |
713 | 97.2k | MagickAttributeToU64(image_info,"heif","max-total-memory", |
714 | 97.2k | security_limits->max_total_memory); |
715 | | |
716 | | /* Added in 1.13.0, September 2, 2022 */ |
717 | 97.2k | MagickAttributeToU32(image_info,"heif","max-sample-description-box-entries", |
718 | 97.2k | security_limits->max_sample_description_box_entries); |
719 | | |
720 | | /* Added in 1.17.6? ?*/ |
721 | 97.2k | MagickAttributeToU32(image_info,"heif","max-sample-group-description-box-entries", |
722 | 97.2k | security_limits->max_sample_group_description_box_entries); |
723 | | |
724 | 97.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
725 | 97.2k | "security limits:" |
726 | | /* version 1 interface */ |
727 | 97.2k | " max_image_size_pixels = %" MAGICK_UINTMAX_F "u""," |
728 | 97.2k | " max_number_of_tiles = %" MAGICK_UINTMAX_F "u""," |
729 | 97.2k | " max_bayer_pattern_pixels = %" MAGICK_UINTMAX_F "u""," |
730 | 97.2k | " max_items = %" MAGICK_UINTMAX_F "u""," |
731 | 97.2k | " max_color_profile_size = %" MAGICK_UINTMAX_F "u""," |
732 | 97.2k | " max_memory_block_size = %" MAGICK_UINTMAX_F "u""," |
733 | 97.2k | " max_components = %" MAGICK_UINTMAX_F "u""," |
734 | 97.2k | " max_iloc_extents_per_item = %" MAGICK_UINTMAX_F "u""," |
735 | 97.2k | " max_size_entity_group = %" MAGICK_UINTMAX_F "u""," |
736 | 97.2k | " max_children_per_box = %" MAGICK_UINTMAX_F "u""," |
737 | | /* version 2 interface */ |
738 | 97.2k | " max_total_memory = %" MAGICK_UINTMAX_F "u""," |
739 | 97.2k | " max_sample_description_box_entries = %" MAGICK_UINTMAX_F "u""," |
740 | 97.2k | " max_sample_group_description_box_entries = %" MAGICK_UINTMAX_F "u", |
741 | | /* version 1 interface */ |
742 | 97.2k | (magick_uintmax_t) security_limits->max_image_size_pixels, |
743 | 97.2k | (magick_uintmax_t) security_limits->max_number_of_tiles, |
744 | 97.2k | (magick_uintmax_t) security_limits->max_bayer_pattern_pixels, |
745 | 97.2k | (magick_uintmax_t) security_limits->max_items, |
746 | 97.2k | (magick_uintmax_t) security_limits->max_color_profile_size, |
747 | 97.2k | (magick_uintmax_t) security_limits->max_memory_block_size, |
748 | 97.2k | (magick_uintmax_t) security_limits->max_components, |
749 | 97.2k | (magick_uintmax_t) security_limits->max_iloc_extents_per_item, |
750 | 97.2k | (magick_uintmax_t) security_limits->max_size_entity_group, |
751 | 97.2k | (magick_uintmax_t) security_limits->max_children_per_box, |
752 | | /* version 2 interface */ |
753 | 97.2k | (magick_uintmax_t) security_limits->max_total_memory, |
754 | 97.2k | (magick_uintmax_t) security_limits->max_sample_description_box_entries, |
755 | 97.2k | (magick_uintmax_t) security_limits->max_sample_group_description_box_entries |
756 | 97.2k | ); |
757 | | #else |
758 | | { |
759 | | /* DEPRECATED: Add an image size limit based on maximum_width ^ 2 */ |
760 | | magick_int64_t width_limit = GetMagickResourceLimit(WidthResource); |
761 | | if (MagickResourceInfinity != width_limit) |
762 | | { |
763 | | if (width_limit > INT_MAX) |
764 | | width_limit = INT_MAX; |
765 | | /* Added in libheif 1.4.0 */ |
766 | | heif_context_set_maximum_image_size_limit(heif, (int) width_limit); |
767 | | } |
768 | | } |
769 | | |
770 | | #endif /* if LIBHEIF_NUMERIC_VERSION >= LIBHEIF_MAKE_VERSION(1,19,0) */ |
771 | | |
772 | 97.2k | return MagickPass; |
773 | 97.2k | } |
774 | | |
775 | | /* |
776 | | libheif/color-conversion/colorconversion.cc provides this to output to |
777 | | an ostream, but our code is not C++! |
778 | | |
779 | | std::ostream& operator<<(std::ostream& ostr, heif_colorspace c) |
780 | | */ |
781 | | static const char *HEIF_colorspace_to_string(const enum heif_colorspace c) |
782 | 107k | { |
783 | 107k | const char *s = "unknown"; |
784 | | |
785 | 107k | switch (c) |
786 | 107k | { |
787 | 34 | case heif_colorspace_undefined: |
788 | 34 | s = "undefined"; |
789 | 34 | break; |
790 | 35.8k | case heif_colorspace_YCbCr: |
791 | 35.8k | s = "YCbCr"; |
792 | 35.8k | break; |
793 | 63.7k | case heif_colorspace_RGB: |
794 | 63.7k | s = "RGB"; |
795 | 63.7k | break; |
796 | 7.40k | case heif_colorspace_monochrome: |
797 | 7.40k | s = "monochrome"; |
798 | 7.40k | break; |
799 | 0 | case heif_colorspace_nonvisual: |
800 | 0 | s = "nonvisual"; |
801 | 0 | break; |
802 | 0 | #if LIBHEIF_HAVE_VERSION(1,21,3) |
803 | | /* Git commit cb6e524 added heif_colorspace_filter_array on 2/24/26 */ |
804 | 5 | case heif_colorspace_filter_array: |
805 | 5 | s = "filter_array"; |
806 | 5 | break; |
807 | 107k | #endif /* if LIBHEIF_HAVE_VERSION(1,21,3) */ |
808 | 107k | } |
809 | | |
810 | 107k | return s; |
811 | 107k | } |
812 | | |
813 | | static ColorspaceType HEIF_colorspace_to_ColorspaceType(const enum heif_colorspace c) |
814 | 15.7k | { |
815 | 15.7k | ColorspaceType t = UndefinedColorspace; |
816 | | |
817 | 15.7k | switch (c) |
818 | 15.7k | { |
819 | 0 | case heif_colorspace_undefined: |
820 | 0 | t = UndefinedColorspace; |
821 | 0 | break; |
822 | 0 | case heif_colorspace_YCbCr: |
823 | 0 | t = YUVColorspace; |
824 | 0 | break; |
825 | 15.3k | case heif_colorspace_RGB: |
826 | 15.3k | t = RGBColorspace; |
827 | 15.3k | break; |
828 | 406 | case heif_colorspace_monochrome: |
829 | 406 | t = GRAYColorspace; |
830 | 406 | break; |
831 | 0 | case heif_colorspace_nonvisual: |
832 | 0 | t = UndefinedColorspace; |
833 | 0 | break; |
834 | 0 | #if LIBHEIF_HAVE_VERSION(1,21,3) |
835 | 1 | case heif_colorspace_filter_array: |
836 | 1 | t = UndefinedColorspace; |
837 | 1 | break; |
838 | 15.7k | #endif /* if LIBHEIF_HAVE_VERSION(1,21,2) */ |
839 | 15.7k | } |
840 | | |
841 | 15.7k | return t; |
842 | 15.7k | } |
843 | | |
844 | | static const char *HEIF_chroma_to_string(const enum heif_chroma c) |
845 | 105k | { |
846 | 105k | const char *s = "unknown"; |
847 | | |
848 | 105k | switch (c) |
849 | 105k | { |
850 | 1.41k | case heif_chroma_undefined: |
851 | 1.41k | s = "undefined"; |
852 | 1.41k | break; |
853 | 7.38k | case heif_chroma_monochrome: |
854 | 7.38k | s = "monochrome"; |
855 | 7.38k | break; |
856 | 15.8k | case heif_chroma_420: |
857 | 15.8k | s = "420"; |
858 | 15.8k | break; |
859 | 310 | case heif_chroma_422: |
860 | 310 | s = "422"; |
861 | 310 | break; |
862 | 81.0k | case heif_chroma_444: |
863 | 81.0k | s = "444"; |
864 | 81.0k | break; |
865 | 0 | case heif_chroma_interleaved_RGB: |
866 | 0 | s = "interleaved_RGB"; |
867 | 0 | break; |
868 | 0 | case heif_chroma_interleaved_RGBA: |
869 | 0 | s = "interleaved_RGBA"; |
870 | 0 | break; |
871 | 0 | case heif_chroma_interleaved_RRGGBB_BE: |
872 | 0 | s = "interleaved_RRGGBB_BE"; |
873 | 0 | break; |
874 | 0 | case heif_chroma_interleaved_RRGGBBAA_BE: |
875 | 0 | s = "interleaved_RRGGBBAA_BE"; |
876 | 0 | break; |
877 | 0 | case heif_chroma_interleaved_RRGGBB_LE: |
878 | 0 | s = "interleaved_RRGGBB_LE"; |
879 | 0 | break; |
880 | 0 | case heif_chroma_interleaved_RRGGBBAA_LE: |
881 | 0 | s = "interleaved_RRGGBBAA_LE"; |
882 | 0 | break; |
883 | 105k | } |
884 | | |
885 | 105k | return s; |
886 | 105k | } |
887 | | |
888 | | static const char *HEIF_channel_to_string(const enum heif_channel c) |
889 | 43.4k | { |
890 | 43.4k | const char *s = "unknown"; |
891 | | |
892 | 43.4k | switch (c) |
893 | 43.4k | { |
894 | 756 | case heif_channel_Y: |
895 | 756 | s = "Y"; |
896 | 756 | break; |
897 | 0 | case heif_channel_Cb: |
898 | 0 | s = "Cb"; |
899 | 0 | break; |
900 | 0 | case heif_channel_Cr: |
901 | 0 | s = "Cr"; |
902 | 0 | break; |
903 | 14.2k | case heif_channel_R: |
904 | 14.2k | s = "R"; |
905 | 14.2k | break; |
906 | 14.2k | case heif_channel_G: |
907 | 14.2k | s = "G"; |
908 | 14.2k | break; |
909 | 14.2k | case heif_channel_B: |
910 | 14.2k | s = "B"; |
911 | 14.2k | break; |
912 | 40 | case heif_channel_Alpha: |
913 | 40 | s = "Alpha"; |
914 | 40 | break; |
915 | 0 | case heif_channel_interleaved: |
916 | 0 | s = "interleaved"; |
917 | 0 | break; |
918 | 0 | #if LIBHEIF_HAVE_VERSION(1,21,3) |
919 | 0 | case heif_channel_filter_array: |
920 | | /* Git commit cb6e524 added heif_channel_filter_array on 2/24/26 */ |
921 | 0 | s = "filter_array"; |
922 | 0 | break; |
923 | 0 | #endif /* if LIBHEIF_HAVE_VERSION(1,21,3) */ |
924 | 0 | case heif_channel_depth: |
925 | 0 | s = "channel_depth"; |
926 | 0 | break; |
927 | 0 | case heif_channel_disparity: |
928 | 0 | s = "disparity"; |
929 | 0 | break; |
930 | 0 | #if LIBHEIF_HAVE_VERSION(1,21,3) |
931 | | /* Added by changeset b4326c42 on 2/12/26 after v1.21.2 */ |
932 | 0 | case heif_channel_unknown: |
933 | 0 | s = "unknown"; |
934 | 0 | break; |
935 | 0 | #endif /* if LIBHEIF_HAVE_VERSION(1,21,3) */ |
936 | 0 | default: |
937 | 0 | s = "unknown"; |
938 | 0 | break; |
939 | 43.4k | } |
940 | | |
941 | 43.4k | return s; |
942 | 43.4k | } |
943 | | |
944 | | static MagickPassFail ReadHEIFImageFrame(heif_image_handle* heif_image_handle, |
945 | | struct heif_decoding_options *decode_options, |
946 | | Image *image, |
947 | | const ImageInfo *image_info, |
948 | | ExceptionInfo *exception) |
949 | 46.1k | { |
950 | 46.1k | #define HEIFReadImageFrameCleanup() \ |
951 | 46.1k | do \ |
952 | 46.1k | { \ |
953 | 46.1k | if (heif_image) \ |
954 | 46.1k | heif_image_release(heif_image); \ |
955 | 46.1k | } while (0); |
956 | | |
957 | 46.1k | #define ThrowHEIFThrowReadImageFrameException(code_,reason_,image_) \ |
958 | 46.1k | do \ |
959 | 30.3k | { \ |
960 | 30.3k | HEIFReadImageFrameCleanup(); \ |
961 | 30.3k | if (code_ > exception->severity) \ |
962 | 30.3k | { \ |
963 | 30.3k | ThrowException(&(image_)->exception,code_,reason_,(image_)->filename); \ |
964 | 30.3k | CopyException(exception,&(image_)->exception); \ |
965 | 30.3k | } \ |
966 | 30.3k | return(MagickFail); \ |
967 | 30.3k | } while (0); |
968 | | |
969 | 46.1k | struct heif_image |
970 | 46.1k | *heif_image = NULL; |
971 | | |
972 | 46.1k | PixelPacket |
973 | 46.1k | *q; |
974 | | |
975 | 46.1k | long |
976 | 46.1k | x, |
977 | 46.1k | y; |
978 | | |
979 | 46.1k | int |
980 | 46.1k | bits_per_pixel; |
981 | | |
982 | 46.1k | struct heif_error |
983 | 46.1k | heif_status; |
984 | | |
985 | | #if HEIF_ENABLE_PROGRESS_MONITOR |
986 | | ProgressUserData |
987 | | progress_user_data; |
988 | | #endif |
989 | | |
990 | 46.1k | enum heif_colorspace |
991 | 46.1k | heif_colorspace, |
992 | 46.1k | preferred_colorspace = heif_colorspace_undefined; |
993 | | |
994 | 46.1k | enum heif_chroma |
995 | 46.1k | preferred_chroma = heif_chroma_undefined; |
996 | | |
997 | | /* |
998 | | Note: Those values are preliminary but likely the upper bound |
999 | | The real image values might be rotated or cropped due to transformations |
1000 | | |
1001 | | heif_image_handle APIs are declared in heif_image_handle.h |
1002 | | */ |
1003 | 46.1k | image->depth=8; |
1004 | 46.1k | bits_per_pixel=heif_image_handle_get_luma_bits_per_pixel(heif_image_handle); |
1005 | | /* |
1006 | | This function only works for YCbCr-based images. We only use it |
1007 | | in ping mode since otherwise it screws up the depth in the Q8 |
1008 | | build (due to scaling in libheif). |
1009 | | */ |
1010 | 46.1k | if (image_info->ping) |
1011 | 0 | { |
1012 | 0 | if (image->logging) |
1013 | 0 | if (bits_per_pixel < 0) |
1014 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1015 | 0 | "Image has undefined bit-depth!"); |
1016 | 0 | if (bits_per_pixel > 0) |
1017 | 0 | image->depth=(unsigned int) bits_per_pixel; |
1018 | 0 | if (image->logging) |
1019 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1020 | 0 | "Depth: %u", image->depth); |
1021 | 0 | } |
1022 | | |
1023 | 46.1k | image->columns=heif_image_handle_get_width(heif_image_handle); |
1024 | 46.1k | image->rows=heif_image_handle_get_height(heif_image_handle); |
1025 | 46.1k | if (image->logging) |
1026 | 46.1k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1027 | 46.1k | "Geometry: %lux%lu", image->columns, image->rows); |
1028 | 46.1k | image->matte=MagickFalse; |
1029 | 46.1k | if (heif_image_handle_has_alpha_channel(heif_image_handle)) |
1030 | 335 | image->matte=MagickTrue; |
1031 | 46.1k | if (image->logging) |
1032 | 46.1k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1033 | 46.1k | "Matte: %s", image->matte ? "True" : "False"); |
1034 | 46.1k | heif_status=heif_image_handle_get_preferred_decoding_colorspace(heif_image_handle, |
1035 | 46.1k | &preferred_colorspace, |
1036 | 46.1k | &preferred_chroma); |
1037 | 46.1k | if (heif_status.code != heif_error_Ok) |
1038 | 440 | { |
1039 | 440 | if (image->logging) |
1040 | 440 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1041 | 440 | "heif_image_handle_get_preferred_decoding_colorspace() " |
1042 | 440 | "reports error %d, suberror %d, message \"%s\"", |
1043 | 440 | heif_status.code, heif_status.subcode, heif_status.message); |
1044 | 440 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
1045 | 0 | } |
1046 | 45.6k | if (image->logging) |
1047 | 45.6k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1048 | 45.6k | "Preferred colorspace: %s, " |
1049 | 45.6k | "Preferred chroma: %s", |
1050 | 45.6k | HEIF_colorspace_to_string(preferred_colorspace), |
1051 | 45.6k | HEIF_chroma_to_string(preferred_chroma)); |
1052 | | |
1053 | | /* Read EXIF and XMP profile */ |
1054 | 45.6k | if (!ReadMetadata(heif_image_handle, image, decode_options->ignore_transformations)) |
1055 | 0 | { |
1056 | 0 | CopyException(exception,&image->exception); |
1057 | 0 | HEIFReadImageFrameCleanup(); |
1058 | 0 | return MagickFail; |
1059 | 0 | } |
1060 | | |
1061 | | /* Read ICC profile */ |
1062 | 45.6k | if (!ReadColorProfile(heif_image_handle, image)) |
1063 | 0 | { |
1064 | 0 | CopyException(exception,&image->exception); |
1065 | 0 | HEIFReadImageFrameCleanup(); |
1066 | 0 | return MagickFail; |
1067 | 0 | } |
1068 | | |
1069 | | /* |
1070 | | When applying transformations the whole image has to be read to |
1071 | | get the real dimensions. We no longer read the whole image because |
1072 | | it makes 'identify' extremely slow for large files. |
1073 | | |
1074 | | The user should specify: |
1075 | | |
1076 | | -define heif:ignore-transformations=false |
1077 | | |
1078 | | in order to force reading much of the image to get fully accurate |
1079 | | information. |
1080 | | */ |
1081 | 45.6k | if (image_info->ping && decode_options->ignore_transformations) |
1082 | 0 | { |
1083 | 0 | HEIFReadImageFrameCleanup(); |
1084 | 0 | if (image->logging) |
1085 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1086 | 0 | "Returning early without pixels due to ping mode..."); |
1087 | 0 | return MagickPass; |
1088 | 0 | } |
1089 | | |
1090 | | |
1091 | | /* |
1092 | | Be prepared to handle images on per-plane basis. Upscale |
1093 | | subsampled chroma if necessary. |
1094 | | */ |
1095 | 45.6k | if (image->logging) |
1096 | 45.6k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1097 | 45.6k | "Decoding image..."); |
1098 | | |
1099 | 45.6k | { |
1100 | 45.6k | enum heif_chroma heif_chroma_decode=preferred_chroma; |
1101 | 45.6k | enum heif_colorspace heif_colorspace_decode=preferred_colorspace; |
1102 | | |
1103 | | /* Influence whether to return original YBbCr or RGB */ |
1104 | 45.6k | if (heif_colorspace_decode == heif_colorspace_YCbCr) |
1105 | 35.8k | { |
1106 | 35.8k | const char *string_value; |
1107 | 35.8k | MagickBool preserve_colorspace = MagickFalse; |
1108 | | /* |
1109 | | Use heif:preserve-colorspace=true or |
1110 | | heif:preserve-colorspace=1 to return the image in the |
1111 | | original colorspace. This prevents the conversion of a |
1112 | | YCbCr image to RGB. Note that libheif may automatically |
1113 | | apply a color profile when converting a YCbCr image to RGB, |
1114 | | and requesting original YCbCr defeats that. |
1115 | | */ |
1116 | 35.8k | if ((string_value = AccessDefinition(image_info,"heif","preserve-colorspace"))) |
1117 | 0 | { |
1118 | 0 | if ((LocaleCompare(string_value,"true") == 0) || |
1119 | 0 | (LocaleCompare(string_value,"yes") == 0) || |
1120 | 0 | (LocaleCompare(string_value,"1") == 0)) |
1121 | 0 | preserve_colorspace = MagickTrue; |
1122 | 0 | } |
1123 | 35.8k | if (preserve_colorspace == MagickFalse) |
1124 | 35.8k | heif_colorspace_decode=heif_colorspace_RGB; |
1125 | | |
1126 | | |
1127 | | /* Upscale chroma if necessary */ |
1128 | 35.8k | if ((heif_chroma_decode == heif_chroma_420) || (heif_chroma_decode == heif_chroma_422)) |
1129 | 16.1k | { |
1130 | 16.1k | heif_chroma_decode=heif_chroma_444; |
1131 | 16.1k | } |
1132 | 35.8k | } |
1133 | 45.6k | if (heif_colorspace_decode == heif_colorspace_RGB) |
1134 | 42.1k | { |
1135 | 42.1k | const char *string_value; |
1136 | 42.1k | MagickBool interleaved_decode = MagickFalse; |
1137 | | |
1138 | 42.1k | if ((string_value = AccessDefinition(image_info,"heif","interleaved-rgb-decode"))) |
1139 | 0 | { |
1140 | 0 | if ((LocaleCompare(string_value,"true") == 0) || |
1141 | 0 | (LocaleCompare(string_value,"yes") == 0) || |
1142 | 0 | (LocaleCompare(string_value,"1") == 0)) |
1143 | 0 | interleaved_decode = MagickTrue; |
1144 | 0 | } |
1145 | | |
1146 | 42.1k | if (interleaved_decode) |
1147 | 0 | { |
1148 | | /* Use interleaved mode */ |
1149 | 0 | if (image->matte) |
1150 | 0 | { |
1151 | 0 | if (bits_per_pixel > 8) |
1152 | 0 | { |
1153 | | #if defined(WORDS_BIGENDIAN) |
1154 | | heif_chroma_decode=heif_chroma_interleaved_RRGGBBAA_BE; |
1155 | | #else |
1156 | 0 | heif_chroma_decode=heif_chroma_interleaved_RRGGBBAA_LE; |
1157 | 0 | #endif /* if defined(WORDS_BIGENDIAN) */ |
1158 | 0 | } |
1159 | 0 | else |
1160 | 0 | { |
1161 | 0 | heif_chroma_decode=heif_chroma_interleaved_RGBA; |
1162 | 0 | } |
1163 | 0 | } |
1164 | 0 | else |
1165 | 0 | { |
1166 | 0 | if (bits_per_pixel > 8) |
1167 | 0 | { |
1168 | | #if defined(WORDS_BIGENDIAN) |
1169 | | heif_chroma_decode=heif_chroma_interleaved_RRGGBB_BE; |
1170 | | #else |
1171 | 0 | heif_chroma_decode=heif_chroma_interleaved_RRGGBB_LE; |
1172 | 0 | #endif/* if defined(WORDS_BIGENDIAN) */ |
1173 | 0 | } |
1174 | 0 | else |
1175 | 0 | { |
1176 | 0 | heif_chroma_decode=heif_chroma_interleaved_RGB; |
1177 | 0 | } |
1178 | 0 | } |
1179 | 0 | } |
1180 | 42.1k | else |
1181 | 42.1k | { |
1182 | | /* Use planar mode */ |
1183 | 42.1k | heif_chroma_decode=heif_chroma_444; |
1184 | 42.1k | } |
1185 | 42.1k | } |
1186 | | |
1187 | 45.6k | if (image->logging && IsEventLogged(CoderEvent)) |
1188 | 0 | { |
1189 | 0 | #if 1 |
1190 | | /* See libheif/api/libheif/heif_tiling.h */ |
1191 | 0 | heif_image_tiling tiling; |
1192 | 0 | (void) memset(&tiling,0,sizeof(tiling)); |
1193 | 0 | heif_status=heif_image_handle_get_image_tiling(heif_image_handle, decode_options->ignore_transformations, &tiling); |
1194 | 0 | if (heif_status.code != heif_error_Ok) |
1195 | 0 | { |
1196 | 0 | if (image->logging) |
1197 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1198 | 0 | "heif_image_handle_get_image_tiling() " |
1199 | 0 | "reports error %d, suberror %d, message \"%s\"", |
1200 | 0 | heif_status.code, heif_status.subcode, heif_status.message); |
1201 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
1202 | 0 | } |
1203 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1204 | 0 | "Tiling: version %d, num_columns=%u, num_rows=%u," |
1205 | 0 | " tile_width=%u, tile_height=%u, image_width=%u, image_height=%u," |
1206 | 0 | " top_offset=%u, left_offset=%u, number_of_extra_dimensions=%u", |
1207 | 0 | tiling.version, tiling.num_columns, tiling.num_rows, |
1208 | 0 | tiling.tile_width, tiling.tile_height, tiling.image_width, tiling.image_height, |
1209 | 0 | tiling.top_offset, tiling.left_offset, |
1210 | 0 | tiling.number_of_extra_dimensions); |
1211 | 0 | #endif |
1212 | | #if 0 |
1213 | | /* From heif_dec.cc decode_image_tiles() */ |
1214 | | for (uint32_t ty = 0; ty < tiling.num_rows; ty++) |
1215 | | for (uint32_t tx = 0; tx < tiling.num_columns; tx++) |
1216 | | { |
1217 | | /* |
1218 | | heif_image* image; |
1219 | | heif_error err; |
1220 | | err = heif_image_handle_decode_image_tile(handle, |
1221 | | &image, |
1222 | | encoder->colorspace(has_alpha), |
1223 | | encoder->chroma(has_alpha, bit_depth), |
1224 | | decode_options, tx, ty); |
1225 | | heif_error heif_image_handle_decode_image_tile(const heif_image_handle* in_handle, |
1226 | | heif_image** out_img, |
1227 | | enum heif_colorspace colorspace, |
1228 | | enum heif_chroma chroma, |
1229 | | const heif_decoding_options* options, |
1230 | | uint32_t tile_x, uint32_t tile_y); |
1231 | | heif_image_release(image); |
1232 | | */ |
1233 | | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1234 | | "Tile %u,%u: (x1=%u y1=%u x2=%u y2=%u)", |
1235 | | tx, ty, tx*tiling.tile_width, ty*tiling.tile_height, |
1236 | | ((tx+1)*tiling.tile_width)-1, ((ty+1)*tiling.tile_height)-1); |
1237 | | } |
1238 | | #endif |
1239 | 0 | } |
1240 | | |
1241 | 45.6k | if (image->logging) |
1242 | 45.6k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1243 | 45.6k | "heif_decode_image: colorspace_decode=%s, chroma_decode=%s", |
1244 | 45.6k | HEIF_colorspace_to_string(heif_colorspace_decode), |
1245 | 45.6k | HEIF_chroma_to_string(heif_chroma_decode)); |
1246 | 45.6k | heif_status=heif_decode_image(heif_image_handle, |
1247 | 45.6k | &heif_image, |
1248 | 45.6k | heif_colorspace_decode, /* Chose preferred colorspace */ |
1249 | 45.6k | heif_chroma_decode, |
1250 | 45.6k | decode_options); |
1251 | 45.6k | if (heif_status.code != heif_error_Ok) |
1252 | 29.9k | { |
1253 | 29.9k | if (image->logging) |
1254 | 29.9k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1255 | 29.9k | "heif_decode_image() " |
1256 | 29.9k | "reports error %d, suberror %d, message \"%s\"", |
1257 | 29.9k | heif_status.code, heif_status.subcode, heif_status.message); |
1258 | 29.9k | } |
1259 | 45.6k | } |
1260 | 45.6k | if (heif_status.code == heif_error_Memory_allocation_error) |
1261 | 45.1k | ThrowHEIFThrowReadImageFrameException(ResourceLimitError,MemoryAllocationFailed,image); |
1262 | 45.1k | if (heif_status.code != heif_error_Ok) |
1263 | 29.3k | { |
1264 | 29.3k | ThrowHEIFThrowReadImageFrameException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
1265 | 0 | } |
1266 | 15.7k | if (heif_image == (struct heif_image *) NULL) |
1267 | 0 | { |
1268 | 0 | if (image->logging) |
1269 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1270 | 0 | "heif_decode_image() reports heif_error_Ok but failed to return an image!"); |
1271 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
1272 | 0 | } |
1273 | | |
1274 | | /* |
1275 | | Trace decoding warnings |
1276 | | */ |
1277 | 15.7k | if (image->logging && IsEventLogged(CoderEvent)) |
1278 | 0 | { |
1279 | 0 | int i; |
1280 | |
|
1281 | 0 | for (i = 0;; i++) |
1282 | 0 | { |
1283 | 0 | int n; |
1284 | 0 | n = heif_image_get_decoding_warnings(heif_image,i,&heif_status, 1); |
1285 | 0 | if (n == 0) |
1286 | 0 | break; |
1287 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1288 | 0 | "Warning: %s", heif_status.message); |
1289 | 0 | } |
1290 | 0 | } |
1291 | | |
1292 | | /* |
1293 | | Update image properties |
1294 | | */ |
1295 | 15.7k | image->columns=heif_image_get_primary_width(heif_image); |
1296 | 15.7k | image->rows=heif_image_get_primary_height(heif_image); |
1297 | 15.7k | if (image->logging) |
1298 | 15.7k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1299 | 15.7k | "Geometry: %lux%lu", |
1300 | 15.7k | image->columns,image->rows); |
1301 | 15.7k | heif_colorspace=heif_image_get_colorspace(heif_image); |
1302 | 15.7k | image->colorspace=HEIF_colorspace_to_ColorspaceType(heif_colorspace); |
1303 | 15.7k | if (image->logging) |
1304 | 15.7k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1305 | 15.7k | "HEIF colorspace: %s", |
1306 | 15.7k | HEIF_colorspace_to_string(heif_colorspace)); |
1307 | | |
1308 | 15.7k | if (image_info->ping) |
1309 | 0 | { |
1310 | 0 | HEIFReadImageFrameCleanup(); |
1311 | 0 | if (image->logging) |
1312 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1313 | 0 | "Returning without pixels due to ping mode..."); |
1314 | 0 | return MagickPass; |
1315 | 0 | } |
1316 | | |
1317 | 15.7k | if (CheckImagePixelLimits(image, exception) != MagickPass) |
1318 | 1.11k | { |
1319 | 1.11k | HEIFReadImageFrameCleanup(); |
1320 | 1.11k | return MagickFail; |
1321 | 1.11k | } |
1322 | | |
1323 | 14.6k | { |
1324 | | |
1325 | 14.6k | typedef union |
1326 | 14.6k | { |
1327 | 14.6k | const uint8_t *u8; |
1328 | 14.6k | const uint16_t *u16; |
1329 | 14.6k | } magick_plane_t; |
1330 | | |
1331 | 14.6k | const char * |
1332 | 14.6k | heif_channel_str; |
1333 | | |
1334 | 14.6k | const char * |
1335 | 14.6k | heif_chroma_format_str; |
1336 | | |
1337 | 14.6k | enum heif_chroma |
1338 | 14.6k | heif_chroma_format; |
1339 | | |
1340 | 14.6k | heif_chroma_format = heif_image_get_chroma_format(heif_image); |
1341 | 14.6k | heif_chroma_format_str = HEIF_chroma_to_string(heif_chroma_format); |
1342 | | |
1343 | 14.6k | switch (heif_image_get_colorspace(heif_image)) |
1344 | 14.6k | { |
1345 | 0 | case heif_colorspace_YCbCr: |
1346 | 0 | { |
1347 | | /* |
1348 | | Decode YCbCr(A) pixels |
1349 | | */ |
1350 | |
|
1351 | 0 | static const enum heif_channel channels_YCbCrA[4] = |
1352 | 0 | { heif_channel_Y, heif_channel_Cb, heif_channel_Cr, heif_channel_Alpha }; |
1353 | |
|
1354 | 0 | magick_plane_t |
1355 | 0 | planes[4]; |
1356 | |
|
1357 | 0 | magick_plane_t |
1358 | 0 | line[4]; |
1359 | |
|
1360 | 0 | size_t |
1361 | 0 | row_stride[4]; |
1362 | |
|
1363 | 0 | int |
1364 | 0 | channel_bits_per_pixel[4], |
1365 | 0 | channel_bits_per_pixel_range[4]; |
1366 | |
|
1367 | 0 | unsigned long |
1368 | 0 | channel_max_value_given_bits[4]; |
1369 | |
|
1370 | 0 | unsigned int |
1371 | 0 | channel_index, |
1372 | 0 | num_channels; |
1373 | |
|
1374 | 0 | enum heif_channel heif_channel; |
1375 | |
|
1376 | 0 | num_channels = image->matte ? 4 : 3; |
1377 | |
|
1378 | 0 | if (image->logging) |
1379 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1380 | 0 | "Decoding %u planes for YCbCr colorspace (chroma %s)", |
1381 | 0 | num_channels, heif_chroma_format_str); |
1382 | |
|
1383 | 0 | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1384 | 0 | { |
1385 | 0 | heif_channel = channels_YCbCrA[channel_index]; |
1386 | 0 | heif_channel_str = HEIF_channel_to_string(heif_channel); |
1387 | 0 | planes[channel_index].u8 = heif_image_get_plane_readonly2(heif_image, |
1388 | 0 | heif_channel, |
1389 | 0 | &row_stride[channel_index]); |
1390 | 0 | if (planes[channel_index].u8 == (const uint8_t*) NULL) |
1391 | 0 | { |
1392 | 0 | if (image->logging) |
1393 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1394 | 0 | "heif_image_get_plane_readonly2() returned NULL" |
1395 | 0 | " for channel index %u (channel %s)!", |
1396 | 0 | channel_index,heif_channel_str); |
1397 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, |
1398 | 0 | AnErrorHasOccurredReadingFromFile, image); |
1399 | 0 | } |
1400 | 0 | channel_bits_per_pixel[channel_index] = heif_image_get_bits_per_pixel(heif_image, |
1401 | 0 | heif_channel); |
1402 | 0 | channel_bits_per_pixel_range[channel_index] = heif_image_get_bits_per_pixel_range(heif_image, |
1403 | 0 | heif_channel); |
1404 | 0 | channel_max_value_given_bits[channel_index] = MaxValueGivenBits(channel_bits_per_pixel_range[channel_index]); |
1405 | 0 | if ((int) image->depth < channel_bits_per_pixel_range[channel_index]) |
1406 | 0 | image->depth=(unsigned int) channel_bits_per_pixel_range[channel_index]; |
1407 | 0 | if (image->logging) |
1408 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1409 | 0 | " channel %s row stride %zu (%d bits in %d bit quantum)", |
1410 | 0 | heif_channel_str, row_stride[channel_index], |
1411 | 0 | channel_bits_per_pixel_range[channel_index], |
1412 | 0 | channel_bits_per_pixel[channel_index]); |
1413 | 0 | } |
1414 | | |
1415 | | /* Transfer pixels to image, using row stride to find start of each row. */ |
1416 | 0 | if (image->logging) |
1417 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1418 | 0 | "Transferring pixels to magick image..."); |
1419 | 0 | for (y=0; y < (long)image->rows; y++) |
1420 | 0 | { |
1421 | 0 | q=SetImagePixelsEx(image,0,y,image->columns,1,exception); |
1422 | 0 | if (q == (PixelPacket *) NULL) |
1423 | 0 | { |
1424 | 0 | HEIFReadImageFrameCleanup(); |
1425 | 0 | return MagickFail; |
1426 | 0 | } |
1427 | 0 | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1428 | 0 | line[channel_index].u8=planes[channel_index].u8+y*row_stride[channel_index]; |
1429 | 0 | for (x=0; x < (long)image->columns; x++) |
1430 | 0 | { |
1431 | 0 | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1432 | 0 | { |
1433 | 0 | uint32_t |
1434 | 0 | sample = 0; |
1435 | |
|
1436 | 0 | Quantum |
1437 | 0 | quantum=0; |
1438 | | |
1439 | | /* |
1440 | | Get sample |
1441 | | */ |
1442 | 0 | switch(channel_bits_per_pixel[channel_index]) |
1443 | 0 | { |
1444 | 0 | case 8: |
1445 | 0 | sample = *line[channel_index].u8; |
1446 | 0 | line[channel_index].u8++; |
1447 | 0 | break; |
1448 | 0 | case 16: |
1449 | 0 | sample = *line[channel_index].u16; |
1450 | 0 | line[channel_index].u16++; |
1451 | 0 | break; |
1452 | 0 | } |
1453 | | |
1454 | | /* |
1455 | | Scale sample to quantum |
1456 | | */ |
1457 | 0 | if (channel_bits_per_pixel_range[channel_index] == 8) |
1458 | 0 | { |
1459 | 0 | quantum=ScaleCharToQuantum(sample); |
1460 | 0 | } |
1461 | 0 | else if (channel_bits_per_pixel_range[channel_index] < 8) |
1462 | 0 | { |
1463 | 0 | quantum=ScaleAnyToQuantum(sample,channel_max_value_given_bits[channel_index]); |
1464 | 0 | } |
1465 | 0 | else if (channel_bits_per_pixel_range[channel_index] == 16) |
1466 | 0 | { |
1467 | 0 | quantum=ScaleShortToQuantum(sample); |
1468 | 0 | } |
1469 | 0 | else if (channel_bits_per_pixel_range[channel_index] < 16) |
1470 | 0 | { |
1471 | 0 | quantum=ScaleAnyToQuantum(sample,channel_max_value_given_bits[channel_index]); |
1472 | 0 | } |
1473 | | |
1474 | | /* |
1475 | | Assign quantum to PixelPacket |
1476 | | */ |
1477 | 0 | switch (channel_index) |
1478 | 0 | { |
1479 | 0 | case 0: |
1480 | 0 | SetYSample(q,quantum); |
1481 | 0 | if (num_channels != 4) |
1482 | 0 | SetOpacitySample(q,OpaqueOpacity); |
1483 | 0 | break; |
1484 | 0 | case 1: |
1485 | 0 | SetCbSample(q,quantum); |
1486 | 0 | break; |
1487 | 0 | case 2: |
1488 | 0 | SetCrSample(q,quantum); |
1489 | 0 | break; |
1490 | 0 | case 3: |
1491 | 0 | SetOpacitySample(q,MaxRGB-quantum); |
1492 | 0 | break; |
1493 | 0 | } |
1494 | 0 | } |
1495 | 0 | q++; |
1496 | 0 | } |
1497 | 0 | if (!SyncImagePixelsEx(image,exception)) |
1498 | 0 | { |
1499 | 0 | HEIFReadImageFrameCleanup(); |
1500 | 0 | return MagickFail; |
1501 | 0 | } |
1502 | 0 | } |
1503 | 0 | if (image->logging) |
1504 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1505 | 0 | " done!"); |
1506 | 0 | } |
1507 | 0 | break; |
1508 | 14.2k | case heif_colorspace_RGB: |
1509 | 14.2k | { |
1510 | 14.2k | if (heif_chroma_444 == heif_chroma_format) |
1511 | 14.2k | { |
1512 | | /* |
1513 | | Decode RGB(A) planar pixels |
1514 | | */ |
1515 | 14.2k | static const enum heif_channel |
1516 | 14.2k | channels_RGBA[4] = |
1517 | 14.2k | { heif_channel_R, heif_channel_G, heif_channel_B, heif_channel_Alpha }; |
1518 | | |
1519 | 14.2k | magick_plane_t |
1520 | 14.2k | planes[4]; |
1521 | | |
1522 | 14.2k | magick_plane_t |
1523 | 14.2k | line[4]; |
1524 | | |
1525 | 14.2k | size_t |
1526 | 14.2k | row_stride[4]; |
1527 | | |
1528 | 14.2k | int |
1529 | 14.2k | channel_bits_per_pixel[4], |
1530 | 14.2k | channel_bits_per_pixel_range[4]; |
1531 | | |
1532 | 14.2k | unsigned long |
1533 | 14.2k | channel_max_value_given_bits[4]; |
1534 | | |
1535 | 14.2k | unsigned int |
1536 | 14.2k | channel_index, |
1537 | 14.2k | num_channels=0; |
1538 | | |
1539 | 14.2k | enum heif_channel |
1540 | 14.2k | heif_channel; |
1541 | | |
1542 | 14.2k | num_channels = image->matte ? 4 : 3; |
1543 | | |
1544 | 14.2k | if (image->logging) |
1545 | 14.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1546 | 14.2k | "Decoding %u planes for RGB colorspace (chroma %s)", |
1547 | 14.2k | num_channels, heif_chroma_format_str); |
1548 | | |
1549 | 56.9k | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1550 | 42.7k | { |
1551 | 42.7k | heif_channel = channels_RGBA[channel_index]; |
1552 | 42.7k | heif_channel_str = HEIF_channel_to_string(heif_channel); |
1553 | 42.7k | planes[channel_index].u8 = heif_image_get_plane_readonly2(heif_image, |
1554 | 42.7k | heif_channel, |
1555 | 42.7k | &row_stride[channel_index]); |
1556 | 42.7k | if (planes[channel_index].u8 == (const uint8_t*) NULL) |
1557 | 0 | { |
1558 | 0 | if (image->logging) |
1559 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1560 | 0 | "heif_image_get_plane_readonly2() returned NULL" |
1561 | 0 | " for channel index %u (channel %s)!", |
1562 | 0 | channel_index,heif_channel_str); |
1563 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, |
1564 | 0 | AnErrorHasOccurredReadingFromFile, image); |
1565 | 0 | } |
1566 | 42.7k | channel_bits_per_pixel[channel_index] = heif_image_get_bits_per_pixel(heif_image, |
1567 | 42.7k | heif_channel); |
1568 | 42.7k | channel_bits_per_pixel_range[channel_index] = heif_image_get_bits_per_pixel_range(heif_image, |
1569 | 42.7k | heif_channel); |
1570 | 42.7k | channel_max_value_given_bits[channel_index] = MaxValueGivenBits(channel_bits_per_pixel_range[channel_index]); |
1571 | 42.7k | if (0 == channel_max_value_given_bits[channel_index]) |
1572 | 0 | { |
1573 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1574 | 0 | "MaxValueGivenBits(%d) returned 0!", |
1575 | 0 | channel_bits_per_pixel_range[channel_index]); |
1576 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, |
1577 | 0 | AnErrorHasOccurredReadingFromFile, image); |
1578 | 0 | } |
1579 | 42.7k | if ((int) image->depth < channel_bits_per_pixel_range[channel_index]) |
1580 | 5.42k | image->depth=(unsigned int) channel_bits_per_pixel_range[channel_index]; |
1581 | 42.7k | if (image->logging) |
1582 | 42.7k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1583 | 42.7k | " channel %s row stride %zu (%d bits in %d bit quantum)", |
1584 | 42.7k | heif_channel_str, row_stride[channel_index], |
1585 | 42.7k | channel_bits_per_pixel_range[channel_index], |
1586 | 42.7k | channel_bits_per_pixel[channel_index]); |
1587 | 42.7k | } |
1588 | | |
1589 | | /* Transfer pixels to image, using row stride to find start of each row. */ |
1590 | 14.2k | if (image->logging) |
1591 | 14.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1592 | 14.2k | "Transferring pixels to magick image..."); |
1593 | 3.03M | for (y=0; y < (long)image->rows; y++) |
1594 | 3.02M | { |
1595 | 3.02M | q=SetImagePixelsEx(image,0,y,image->columns,1,exception); |
1596 | 3.02M | if (q == (PixelPacket *) NULL) |
1597 | 4 | { |
1598 | 4 | HEIFReadImageFrameCleanup(); |
1599 | 4 | return MagickFail; |
1600 | 4 | } |
1601 | 12.0M | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1602 | 9.07M | line[channel_index].u8=planes[channel_index].u8+y*row_stride[channel_index]; |
1603 | 1.87G | for (x=0; x < (long)image->columns; x++) |
1604 | 1.87G | { |
1605 | 7.48G | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1606 | 5.61G | { |
1607 | 5.61G | uint32_t |
1608 | 5.61G | sample = 0; |
1609 | | |
1610 | 5.61G | Quantum |
1611 | 5.61G | quantum=0; |
1612 | | |
1613 | | /* |
1614 | | Get sample |
1615 | | */ |
1616 | 5.61G | switch(channel_bits_per_pixel[channel_index]) |
1617 | 5.61G | { |
1618 | 3.76G | case 8: |
1619 | 3.76G | sample = *line[channel_index].u8; |
1620 | 3.76G | line[channel_index].u8++; |
1621 | 3.76G | break; |
1622 | 1.84G | case 16: |
1623 | 1.84G | sample = *line[channel_index].u16; |
1624 | 1.84G | line[channel_index].u16++; |
1625 | 1.84G | break; |
1626 | 5.61G | } |
1627 | | |
1628 | | /* |
1629 | | Scale sample to quantum |
1630 | | */ |
1631 | 5.61G | if (channel_bits_per_pixel_range[channel_index] == 8) |
1632 | 3.76G | { |
1633 | 3.76G | quantum=ScaleCharToQuantum(sample); |
1634 | 3.76G | } |
1635 | 1.84G | else if (channel_bits_per_pixel_range[channel_index] < 8) |
1636 | 21.6k | { |
1637 | 21.6k | quantum=ScaleAnyToQuantum(sample,channel_max_value_given_bits[channel_index]); |
1638 | 21.6k | } |
1639 | 1.84G | else if (channel_bits_per_pixel_range[channel_index] == 16) |
1640 | 71.9k | { |
1641 | 71.9k | quantum=ScaleShortToQuantum(sample); |
1642 | 71.9k | } |
1643 | 1.84G | else if (channel_bits_per_pixel_range[channel_index] < 16) |
1644 | 1.84G | { |
1645 | 1.84G | quantum=ScaleAnyToQuantum(sample,channel_max_value_given_bits[channel_index]); |
1646 | 1.84G | } |
1647 | | |
1648 | | /* |
1649 | | Assign quantum to PixelPacket |
1650 | | */ |
1651 | 5.61G | switch (channel_index) |
1652 | 5.61G | { |
1653 | 1.87G | case 0: |
1654 | 1.87G | SetRedSample(q,quantum); |
1655 | 1.87G | if (num_channels != 4) |
1656 | 1.87G | SetOpacitySample(q,OpaqueOpacity); |
1657 | 1.87G | break; |
1658 | 1.87G | case 1: |
1659 | 1.87G | SetGreenSample(q,quantum); |
1660 | 1.87G | break; |
1661 | 1.87G | case 2: |
1662 | 1.87G | SetBlueSample(q,quantum); |
1663 | 1.87G | break; |
1664 | 95.4k | case 3: |
1665 | 95.4k | SetOpacitySample(q,MaxRGB-quantum); |
1666 | 95.4k | break; |
1667 | 5.61G | } |
1668 | 5.61G | } |
1669 | 1.87G | q++; |
1670 | 1.87G | } |
1671 | 3.02M | if (!SyncImagePixelsEx(image,exception)) |
1672 | 0 | { |
1673 | 0 | HEIFReadImageFrameCleanup(); |
1674 | 0 | return MagickFail; |
1675 | 0 | } |
1676 | 3.02M | } |
1677 | 14.2k | if (image->logging) |
1678 | 14.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1679 | 14.2k | " done!"); |
1680 | 14.2k | } /* heif_chroma_444 */ |
1681 | 0 | else |
1682 | 0 | { |
1683 | | /* |
1684 | | Decode RGB(A) interleaved pixels |
1685 | | |
1686 | | This requires set-up in advance to request the desired RGB(A) sub-format. |
1687 | | |
1688 | | heif_chroma_interleaved_RGB |
1689 | | heif_chroma_interleaved_RGBA |
1690 | | heif_chroma_interleaved_RRGGBB_BE |
1691 | | heif_chroma_interleaved_RRGGBBAA_BE |
1692 | | heif_chroma_interleaved_RRGGBB_LE |
1693 | | heif_chroma_interleaved_RRGGBBAA_LE |
1694 | | */ |
1695 | |
|
1696 | 0 | typedef union |
1697 | 0 | { |
1698 | 0 | const uint8_t *u8; |
1699 | 0 | const uint16_t *u16; |
1700 | 0 | } magick_pixels_t; |
1701 | |
|
1702 | 0 | magick_pixels_t |
1703 | 0 | pixels; |
1704 | |
|
1705 | 0 | size_t |
1706 | 0 | row_stride; |
1707 | |
|
1708 | 0 | unsigned long |
1709 | 0 | max_value_given_bits, |
1710 | 0 | x, |
1711 | 0 | y; |
1712 | |
|
1713 | 0 | int |
1714 | 0 | bits_per_pixel, |
1715 | 0 | bits_per_pixel_range; |
1716 | |
|
1717 | 0 | enum heif_channel |
1718 | 0 | heif_channel; |
1719 | |
|
1720 | 0 | const MagickBool |
1721 | 0 | matte = image->matte; |
1722 | |
|
1723 | 0 | if (image->logging) |
1724 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1725 | 0 | "Chroma format: %s", heif_chroma_format_str); |
1726 | |
|
1727 | 0 | heif_channel = heif_channel_interleaved; |
1728 | |
|
1729 | 0 | heif_channel_str = HEIF_channel_to_string(heif_channel); |
1730 | |
|
1731 | 0 | bits_per_pixel = heif_image_get_bits_per_pixel(heif_image, |
1732 | 0 | heif_channel); |
1733 | 0 | bits_per_pixel_range = heif_image_get_bits_per_pixel_range(heif_image, |
1734 | 0 | heif_channel); |
1735 | |
|
1736 | 0 | max_value_given_bits = MaxValueGivenBits(bits_per_pixel_range); |
1737 | |
|
1738 | 0 | if (0 == max_value_given_bits) |
1739 | 0 | { |
1740 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1741 | 0 | "MaxValueGivenBits(%d) returned 0!", |
1742 | 0 | bits_per_pixel_range); |
1743 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, |
1744 | 0 | AnErrorHasOccurredReadingFromFile, image); |
1745 | 0 | } |
1746 | | |
1747 | 0 | pixels.u8 = heif_image_get_plane_readonly2(heif_image, |
1748 | 0 | heif_channel, |
1749 | 0 | &row_stride); |
1750 | |
|
1751 | 0 | if (pixels.u8 == (const uint8_t*) NULL) |
1752 | 0 | { |
1753 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1754 | 0 | "heif_image_get_plane_readonly2() returned NULL" |
1755 | 0 | " (channel %s)!",heif_channel_str); |
1756 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, |
1757 | 0 | AnErrorHasOccurredReadingFromFile, image); |
1758 | 0 | } |
1759 | | |
1760 | | /* interleaved row stride 1536 (8 bits in 24 bit packet) */ |
1761 | | /* interleaved row stride 3072 (16 bit samples in 48 bit packet) */ |
1762 | 0 | if (image->logging) |
1763 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1764 | 0 | " %s row stride %zu (%d bit samples in %d bit packet)", |
1765 | 0 | heif_channel_str, row_stride, |
1766 | 0 | bits_per_pixel_range, |
1767 | 0 | bits_per_pixel); |
1768 | |
|
1769 | 0 | if (bits_per_pixel_range <= 8) |
1770 | 0 | { |
1771 | 0 | const uint8_t |
1772 | 0 | *p; |
1773 | |
|
1774 | 0 | for (y=0; y < image->rows; y++) |
1775 | 0 | { |
1776 | 0 | p=pixels.u8+y*row_stride; |
1777 | 0 | q=SetImagePixelsEx(image,0,y,image->columns,1,exception); |
1778 | 0 | if (q == (PixelPacket *) NULL) |
1779 | 0 | { |
1780 | 0 | HEIFReadImageFrameCleanup(); |
1781 | 0 | return MagickFail; |
1782 | 0 | } |
1783 | 0 | if (bits_per_pixel_range == 8) |
1784 | 0 | { |
1785 | 0 | for (x=0; x < image->columns; x++) |
1786 | 0 | { |
1787 | 0 | SetRedSample(q,ScaleCharToQuantum(*p++)); |
1788 | 0 | SetGreenSample(q,ScaleCharToQuantum(*p++)); |
1789 | 0 | SetBlueSample(q,ScaleCharToQuantum(*p++)); |
1790 | 0 | if (matte) |
1791 | 0 | SetOpacitySample(q,MaxRGB-ScaleCharToQuantum(*p++)); |
1792 | 0 | else |
1793 | 0 | SetOpacitySample(q,OpaqueOpacity); |
1794 | 0 | q++; |
1795 | 0 | } |
1796 | 0 | } |
1797 | 0 | else |
1798 | 0 | { |
1799 | | /* Maybe this case will never happen */ |
1800 | 0 | for (x=0; x < image->columns; x++) |
1801 | 0 | { |
1802 | 0 | SetRedSample(q,ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1803 | 0 | SetGreenSample(q,ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1804 | 0 | SetBlueSample(q,ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1805 | 0 | if (matte) |
1806 | 0 | SetOpacitySample(q,MaxRGB-ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1807 | 0 | else |
1808 | 0 | SetOpacitySample(q,OpaqueOpacity); |
1809 | 0 | q++; |
1810 | 0 | } |
1811 | 0 | } |
1812 | 0 | if (!SyncImagePixelsEx(image,exception)) |
1813 | 0 | { |
1814 | 0 | HEIFReadImageFrameCleanup(); |
1815 | 0 | return MagickFail; |
1816 | 0 | } |
1817 | 0 | } |
1818 | 0 | } |
1819 | 0 | else if (bits_per_pixel_range <= 16) |
1820 | 0 | { |
1821 | 0 | const uint16_t |
1822 | 0 | *p; |
1823 | |
|
1824 | 0 | const size_t |
1825 | 0 | row_stride_uint16 = row_stride/sizeof(uint16_t); |
1826 | |
|
1827 | 0 | for (y=0; y < image->rows; y++) |
1828 | 0 | { |
1829 | 0 | p=pixels.u16+y*row_stride_uint16; |
1830 | 0 | q=SetImagePixelsEx(image,0,y,image->columns,1,exception); |
1831 | 0 | if (q == (PixelPacket *) NULL) |
1832 | 0 | { |
1833 | 0 | HEIFReadImageFrameCleanup(); |
1834 | 0 | return MagickFail; |
1835 | 0 | } |
1836 | 0 | if (bits_per_pixel_range == 16) |
1837 | 0 | { |
1838 | 0 | for (x=0; x < image->columns; x++) |
1839 | 0 | { |
1840 | 0 | SetRedSample(q,ScaleShortToQuantum(*p++)); |
1841 | 0 | SetGreenSample(q,ScaleShortToQuantum(*p++)); |
1842 | 0 | SetBlueSample(q,ScaleShortToQuantum(*p++)); |
1843 | 0 | if (matte) |
1844 | 0 | SetOpacitySample(q,MaxRGB-ScaleShortToQuantum(*p++)); |
1845 | 0 | else |
1846 | 0 | SetOpacitySample(q,OpaqueOpacity); |
1847 | 0 | q++; |
1848 | 0 | } |
1849 | 0 | } |
1850 | 0 | else |
1851 | 0 | { |
1852 | 0 | for (x=0; x < image->columns; x++) |
1853 | 0 | { |
1854 | 0 | SetRedSample(q,ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1855 | 0 | SetGreenSample(q,ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1856 | 0 | SetBlueSample(q,ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1857 | 0 | if (matte) |
1858 | 0 | SetOpacitySample(q,MaxRGB-ScaleAnyToQuantum(*p++,max_value_given_bits)); |
1859 | 0 | else |
1860 | 0 | SetOpacitySample(q,OpaqueOpacity); |
1861 | 0 | q++; |
1862 | 0 | } |
1863 | 0 | } |
1864 | 0 | if (!SyncImagePixelsEx(image,exception)) |
1865 | 0 | { |
1866 | 0 | HEIFReadImageFrameCleanup(); |
1867 | 0 | return MagickFail; |
1868 | 0 | } |
1869 | 0 | } |
1870 | 0 | } |
1871 | 0 | } |
1872 | 14.2k | break; |
1873 | 14.2k | } /* case heif_colorspace_RGB */ |
1874 | 14.2k | case heif_colorspace_monochrome: |
1875 | 378 | { |
1876 | | /* |
1877 | | Decode Y(A) pixels |
1878 | | */ |
1879 | 378 | static const enum heif_channel channels_YA[4] = |
1880 | 378 | { heif_channel_Y, heif_channel_Alpha }; |
1881 | | |
1882 | 378 | magick_plane_t |
1883 | 378 | planes[2]; |
1884 | | |
1885 | 378 | magick_plane_t |
1886 | 378 | line[2]; |
1887 | | |
1888 | 378 | size_t |
1889 | 378 | row_stride[2]; |
1890 | | |
1891 | 378 | int |
1892 | 378 | channel_bits_per_pixel[2]; |
1893 | | |
1894 | 378 | int |
1895 | 378 | channel_bits_per_pixel_range[2]; |
1896 | | |
1897 | 378 | unsigned long |
1898 | 378 | channel_max_value_given_bits[2]; |
1899 | | |
1900 | 378 | unsigned int |
1901 | 378 | channel_index, |
1902 | 378 | num_channels; |
1903 | | |
1904 | 378 | enum heif_channel heif_channel; |
1905 | | |
1906 | 378 | num_channels = image->matte ? 2 : 1; |
1907 | | |
1908 | 378 | assert(heif_image_get_chroma_format(heif_image) == heif_chroma_monochrome); |
1909 | | |
1910 | 378 | if (image->logging) |
1911 | 378 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1912 | 378 | "Decoding %u plane%s for monochrome colorspace (chroma %s)", |
1913 | 378 | num_channels, num_channels > 1 ? "s" : "", heif_chroma_format_str); |
1914 | | |
1915 | 757 | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1916 | 379 | { |
1917 | 379 | heif_channel = channels_YA[channel_index]; |
1918 | 379 | heif_channel_str = HEIF_channel_to_string(heif_channel); |
1919 | 379 | planes[channel_index].u8 = heif_image_get_plane_readonly2(heif_image, |
1920 | 379 | heif_channel, |
1921 | 379 | &row_stride[channel_index]); |
1922 | 379 | if (planes[channel_index].u8 == (const uint8_t*) NULL) |
1923 | 0 | { |
1924 | 0 | if (image->logging) |
1925 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1926 | 0 | "heif_image_get_plane_readonly2() returned NULL" |
1927 | 0 | " for channel index %u (channel %s)!", |
1928 | 0 | channel_index,heif_channel_str); |
1929 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, |
1930 | 0 | AnErrorHasOccurredReadingFromFile, image); |
1931 | 0 | } |
1932 | 379 | channel_bits_per_pixel[channel_index] = heif_image_get_bits_per_pixel(heif_image, |
1933 | 379 | heif_channel); |
1934 | 379 | channel_bits_per_pixel_range[channel_index] = heif_image_get_bits_per_pixel_range(heif_image, |
1935 | 379 | heif_channel); |
1936 | 379 | channel_max_value_given_bits[channel_index] = MaxValueGivenBits(channel_bits_per_pixel_range[channel_index]); |
1937 | 379 | if (0 == channel_max_value_given_bits[channel_index]) |
1938 | 0 | { |
1939 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1940 | 0 | "MaxValueGivenBits(%d) returned 0!", |
1941 | 0 | channel_bits_per_pixel_range[channel_index]); |
1942 | 0 | ThrowHEIFThrowReadImageFrameException(CorruptImageError, |
1943 | 0 | AnErrorHasOccurredReadingFromFile, image); |
1944 | 0 | } |
1945 | 379 | if ((int) image->depth < channel_bits_per_pixel_range[channel_index]) |
1946 | 25 | image->depth=(unsigned int) channel_bits_per_pixel_range[channel_index]; |
1947 | 379 | heif_channel_str = HEIF_channel_to_string(heif_channel); |
1948 | 379 | if (image->logging) |
1949 | 379 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1950 | 379 | " channel %s row stride %zu (%d bits in %d bit quantum)", |
1951 | 379 | heif_channel_str, row_stride[channel_index], |
1952 | 379 | channel_bits_per_pixel_range[channel_index], |
1953 | 379 | channel_bits_per_pixel[channel_index]); |
1954 | 379 | } |
1955 | | |
1956 | | /* Transfer pixels to image, using row stride to find start of each row. */ |
1957 | 378 | if (image->logging) |
1958 | 378 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1959 | 378 | "Transferring pixels to magick image..."); |
1960 | 97.8k | for (y=0; y < (long)image->rows; y++) |
1961 | 97.4k | { |
1962 | 97.4k | q=SetImagePixelsEx(image,0,y,image->columns,1,exception); |
1963 | 97.4k | if (q == (PixelPacket *) NULL) |
1964 | 0 | { |
1965 | 0 | HEIFReadImageFrameCleanup(); |
1966 | 0 | return MagickFail; |
1967 | 0 | } |
1968 | 194k | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1969 | 97.4k | line[channel_index].u8=planes[channel_index].u8+y*row_stride[channel_index]; |
1970 | 26.3M | for (x=0; x < (long)image->columns; x++) |
1971 | 26.3M | { |
1972 | 52.6M | for (channel_index = 0; channel_index < num_channels; channel_index++) |
1973 | 26.3M | { |
1974 | 26.3M | uint32_t |
1975 | 26.3M | sample = 0; |
1976 | | |
1977 | 26.3M | Quantum |
1978 | 26.3M | quantum=0; |
1979 | | |
1980 | | /* |
1981 | | Get sample |
1982 | | */ |
1983 | 26.3M | switch(channel_bits_per_pixel[channel_index]) |
1984 | 26.3M | { |
1985 | 16.7M | case 8: |
1986 | 16.7M | sample = *line[channel_index].u8; |
1987 | 16.7M | line[channel_index].u8++; |
1988 | 16.7M | break; |
1989 | 9.57M | case 16: |
1990 | 9.57M | sample = *line[channel_index].u16; |
1991 | 9.57M | line[channel_index].u16++; |
1992 | 9.57M | break; |
1993 | 26.3M | } |
1994 | | |
1995 | | /* |
1996 | | Scale sample to quantum |
1997 | | */ |
1998 | 26.3M | if (channel_bits_per_pixel_range[channel_index] == 8) |
1999 | 16.7M | { |
2000 | 16.7M | quantum=ScaleCharToQuantum(sample); |
2001 | 16.7M | } |
2002 | 9.57M | else if (channel_bits_per_pixel_range[channel_index] < 8) |
2003 | 600 | { |
2004 | 600 | quantum=ScaleAnyToQuantum(sample,channel_max_value_given_bits[channel_index]); |
2005 | 600 | } |
2006 | 9.57M | else if (channel_bits_per_pixel_range[channel_index] == 16) |
2007 | 0 | { |
2008 | 0 | quantum=ScaleShortToQuantum(sample); |
2009 | 0 | } |
2010 | 9.57M | else if (channel_bits_per_pixel_range[channel_index] < 16) |
2011 | 9.57M | { |
2012 | 9.57M | quantum=ScaleAnyToQuantum(sample,channel_max_value_given_bits[channel_index]); |
2013 | 9.57M | } |
2014 | | |
2015 | | /* |
2016 | | Assign quantum to PixelPacket |
2017 | | */ |
2018 | 26.3M | switch (channel_index) |
2019 | 26.3M | { |
2020 | 26.3M | case 0: |
2021 | 26.3M | SetGraySample(q,quantum); |
2022 | 26.3M | if (num_channels != 2) |
2023 | 26.2M | SetOpacitySample(q,OpaqueOpacity); |
2024 | 26.3M | break; |
2025 | 1.02k | case 1: |
2026 | 1.02k | SetOpacitySample(q,MaxRGB-quantum); |
2027 | 1.02k | break; |
2028 | 26.3M | } |
2029 | 26.3M | } |
2030 | 26.3M | q++; |
2031 | 26.3M | } |
2032 | 97.4k | if (!SyncImagePixelsEx(image,exception)) |
2033 | 0 | { |
2034 | 0 | HEIFReadImageFrameCleanup(); |
2035 | 0 | return MagickFail; |
2036 | 0 | } |
2037 | 97.4k | } |
2038 | 378 | if (image->logging) |
2039 | 378 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2040 | 378 | " done!"); |
2041 | 378 | } |
2042 | 0 | break; |
2043 | 0 | case heif_colorspace_nonvisual: |
2044 | | /* No visual channels */ |
2045 | 0 | #if LIBHEIF_HAVE_VERSION(1,21,3) |
2046 | | /* Git commit cb6e524 added heif_colorspace_filter_array on 2/24/26 */ |
2047 | 1 | case heif_colorspace_filter_array: |
2048 | 1 | #endif /* if LIBHEIF_HAVE_VERSION(1,21,3) */ |
2049 | 1 | default: |
2050 | | /* Should never reach here. */ |
2051 | 1 | break; |
2052 | 14.6k | } |
2053 | 14.6k | } |
2054 | | |
2055 | 14.6k | HEIFReadImageFrameCleanup(); |
2056 | | |
2057 | 14.6k | if (image->logging) |
2058 | 14.6k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2059 | 14.6k | "Image depth: %u", image->depth); |
2060 | | |
2061 | 14.6k | return MagickPass; |
2062 | 14.6k | } |
2063 | | |
2064 | | static Image *ReadHEIFImage(const ImageInfo *image_info, |
2065 | | ExceptionInfo *exception) |
2066 | 97.2k | { |
2067 | 97.2k | Image |
2068 | 97.2k | *image; |
2069 | | |
2070 | 97.2k | struct heif_context |
2071 | 97.2k | *heif = NULL; |
2072 | | |
2073 | 97.2k | struct heif_image_handle |
2074 | 97.2k | *heif_image_handle = NULL; |
2075 | | |
2076 | 97.2k | struct heif_image |
2077 | 97.2k | *heif_image = NULL; |
2078 | | |
2079 | 97.2k | struct heif_decoding_options |
2080 | 97.2k | *decode_options = NULL; |
2081 | | |
2082 | 97.2k | const char |
2083 | 97.2k | *value; |
2084 | | |
2085 | 97.2k | unsigned char |
2086 | 97.2k | *in_buf = NULL; |
2087 | | |
2088 | 97.2k | heif_item_id |
2089 | 97.2k | *item_ids = NULL; |
2090 | | |
2091 | 97.2k | const char * |
2092 | 97.2k | magick; |
2093 | | |
2094 | 97.2k | size_t |
2095 | 97.2k | in_len = 0; |
2096 | | |
2097 | 97.2k | struct heif_error |
2098 | 97.2k | heif_status; |
2099 | | |
2100 | 97.2k | int |
2101 | 97.2k | #if defined(HEIF_SUPPORT_SEQUENCES) && HEIF_SUPPORT_SEQUENCES |
2102 | 97.2k | has_sequence, |
2103 | 97.2k | #endif /* if defined(HEIF_SUPPORT_SEQUENCES) && HEIF_SUPPORT_SEQUENCES */ |
2104 | 97.2k | number_of_top_level_images; |
2105 | | |
2106 | 97.2k | char |
2107 | 97.2k | fourcc[5]; |
2108 | | |
2109 | 97.2k | heif_item_id |
2110 | 97.2k | primary_image_id; |
2111 | | |
2112 | | #if HEIF_ENABLE_PROGRESS_MONITOR |
2113 | | ProgressUserData |
2114 | | progress_user_data; |
2115 | | #endif |
2116 | | |
2117 | 97.2k | MagickBool |
2118 | 97.2k | in_buf_allocated=MagickFalse; |
2119 | | |
2120 | 97.2k | MagickPassFail |
2121 | 97.2k | status; |
2122 | | |
2123 | 97.2k | assert(image_info != (const ImageInfo *) NULL); |
2124 | 97.2k | assert(image_info->signature == MagickSignature); |
2125 | 97.2k | assert(exception != (ExceptionInfo *) NULL); |
2126 | 97.2k | assert(exception->signature == MagickSignature); |
2127 | | |
2128 | 97.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2129 | 97.2k | "ImageInfo magick = %s", image_info->magick); |
2130 | | /* |
2131 | | Open image file. |
2132 | | */ |
2133 | 97.2k | image=AllocateImage(image_info); |
2134 | 97.2k | if (image == (Image *) NULL) |
2135 | 97.2k | ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image); |
2136 | | |
2137 | 97.2k | if (OpenBlob(image_info,image,ReadBinaryBlobMode,exception) == MagickFail) |
2138 | 97.2k | ThrowReaderException(FileOpenError,UnableToOpenFile,image); |
2139 | | |
2140 | 97.2k | if (!heif_initialized) |
2141 | 9 | { |
2142 | | /* heif_init() accepts a 'struct heif_init_params *' argument */ |
2143 | 9 | heif_init((struct heif_init_params *) NULL); |
2144 | 9 | heif_initialized = MagickTrue; |
2145 | 9 | } |
2146 | | |
2147 | | |
2148 | | /* Add decoding options support */ |
2149 | 97.2k | decode_options = heif_decoding_options_alloc(); |
2150 | 97.2k | if (decode_options == (struct heif_decoding_options*) NULL) |
2151 | 97.2k | ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image); |
2152 | | |
2153 | 97.2k | decode_options->ignore_transformations = image_info->ping ? MagickTrue : MagickFalse; |
2154 | 97.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2155 | 97.2k | "Initial ignore_transformations = %s", |
2156 | 97.2k | decode_options->ignore_transformations ? "True" : "False"); |
2157 | | #if HEIF_ENABLE_PROGRESS_MONITOR |
2158 | | progress_user_data.exception = exception; |
2159 | | progress_user_data.image = image; |
2160 | | progress_user_data.max_progress = 0; |
2161 | | progress_user_data.progress = 0; |
2162 | | #endif |
2163 | | |
2164 | | #if HEIF_ENABLE_PROGRESS_MONITOR |
2165 | | decode_options->start_progress = start_progress; |
2166 | | decode_options->on_progress = on_progress; |
2167 | | decode_options->end_progress = end_progress; |
2168 | | decode_options->progress_user_data = &progress_user_data; |
2169 | | #endif /* if HEIF_ENABLE_PROGRESS_MONITOR */ |
2170 | | |
2171 | | /* version 2 decode options */ |
2172 | | |
2173 | | /* Let libheif do the scaling from deep images if QuantumDepth == 8 */ |
2174 | | #if QuantumDepth == 8 |
2175 | | decode_options->convert_hdr_to_8bit = 1; |
2176 | | #endif /* if QuantumDepth == 8 */ |
2177 | | |
2178 | | |
2179 | 97.2k | in_len=GetBlobSize(image); |
2180 | 97.2k | in_buf=GetBlobStreamData(image); |
2181 | 97.2k | if (in_buf == (unsigned char *) NULL) |
2182 | 0 | { |
2183 | | /* |
2184 | | Read the whole input file into a memory blob |
2185 | | */ |
2186 | 0 | in_buf=MagickAllocateResourceLimitedArray(unsigned char *,in_len,sizeof(*in_buf)); |
2187 | 0 | if (in_buf == (unsigned char *) NULL) |
2188 | 0 | ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image); |
2189 | 0 | in_buf_allocated=MagickTrue; |
2190 | |
|
2191 | 0 | if (image->logging) |
2192 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2193 | 0 | "Reading file into in-memory blob..."); |
2194 | 0 | if (ReadBlob(image,in_len,in_buf) != in_len) |
2195 | 0 | ThrowHEIFReaderException(CorruptImageError, UnexpectedEndOfFile, image); |
2196 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2197 | 0 | " Done!"); |
2198 | 0 | } |
2199 | 97.2k | else |
2200 | 97.2k | { |
2201 | | /* |
2202 | | Accessing memory-mapped input file or blob memory directly. |
2203 | | */ |
2204 | 97.2k | if (image->logging) |
2205 | 97.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2206 | 97.2k | "Input accessed as in-memory blob"); |
2207 | 97.2k | } |
2208 | | |
2209 | | /* Init HEIF-Decoder handles */ |
2210 | 97.2k | heif=heif_context_alloc(); |
2211 | | |
2212 | | /* Apply security limits */ |
2213 | 97.2k | (void) apply_security_limits(image_info,heif); |
2214 | | |
2215 | | /* Header sanity check */ |
2216 | 97.2k | if (!IsHEIF(in_buf, in_len)) |
2217 | 97.0k | ThrowHEIFReaderException(CorruptImageError, ImproperImageHeader, image); |
2218 | | |
2219 | | /* Test file header for format, and update image magick string */ |
2220 | 97.0k | magick="HEIF"; |
2221 | 97.0k | if (heif_has_compatible_brand(in_buf, in_len, "avci") == 1) |
2222 | 798 | magick="AVCI"; |
2223 | 96.2k | else if (heif_has_compatible_brand(in_buf, in_len, "avif") == 1) |
2224 | 29.2k | magick="AVIF"; |
2225 | 66.9k | else if (heif_has_compatible_brand(in_buf, in_len, "heix") == 1) |
2226 | 653 | magick="HEIC"; |
2227 | 66.3k | else if (heif_has_compatible_brand(in_buf, in_len, "heic") == 1) |
2228 | 12.8k | magick="HEIC"; |
2229 | 53.4k | else if (heif_has_compatible_brand(in_buf, in_len, "mp41") == 1) |
2230 | 333 | magick="MP4"; |
2231 | 53.1k | else if (heif_has_compatible_brand(in_buf, in_len, "mp42") == 1) |
2232 | 165 | magick="MP4"; |
2233 | 97.0k | (void) strlcpy(image->magick,magick,sizeof(image->magick)); |
2234 | 97.0k | if (image->logging) |
2235 | 97.0k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2236 | 97.0k | "Assigned magick: %s", magick); |
2237 | | |
2238 | | /* |
2239 | | Trace some information about the file |
2240 | | */ |
2241 | 97.0k | if (image->logging) |
2242 | 97.0k | { |
2243 | 97.0k | { |
2244 | | /* Log main brand */ |
2245 | 97.0k | (void) memset(fourcc,0,sizeof(fourcc)); |
2246 | 97.0k | heif_brand_to_fourcc(heif_read_main_brand(in_buf, in_len), fourcc); |
2247 | 97.0k | fourcc[4]=0; |
2248 | 97.0k | if (fourcc[0] == 0) |
2249 | 13.2k | { |
2250 | 13.2k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2251 | 13.2k | "Failed to read main brand!"); |
2252 | 13.2k | } |
2253 | 83.7k | else |
2254 | 83.7k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2255 | 83.7k | "HEIF Brand: \"%s\"", fourcc); |
2256 | 97.0k | } |
2257 | 97.0k | { |
2258 | | /* Log compatible brands */ |
2259 | 97.0k | heif_brand2* brands = NULL; |
2260 | 97.0k | int nBrands = 0; |
2261 | 97.0k | heif_status = heif_list_compatible_brands(in_buf, in_len, &brands, &nBrands); |
2262 | 97.0k | if (heif_status.code == heif_error_Ok) |
2263 | 70.3k | { |
2264 | 70.3k | char |
2265 | 70.3k | *compatible_brands; |
2266 | | |
2267 | 70.3k | const size_t |
2268 | 70.3k | compatible_brands_alloc = 6*(nBrands+1); |
2269 | | |
2270 | 70.3k | compatible_brands=MagickAllocateClearedMemory(char *,compatible_brands_alloc); |
2271 | 70.3k | if (compatible_brands == (char *) NULL) |
2272 | 70.3k | ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image); |
2273 | 70.3k | { |
2274 | 70.3k | int |
2275 | 70.3k | i; |
2276 | | |
2277 | 300k | for (i = 0; i < nBrands; i++) |
2278 | 230k | { |
2279 | 230k | heif_brand_to_fourcc(brands[i], fourcc); |
2280 | 230k | fourcc[4]=0; |
2281 | 230k | if (i > 0) |
2282 | 161k | (void) strlcat(compatible_brands,", ",compatible_brands_alloc); |
2283 | 230k | (void) strlcat(compatible_brands,fourcc,compatible_brands_alloc); |
2284 | 230k | } |
2285 | 70.3k | } |
2286 | 70.3k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2287 | 70.3k | "Compatible HEIF brands: %s", compatible_brands); |
2288 | 70.3k | MagickFreeMemory(compatible_brands); |
2289 | 70.3k | } |
2290 | 26.6k | else |
2291 | 26.6k | { |
2292 | 26.6k | if (image->logging) |
2293 | 26.6k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2294 | 26.6k | "heif_list_compatible_brands() " |
2295 | 26.6k | "reports error %d, suberror %d, message \"%s\"", |
2296 | 26.6k | heif_status.code, heif_status.subcode, heif_status.message); |
2297 | 26.6k | } |
2298 | 97.0k | heif_free_list_of_compatible_brands(brands); |
2299 | 97.0k | } |
2300 | 0 | { |
2301 | | /* Log MIME type */ |
2302 | 97.0k | const char *mime_type = heif_get_file_mime_type(in_buf, in_len); |
2303 | 97.0k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2304 | 97.0k | "MIME Type: %s", |
2305 | 97.0k | mime_type == (const char *) NULL ? "unknown" : mime_type); |
2306 | 97.0k | } |
2307 | 97.0k | } /* if (image->logging) */ |
2308 | | |
2309 | 97.0k | #if LIBHEIF_HAVE_VERSION(1,15,0) |
2310 | | /* Specify the maximum number of tile decoding threads used by libheif (default 4). */ |
2311 | 97.0k | { |
2312 | 97.0k | unsigned int decoding_threads; |
2313 | | #if defined(HAVE_OPENMP) |
2314 | | decoding_threads=(uint32_t) omp_get_max_threads(); |
2315 | | #else |
2316 | 97.0k | decoding_threads = 4; |
2317 | 97.0k | #endif |
2318 | 97.0k | MagickAttributeToU32(image_info,"heif","tile-threads",decoding_threads); |
2319 | 97.0k | if (image->logging) |
2320 | 97.0k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2321 | 97.0k | "Using %u tile decoding threads...", decoding_threads); |
2322 | 97.0k | heif_context_set_max_decoding_threads(heif,decoding_threads); |
2323 | 97.0k | } |
2324 | 97.0k | #endif /* LIBHEIF_HAVE_VERSION(1,15,0) */ |
2325 | | |
2326 | | /* |
2327 | | Allow the user to enable transformations (e.g. the image might be rotated). |
2328 | | */ |
2329 | 97.0k | if ((value=AccessDefinition(image_info,"heif","ignore-transformations"))) |
2330 | 0 | { |
2331 | 0 | if (LocaleCompare(value,"FALSE") == 0) |
2332 | 0 | decode_options->ignore_transformations = MagickFalse; |
2333 | 0 | else if (LocaleCompare(value,"TRUE") == 0) |
2334 | 0 | decode_options->ignore_transformations = MagickTrue; |
2335 | 0 | if (image->logging) |
2336 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2337 | 0 | "Updated ignore-transformations: %s", |
2338 | 0 | decode_options->ignore_transformations ? "True" : "False"); |
2339 | 0 | } |
2340 | | |
2341 | | |
2342 | 97.0k | heif_status=heif_context_read_from_memory_without_copy(heif, in_buf, in_len, NULL); |
2343 | 97.0k | if (heif_status.code == heif_error_Unsupported_filetype |
2344 | 94.8k | || heif_status.code == heif_error_Unsupported_feature) |
2345 | 94.4k | ThrowHEIFReaderException(CoderError, ImageTypeNotSupported, image); |
2346 | 94.4k | if (heif_status.code != heif_error_Ok) |
2347 | 48.3k | { |
2348 | 48.3k | if (image->logging) |
2349 | 48.3k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2350 | 48.3k | "heif_context_read_from_memory_without_copy() " |
2351 | 48.3k | "reports error %d, suberror %d, message \"%s\"", |
2352 | 48.3k | heif_status.code, heif_status.subcode, heif_status.message); |
2353 | 48.3k | ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
2354 | 0 | } |
2355 | | |
2356 | 46.0k | #if defined(HEIF_SUPPORT_SEQUENCES) && HEIF_SUPPORT_SEQUENCES |
2357 | 46.0k | has_sequence = heif_context_has_sequence(heif); |
2358 | 46.0k | if (image->logging) |
2359 | 46.0k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2360 | 46.0k | "Has sequence: %c", has_sequence ? 'Y' : 'N'); |
2361 | 46.0k | #endif /* if defined(HEIF_SUPPORT_SEQUENCES) && HEIF_SUPPORT_SEQUENCES */ |
2362 | | |
2363 | | /* Get the number of top level images */ |
2364 | 46.0k | number_of_top_level_images=heif_context_get_number_of_top_level_images(heif); |
2365 | 46.0k | if (image->logging) |
2366 | 46.0k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2367 | 46.0k | "Number of top level images: %d", |
2368 | 46.0k | number_of_top_level_images); |
2369 | 46.0k | if (number_of_top_level_images <= 0) |
2370 | 375 | { |
2371 | 375 | if (image->logging) |
2372 | 375 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2373 | 375 | "heif_context_get_number_of_top_level_images() reports %d \"%s\"", |
2374 | 375 | number_of_top_level_images, heif_status.message); |
2375 | 375 | ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
2376 | 0 | } |
2377 | | |
2378 | | /* Get the primary image id */ |
2379 | 45.7k | heif_status=heif_context_get_primary_image_ID(heif,&primary_image_id); |
2380 | 45.7k | if (heif_status.code != heif_error_Ok) |
2381 | 0 | { |
2382 | 0 | if (image->logging) |
2383 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2384 | 0 | "heif_context_get_primary_image_ID() " |
2385 | 0 | "reports error %d, suberror %d, message \"%s\"", |
2386 | 0 | heif_status.code, heif_status.subcode, heif_status.message); |
2387 | 0 | ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
2388 | 0 | } |
2389 | 45.7k | if (image->logging) |
2390 | 45.7k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2391 | 45.7k | "Primary image id: %d", primary_image_id); |
2392 | | |
2393 | | /* Get a handle for the primary image id */ |
2394 | 45.7k | heif_status=heif_context_get_image_handle(heif, primary_image_id, &heif_image_handle); |
2395 | 45.7k | if (heif_status.code == heif_error_Memory_allocation_error) |
2396 | 45.7k | ThrowHEIFReaderException(ResourceLimitError, MemoryAllocationFailed, image); |
2397 | 45.7k | if (heif_status.code != heif_error_Ok) |
2398 | 1.14k | { |
2399 | 1.14k | if (image->logging) |
2400 | 1.14k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2401 | 1.14k | "heif_context_get_image_handle() " |
2402 | 1.14k | "reports error %d, suberror %d, message \"%s\"", |
2403 | 1.14k | heif_status.code, heif_status.subcode, heif_status.message); |
2404 | 1.14k | ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
2405 | 0 | } |
2406 | | |
2407 | 44.5k | status=ReadHEIFImageFrame(heif_image_handle,decode_options,image,image_info,exception); |
2408 | | |
2409 | 44.5k | heif_image_handle_release(heif_image_handle); |
2410 | 44.5k | heif_image_handle=(struct heif_image_handle *) NULL; |
2411 | | |
2412 | | /* |
2413 | | Read any additional top-level images |
2414 | | */ |
2415 | 44.5k | if ((status != MagickFail) && (number_of_top_level_images > 1)) |
2416 | 1.65k | { |
2417 | 1.65k | int |
2418 | 1.65k | item_num; |
2419 | | |
2420 | 1.65k | item_ids=MagickAllocateResourceLimitedArray(heif_item_id *,number_of_top_level_images, |
2421 | 1.65k | sizeof(*item_ids)); |
2422 | 1.65k | if (item_ids == (heif_item_id *) NULL) |
2423 | 1.65k | ThrowHEIFReaderException(ResourceLimitError, MemoryAllocationFailed, image); |
2424 | | |
2425 | 1.65k | heif_context_get_list_of_top_level_image_IDs(heif,item_ids,number_of_top_level_images); |
2426 | | |
2427 | 3.28k | for (item_num=0; item_num < number_of_top_level_images; item_num++) |
2428 | 3.28k | { |
2429 | 3.28k | if (image->logging) |
2430 | 3.28k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2431 | 3.28k | "Image index %d: image id %d", |
2432 | 3.28k | item_num, item_ids[item_num]); |
2433 | | |
2434 | | /* Skip the primary image we already read. Hopefully this |
2435 | | was also the first image in the top level image list. */ |
2436 | 3.28k | if (item_ids[item_num] == primary_image_id) |
2437 | 1.62k | { |
2438 | 1.62k | if (image->logging) |
2439 | 1.62k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2440 | 1.62k | "Item %d matches primary image id %d (skipping)", |
2441 | 1.62k | item_num, primary_image_id); |
2442 | 1.62k | continue; |
2443 | 1.62k | } |
2444 | | |
2445 | | /* |
2446 | | Allocate next image structure. |
2447 | | */ |
2448 | 1.65k | AllocateNextImage(image_info,image); |
2449 | 1.65k | image=SyncNextImageInList(image); |
2450 | 1.65k | (void) strlcpy(image->magick,magick,sizeof(image->magick)); |
2451 | | |
2452 | | /* Get HEIF image handle */ |
2453 | 1.65k | heif_status=heif_context_get_image_handle(heif, item_ids[item_num],&heif_image_handle); |
2454 | 1.65k | if (heif_status.code == heif_error_Memory_allocation_error) |
2455 | 1.65k | ThrowHEIFReaderException(ResourceLimitError, MemoryAllocationFailed, image); |
2456 | 1.65k | if (heif_status.code != heif_error_Ok) |
2457 | 118 | { |
2458 | 118 | if (image->logging) |
2459 | 118 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
2460 | 118 | "heif_context_get_image_handle() " |
2461 | 118 | "reports error %d, suberror %d, message \"%s\"", |
2462 | 118 | heif_status.code, heif_status.subcode, heif_status.message); |
2463 | 118 | ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image); |
2464 | 0 | } |
2465 | | |
2466 | | /* Read the HEIF frame */ |
2467 | 1.53k | status=ReadHEIFImageFrame(heif_image_handle,decode_options,image,image_info,exception); |
2468 | | |
2469 | | /* Release the HEIF image handle */ |
2470 | 1.53k | heif_image_handle_release(heif_image_handle); |
2471 | 1.53k | heif_image_handle=(struct heif_image_handle *) NULL; |
2472 | | |
2473 | 1.53k | if (status == MagickFail) |
2474 | 774 | break; |
2475 | | |
2476 | | /* Quit if no more scenes requested */ |
2477 | 761 | if (image_info->subrange != 0) |
2478 | 761 | if (image->scene >= (image_info->subimage+image_info->subrange-1)) |
2479 | 761 | break; |
2480 | 761 | } |
2481 | 1.53k | MagickFreeResourceLimitedMemory(heif_item_id *,item_ids); |
2482 | 1.53k | } |
2483 | | |
2484 | 44.4k | HEIFReadCleanup(); |
2485 | 44.4k | CloseBlob(image); |
2486 | 44.4k | if (status == MagickFail) |
2487 | 31.5k | { |
2488 | 31.5k | DestroyImageList(image); |
2489 | 31.5k | image=(Image *) NULL; |
2490 | 31.5k | } |
2491 | | |
2492 | 44.4k | return image; |
2493 | 44.5k | } |
2494 | | |
2495 | | #endif /* HasHEIF */ |
2496 | | |
2497 | | /* |
2498 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2499 | | % % |
2500 | | % % |
2501 | | % % |
2502 | | % R e g i s t e r H E I F I m a g e % |
2503 | | % % |
2504 | | % % |
2505 | | % % |
2506 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2507 | | % |
2508 | | % Method RegisterHEIFImage adds attributes for the HEIF image format to |
2509 | | % the list of supported formats. The attributes include the image format |
2510 | | % tag, a method to read and/or write the format and a brief |
2511 | | % description of the format. |
2512 | | % |
2513 | | % The format of the RegisterHEIFImage method is: |
2514 | | % |
2515 | | % RegisterHEIFImage(void) |
2516 | | % |
2517 | | */ |
2518 | | static void GetHEIFDecoders(enum heif_compression_format compression_format, |
2519 | | char *output_str, |
2520 | | const size_t sizeof_output_str) |
2521 | 36 | { |
2522 | 36 | const char |
2523 | 36 | *decoder_description; |
2524 | | |
2525 | 36 | int |
2526 | 36 | decoder_count, |
2527 | 36 | decoder_index; |
2528 | | |
2529 | 36 | #define MAX_HEIF_DECODERS 20 |
2530 | | |
2531 | | /* heif_compression_format is declared in libheif/api/libheif/heif_context.h */ |
2532 | 36 | const heif_decoder_descriptor |
2533 | 36 | *out_decoders[MAX_HEIF_DECODERS]; |
2534 | | |
2535 | 36 | decoder_count = heif_get_decoder_descriptors(compression_format, |
2536 | 36 | out_decoders, |
2537 | 36 | MAX_HEIF_DECODERS); |
2538 | 36 | if (decoder_count > 0) |
2539 | 36 | { |
2540 | 36 | (void) strlcpy(output_str,"Decoders: ", sizeof_output_str); |
2541 | 135 | for (decoder_index = 0 ; decoder_index < decoder_count; decoder_index++) |
2542 | 99 | { |
2543 | 99 | decoder_description = heif_decoder_descriptor_get_name(out_decoders[decoder_index]); |
2544 | 99 | if (decoder_description != (const char *) NULL) |
2545 | 99 | { |
2546 | 99 | if (decoder_index != 0) |
2547 | 63 | (void) strlcat(output_str,"; ",sizeof_output_str); |
2548 | 99 | (void) strlcat(output_str,decoder_description,sizeof_output_str); |
2549 | 99 | } |
2550 | 99 | } |
2551 | 36 | } |
2552 | 36 | } |
2553 | | ModuleExport void RegisterHEIFImage(void) |
2554 | 9 | { |
2555 | 9 | #if defined(HasHEIF) |
2556 | 9 | static char |
2557 | 9 | version[20]; |
2558 | | |
2559 | 9 | static const MagickBool |
2560 | 9 | need_seekable_stream=MagickFalse; /* Currently use in memory blob */ |
2561 | | |
2562 | 9 | MagickInfo |
2563 | 9 | *entry; |
2564 | | |
2565 | | /* Get version information */ |
2566 | 9 | { |
2567 | 9 | unsigned int |
2568 | 9 | heif_major, |
2569 | 9 | heif_minor, |
2570 | 9 | heif_revision; |
2571 | | |
2572 | 9 | int |
2573 | 9 | encoder_version; |
2574 | | |
2575 | 9 | encoder_version=heif_get_version_number(); |
2576 | 9 | heif_major=(encoder_version >> 16) & 0xff; |
2577 | 9 | heif_minor=(encoder_version >> 8) & 0xff; |
2578 | 9 | heif_revision=encoder_version & 0xff; |
2579 | 9 | *version='\0'; |
2580 | 9 | (void) snprintf(version, sizeof(version), |
2581 | 9 | "heif v%u.%u.%u", heif_major, |
2582 | 9 | heif_minor, heif_revision); |
2583 | 9 | } |
2584 | | |
2585 | | /* heif_compression_format is declared in libheif/api/libheif/heif_context.h */ |
2586 | | |
2587 | | /* AVCI (H.264) */ |
2588 | 9 | if (heif_have_decoder_for_format(heif_compression_AVC)/* || */ |
2589 | 9 | /* heif_have_encoder_for_format(heif_compression_AVC) */) |
2590 | 9 | { |
2591 | 9 | static char |
2592 | 9 | AVCNote[64] = ""; |
2593 | | |
2594 | 9 | GetHEIFDecoders(heif_compression_AVC,AVCNote,sizeof(AVCNote)); |
2595 | | |
2596 | 9 | entry=SetMagickInfo("AVCI"); |
2597 | 9 | entry->decoder=(DecoderHandler) ReadHEIFImage; |
2598 | 9 | entry->magick=(MagickHandler) IsHEIF; |
2599 | 9 | entry->description="Advanced Video Coding Image Format"; |
2600 | 9 | if (AVCNote[0] != '\0') |
2601 | 9 | entry->note=AVCNote; |
2602 | 9 | entry->adjoin=MagickFalse; |
2603 | 9 | entry->seekable_stream=need_seekable_stream; |
2604 | 9 | if (*version != '\0') |
2605 | 9 | entry->version=version; |
2606 | 9 | entry->module="HEIF"; |
2607 | 9 | entry->coder_class=PrimaryCoderClass; |
2608 | 9 | entry->extension_treatment=ObeyExtensionTreatment; |
2609 | 9 | (void) RegisterMagickInfo(entry); |
2610 | 9 | } |
2611 | | |
2612 | | /* AVIF / heif_compression_AV1 */ |
2613 | 9 | if (heif_have_decoder_for_format(heif_compression_AV1)/* || */ |
2614 | 9 | /* heif_have_encoder_for_format(heif_compression_AV1) */) |
2615 | 9 | { |
2616 | 9 | static char |
2617 | 9 | AV1Note[64] = ""; |
2618 | | |
2619 | 9 | GetHEIFDecoders(heif_compression_AV1,AV1Note,sizeof(AV1Note)); |
2620 | 9 | entry=SetMagickInfo("AVIF"); |
2621 | 9 | entry->decoder=(DecoderHandler) ReadHEIFImage; |
2622 | 9 | entry->magick=(MagickHandler) IsHEIF; |
2623 | 9 | entry->description="AV1 Image Format"; |
2624 | 9 | if (AV1Note[0] != '\0') |
2625 | 9 | entry->note=AV1Note; |
2626 | 9 | entry->adjoin=MagickFalse; |
2627 | 9 | entry->seekable_stream=need_seekable_stream; |
2628 | 9 | if (*version != '\0') |
2629 | 9 | entry->version=version; |
2630 | 9 | entry->module="HEIF"; |
2631 | 9 | entry->coder_class=PrimaryCoderClass; |
2632 | 9 | entry->extension_treatment=ObeyExtensionTreatment; |
2633 | 9 | (void) RegisterMagickInfo(entry); |
2634 | 9 | } |
2635 | | |
2636 | | /* |
2637 | | This entry is used for anything which looks like HEIF, but is not |
2638 | | one of our directly supported subformats. |
2639 | | */ |
2640 | 9 | { |
2641 | 9 | static char |
2642 | 9 | HEIFNote[256] = ""; |
2643 | | |
2644 | 9 | GetHEIFDecoders(heif_compression_undefined,HEIFNote,sizeof(HEIFNote)); |
2645 | | |
2646 | 9 | entry=SetMagickInfo("HEIF"); |
2647 | 9 | entry->decoder=(DecoderHandler) ReadHEIFImage; |
2648 | 9 | entry->magick=(MagickHandler) IsHEIF; |
2649 | 9 | entry->description="High Efficiency Image Format"; |
2650 | 9 | if (HEIFNote[0] != '\0') |
2651 | 9 | entry->note=HEIFNote; |
2652 | 9 | entry->adjoin=MagickFalse; |
2653 | 9 | entry->seekable_stream=need_seekable_stream; |
2654 | 9 | if (*version != '\0') |
2655 | 9 | entry->version=version; |
2656 | 9 | entry->module="HEIF"; |
2657 | 9 | entry->coder_class=PrimaryCoderClass; |
2658 | 9 | entry->extension_treatment=ObeyExtensionTreatment; |
2659 | 9 | (void) RegisterMagickInfo(entry); |
2660 | 9 | } |
2661 | | |
2662 | | /* HEIC (H.265) / heif_compression_HEVC */ |
2663 | 9 | if (heif_have_decoder_for_format(heif_compression_HEVC) /* || */ |
2664 | 9 | /* heif_have_encoder_for_format(heif_compression_HEVC) */) |
2665 | 9 | { |
2666 | 9 | static char |
2667 | 9 | HEVCNote[64] = ""; |
2668 | | |
2669 | 9 | GetHEIFDecoders(heif_compression_AV1,HEVCNote,sizeof(HEVCNote)); |
2670 | | |
2671 | 9 | entry=SetMagickInfo("HEIC"); |
2672 | 9 | entry->decoder=(DecoderHandler) ReadHEIFImage; |
2673 | 9 | entry->magick=(MagickHandler) IsHEIF; |
2674 | 9 | entry->description="High-Efficiency Image Codec in HEIF"; |
2675 | 9 | if (HEVCNote[0] != '\0') |
2676 | 9 | entry->note=HEVCNote; |
2677 | 9 | entry->adjoin=MagickFalse; |
2678 | 9 | entry->seekable_stream=need_seekable_stream; |
2679 | 9 | if (*version != '\0') |
2680 | 9 | entry->version=version; |
2681 | 9 | entry->module="HEIF"; |
2682 | 9 | entry->coder_class=PrimaryCoderClass; |
2683 | 9 | entry->extension_treatment=ObeyExtensionTreatment; |
2684 | 9 | (void) RegisterMagickInfo(entry); |
2685 | | |
2686 | | /* Libheif supports some MP4 files */ |
2687 | 9 | entry=SetMagickInfo("MP4"); |
2688 | 9 | entry->decoder=(DecoderHandler) ReadHEIFImage; |
2689 | 9 | entry->magick=(MagickHandler) IsHEIF; |
2690 | 9 | entry->description="MPEG-4 Part 14 in HEIF"; |
2691 | 9 | if (HEVCNote[0] != '\0') |
2692 | 9 | entry->note=HEVCNote; |
2693 | 9 | entry->adjoin=MagickFalse; |
2694 | 9 | entry->seekable_stream=need_seekable_stream; |
2695 | 9 | if (*version != '\0') |
2696 | 9 | entry->version=version; |
2697 | 9 | entry->module="HEIF"; |
2698 | 9 | entry->coder_class=PrimaryCoderClass; |
2699 | 9 | entry->extension_treatment=ObeyExtensionTreatment; |
2700 | 9 | (void) RegisterMagickInfo(entry); |
2701 | 9 | } |
2702 | | |
2703 | | /* MP4 (H.264, H.265) / heif_compression_HEVC */ |
2704 | | /* |
2705 | | Since HEIF image sequences are very similar to MP4 video, libheif |
2706 | | can also read and write MP4 video (without audio) with all |
2707 | | supported codecs. |
2708 | | |
2709 | | heif_context_has_sequence() will return if input is a sequence. |
2710 | | Note that a file can contain still images and a sequence at the |
2711 | | same time. |
2712 | | |
2713 | | heif_compression_HEVC is H.265, heif_compression_AVC is H.264, and |
2714 | | heif_compression_AV1 is AV1 |
2715 | | |
2716 | | video/mp4 or application/mp4 |
2717 | | */ |
2718 | | #if 0 |
2719 | | if (heif_have_decoder_for_format(heif_compression_HEVC) || |
2720 | | heif_have_decoder_for_format(heif_compression_AVC) || |
2721 | | heif_have_decoder_for_format(heif_compression_AV1)) |
2722 | | { |
2723 | | static char |
2724 | | MP4Note[256] = ""; |
2725 | | |
2726 | | /* FIXME: Needs to handle all MP4 codecs */ |
2727 | | GetHEIFDecoders(heif_compression_AVC,MP4Note,sizeof(MP4Note)); |
2728 | | |
2729 | | entry=SetMagickInfo("MP4"); |
2730 | | entry->decoder=(DecoderHandler) ReadHEIFImage; |
2731 | | entry->magick=(MagickHandler) IsHEIF; |
2732 | | entry->description="MP4 video container (HVEC, AVC, or AV1 compression)"; |
2733 | | if (MP4Note[0] != '\0') |
2734 | | entry->note=MP4Note; |
2735 | | entry->adjoin=MagickFalse; |
2736 | | entry->seekable_stream=need_seekable_stream; |
2737 | | if (*version != '\0') |
2738 | | entry->version=version; |
2739 | | entry->module="HEIF"; |
2740 | | entry->coder_class=UnstableCoderClass; |
2741 | | entry->extension_treatment=HintExtensionTreatment; |
2742 | | (void) RegisterMagickInfo(entry); |
2743 | | } |
2744 | | #endif |
2745 | | |
2746 | 9 | #endif /* HasHEIF */ |
2747 | 9 | } |
2748 | | |
2749 | | /* |
2750 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2751 | | % % |
2752 | | % % |
2753 | | % % |
2754 | | % U n r e g i s t e r H E I F I m a g e % |
2755 | | % % |
2756 | | % % |
2757 | | % % |
2758 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2759 | | % |
2760 | | % Method UnregisterHEIFImage removes format registrations made by the |
2761 | | % HEIF module from the list of supported formats. |
2762 | | % |
2763 | | % The format of the UnregisterHEIFImage method is: |
2764 | | % |
2765 | | % UnregisterHEIFImage(void) |
2766 | | % |
2767 | | */ |
2768 | | ModuleExport void UnregisterHEIFImage(void) |
2769 | 0 | { |
2770 | 0 | #if defined(HasHEIF) |
2771 | 0 | (void) UnregisterMagickInfo("AVCI"); |
2772 | 0 | (void) UnregisterMagickInfo("AVIF"); |
2773 | 0 | (void) UnregisterMagickInfo("HEIC"); |
2774 | 0 | (void) UnregisterMagickInfo("HEIF"); |
2775 | 0 | (void) UnregisterMagickInfo("MP4"); |
2776 | 0 | if (heif_initialized) |
2777 | 0 | heif_deinit(); |
2778 | 0 | #endif /* HasHEIF */ |
2779 | 0 | } |