1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %                        H   H  EEEEE  IIIII   CCCC                           %
7 %                        H   H  E        I    C                               %
8 %                        HHHHH  EEE      I    C                               %
9 %                        H   H  E        I    C                               %
10 %                        H   H  EEEEE  IIIII   CCCC                           %
11 %                                                                             %
12 %                                                                             %
13 %                        Read/Write Heic Image Format                         %
14 %                                                                             %
15 %                                 Dirk Farin                                  %
16 %                                 April 2018                                  %
17 %                                                                             %
18 %                         Copyright 2018 Struktur AG                          %
19 %                                                                             %
20 %                               Anton Kortunov                                %
21 %                               December 2017                                 %
22 %                                                                             %
23 %                      Copyright 2017-2018 YANDEX LLC.                        %
24 %                                                                             %
25 %  Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization      %
26 %  dedicated to making software imaging solutions freely available.           %
27 %                                                                             %
28 %  You may not use this file except in compliance with the License.  You may  %
29 %  obtain a copy of the License at                                            %
30 %                                                                             %
31 %    https://imagemagick.org/script/license.php                               %
32 %                                                                             %
33 %  Unless required by applicable law or agreed to in writing, software        %
34 %  distributed under the License is distributed on an "AS IS" BASIS,          %
35 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
36 %  See the License for the specific language governing permissions and        %
37 %  limitations under the License.                                             %
38 %                                                                             %
39 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
40 %
41 %
42 */
43 
44 /*
45   Include declarations.
46 */
47 #include "MagickCore/studio.h"
48 #include "MagickCore/artifact.h"
49 #include "MagickCore/blob.h"
50 #include "MagickCore/blob-private.h"
51 #include "MagickCore/client.h"
52 #include "MagickCore/colorspace-private.h"
53 #include "MagickCore/property.h"
54 #include "MagickCore/display.h"
55 #include "MagickCore/exception.h"
56 #include "MagickCore/exception-private.h"
57 #include "MagickCore/image.h"
58 #include "MagickCore/image-private.h"
59 #include "MagickCore/list.h"
60 #include "MagickCore/magick.h"
61 #include "MagickCore/monitor.h"
62 #include "MagickCore/monitor-private.h"
63 #include "MagickCore/montage.h"
64 #include "MagickCore/transform.h"
65 #include "MagickCore/distort.h"
66 #include "MagickCore/memory_.h"
67 #include "MagickCore/memory-private.h"
68 #include "MagickCore/option.h"
69 #include "MagickCore/pixel-accessor.h"
70 #include "MagickCore/quantum-private.h"
71 #include "MagickCore/static.h"
72 #include "MagickCore/string_.h"
73 #include "MagickCore/string-private.h"
74 #include "MagickCore/module.h"
75 #include "MagickCore/utility.h"
76 #if defined(MAGICKCORE_HEIC_DELEGATE)
77 #if defined(MAGICKCORE_WINDOWS_SUPPORT)
78 #include <heif.h>
79 #else
80 #include <libheif/heif.h>
81 #endif
82 #endif
83 
84 #if defined(MAGICKCORE_HEIC_DELEGATE)
85 /*
86   Define declarations.
87 */
88 #define XmpNamespaceExtent  28
89 
90 /*
91   Const declarations.
92 */
93 static const char
94   xmp_namespace[] = "http://ns.adobe.com/xap/1.0/ ";
95 
96 /*
97   Forward declarations.
98 */
99 static MagickBooleanType
100   WriteHEICImage(const ImageInfo *,Image *,ExceptionInfo *);
101 
102 /*x
103 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
104 %                                                                             %
105 %                                                                             %
106 %                                                                             %
107 %   R e a d H E I C I m a g e                                                 %
108 %                                                                             %
109 %                                                                             %
110 %                                                                             %
111 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
112 %
113 %  ReadHEICImage retrieves an image via a file descriptor, decodes the image,
114 %  and returns it.  It allocates the memory necessary for the new Image
115 %  structure and returns a pointer to the new image.
116 %
117 %  The format of the ReadHEICImage method is:
118 %
119 %      Image *ReadHEICImage(const ImageInfo *image_info,
120 %        ExceptionInfo *exception)
121 %
122 %  A description of each parameter follows:
123 %
124 %    o image_info: the image info.
125 %
126 %    o exception: return any errors or warnings in this structure.
127 %
128 */
129 
IsHeifSuccess(Image * image,struct heif_error * error,ExceptionInfo * exception)130 static MagickBooleanType IsHeifSuccess(Image *image,struct heif_error *error,
131   ExceptionInfo *exception)
132 {
133   if (error->code == 0)
134     return(MagickTrue);
135   ThrowBinaryException(CorruptImageError,error->message,image->filename);
136 }
137 
ReadHEICColorProfile(Image * image,struct heif_image_handle * image_handle,ExceptionInfo * exception)138 static MagickBooleanType ReadHEICColorProfile(Image *image,
139   struct heif_image_handle *image_handle,ExceptionInfo *exception)
140 {
141   size_t
142     length;
143 
144   /*
145     Read color profile.
146   */
147 #if LIBHEIF_NUMERIC_VERSION >= 0x01040000
148   length=heif_image_handle_get_raw_color_profile_size(image_handle);
149   if (length > 0)
150     {
151       unsigned char
152         *color_buffer;
153 
154       if ((MagickSizeType) length > GetBlobSize(image))
155         ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
156           image->filename);
157       color_buffer=(unsigned char *) AcquireQuantumMemory(1,length);
158       if (color_buffer != (unsigned char *) NULL)
159         {
160           struct heif_error
161             error;
162 
163           error=heif_image_handle_get_raw_color_profile(image_handle,
164             color_buffer);
165           if (error.code == 0)
166             {
167               StringInfo
168                 *profile;
169 
170               profile=BlobToStringInfo(color_buffer,length);
171               if (profile != (StringInfo*) NULL)
172                 {
173                   (void) SetImageProfile(image,"icc",profile,exception);
174                   profile=DestroyStringInfo(profile);
175                 }
176             }
177         }
178       color_buffer=(unsigned char *) RelinquishMagickMemory(color_buffer);
179     }
180 #endif
181   return(MagickTrue);
182 }
183 
ReadHEICExifProfile(Image * image,struct heif_image_handle * image_handle,ExceptionInfo * exception)184 static MagickBooleanType ReadHEICExifProfile(Image *image,
185   struct heif_image_handle *image_handle,ExceptionInfo *exception)
186 {
187   heif_item_id
188     exif_id;
189 
190   int
191     count;
192 
193   /*
194     Read Exif profile.
195   */
196   count=heif_image_handle_get_list_of_metadata_block_IDs(image_handle,"Exif",
197     &exif_id,1);
198   if (count > 0)
199     {
200       size_t
201         exif_size;
202 
203       unsigned char
204         *exif_buffer;
205 
206       exif_size=heif_image_handle_get_metadata_size(image_handle,exif_id);
207       if ((MagickSizeType) exif_size > GetBlobSize(image))
208         ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
209           image->filename);
210       exif_buffer=(unsigned char *) AcquireQuantumMemory(1,exif_size);
211       if (exif_buffer != (unsigned char *) NULL)
212         {
213           struct heif_error
214             error;
215 
216           error=heif_image_handle_get_metadata(image_handle,
217             exif_id,exif_buffer);
218           if (error.code == 0)
219             {
220               StringInfo
221                 *profile;
222 
223               /*
224                 Skip first 4 bytes, the offset to the TIFF header.
225               */
226               profile=(StringInfo*) NULL;
227               if (exif_size > 8)
228                 profile=BlobToStringInfo(exif_buffer+4,(size_t) exif_size-4);
229               if (profile != (StringInfo*) NULL)
230                 {
231                   (void) SetImageProfile(image,"exif",profile,exception);
232                   profile=DestroyStringInfo(profile);
233                 }
234             }
235         }
236       exif_buffer=(unsigned char *) RelinquishMagickMemory(exif_buffer);
237   }
238   return(MagickTrue);
239 }
240 
HEICSkipImage(const ImageInfo * image_info,Image * image)241 static inline MagickBooleanType HEICSkipImage(const ImageInfo *image_info,
242   Image *image)
243 {
244   if (image_info->number_scenes == 0)
245     return(MagickFalse);
246   if (image->scene == 0)
247     return(MagickFalse);
248   if (image->scene < image_info->scene)
249     return(MagickTrue);
250   if (image->scene > image_info->scene+image_info->number_scenes-1)
251     return(MagickTrue);
252   return(MagickFalse);
253 }
254 
ReadHEICImageByID(const ImageInfo * image_info,Image * image,struct heif_image_handle * image_handle,ExceptionInfo * exception)255 static MagickBooleanType ReadHEICImageByID(const ImageInfo *image_info,
256   Image *image,struct heif_image_handle *image_handle,
257   ExceptionInfo *exception)
258 {
259   const uint8_t
260     *p;
261 
262   int
263     stride = 0;
264 
265   MagickBooleanType
266     preserve_orientation,
267     status;
268 
269   ssize_t
270     y;
271 
272   struct heif_decoding_options
273     *decode_options;
274 
275   struct heif_error
276     error;
277 
278   struct heif_image
279     *heif_image;
280 
281   /*
282     Read HEIC image from container.
283   */
284   image->columns=(size_t) heif_image_handle_get_width(image_handle);
285   image->rows=(size_t) heif_image_handle_get_height(image_handle);
286   image->depth=8;
287 #if LIBHEIF_NUMERIC_VERSION > 0x01040000
288   {
289     int
290       bits_per_pixel;
291 
292     bits_per_pixel=heif_image_handle_get_luma_bits_per_pixel(image_handle);
293     if (bits_per_pixel != -1)
294       image->depth=(size_t) bits_per_pixel;
295   }
296 #endif
297   if (heif_image_handle_has_alpha_channel(image_handle))
298     image->alpha_trait=BlendPixelTrait;
299   preserve_orientation=IsStringTrue(GetImageOption(image_info,
300     "heic:preserve-orientation"));
301   if (preserve_orientation == MagickFalse)
302     (void) SetImageProperty(image,"exif:Orientation","1",exception);
303   if (ReadHEICColorProfile(image,image_handle,exception) == MagickFalse)
304     return(MagickFalse);
305   if (ReadHEICExifProfile(image,image_handle,exception) == MagickFalse)
306     return(MagickFalse);
307   if (image_info->ping != MagickFalse)
308     return(MagickTrue);
309   if (HEICSkipImage(image_info,image) != MagickFalse)
310     return(MagickTrue);
311   status=SetImageExtent(image,image->columns,image->rows,exception);
312   if (status == MagickFalse)
313     return(MagickFalse);
314   decode_options=heif_decoding_options_alloc();
315 #if LIBHEIF_NUMERIC_VERSION > 0x01070000
316   decode_options->convert_hdr_to_8bit=1;
317 #endif
318   if (preserve_orientation == MagickTrue)
319     decode_options->ignore_transformations=1;
320   error=heif_decode_image(image_handle,&heif_image,heif_colorspace_RGB,
321     image->alpha_trait != UndefinedPixelTrait ? heif_chroma_interleaved_RGBA :
322     heif_chroma_interleaved_RGB,decode_options);
323   heif_decoding_options_free(decode_options);
324   if (IsHeifSuccess(image,&error,exception) == MagickFalse)
325     return(MagickFalse);
326   image->columns=(size_t) heif_image_get_width(heif_image,
327     heif_channel_interleaved);
328   image->rows=(size_t) heif_image_get_height(heif_image
329     ,heif_channel_interleaved);
330   status=SetImageExtent(image,image->columns,image->rows,exception);
331   if (status == MagickFalse)
332     {
333       heif_image_release(heif_image);
334       return(MagickFalse);
335     }
336   p=heif_image_get_plane_readonly(heif_image,heif_channel_interleaved,&stride);
337   stride-=(int) (image->columns * (image->alpha_trait != UndefinedPixelTrait ?
338     4 : 3));
339   for (y=0; y < (ssize_t) image->rows; y++)
340   {
341     Quantum
342       *q;
343 
344     ssize_t
345       x;
346 
347     q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
348     if (q == (Quantum *) NULL)
349       break;
350     for (x=0; x < (ssize_t) image->columns; x++)
351     {
352       SetPixelRed(image,ScaleCharToQuantum((unsigned char) *(p++)),q);
353       SetPixelGreen(image,ScaleCharToQuantum((unsigned char) *(p++)),q);
354       SetPixelBlue(image,ScaleCharToQuantum((unsigned char) *(p++)),q);
355       if (image->alpha_trait != UndefinedPixelTrait)
356         SetPixelAlpha(image,ScaleCharToQuantum((unsigned char) *(p++)),q);
357       q+=GetPixelChannels(image);
358     }
359     p+=stride;
360     if (SyncAuthenticPixels(image,exception) == MagickFalse)
361       break;
362   }
363   heif_image_release(heif_image);
364   return(MagickTrue);
365 }
366 
ReadHEICImage(const ImageInfo * image_info,ExceptionInfo * exception)367 static Image *ReadHEICImage(const ImageInfo *image_info,
368   ExceptionInfo *exception)
369 {
370   heif_item_id
371     *image_ids,
372     primary_image_id;
373 
374   Image
375     *image;
376 
377   MagickBooleanType
378     status;
379 
380   size_t
381     count,
382     length;
383 
384   struct heif_context
385     *heif_context;
386 
387   struct heif_error
388     error;
389 
390   struct heif_image_handle
391     *image_handle;
392 
393   void
394     *file_data;
395 
396   /*
397     Open image file.
398   */
399   assert(image_info != (const ImageInfo *) NULL);
400   assert(image_info->signature == MagickCoreSignature);
401   if (image_info->debug != MagickFalse)
402     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
403       image_info->filename);
404   assert(exception != (ExceptionInfo *) NULL);
405   assert(exception->signature == MagickCoreSignature);
406   image=AcquireImage(image_info,exception);
407   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
408   if (status == MagickFalse)
409     return(DestroyImageList(image));
410   if (GetBlobSize(image) > (MagickSizeType) MAGICK_SSIZE_MAX)
411     ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
412   length=(size_t) GetBlobSize(image);
413   file_data=AcquireMagickMemory(length);
414   if (file_data == (void *) NULL)
415     ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
416   if (ReadBlob(image,length,file_data) != (ssize_t) length)
417     {
418       file_data=RelinquishMagickMemory(file_data);
419       ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
420     }
421 #if LIBHEIF_NUMERIC_VERSION >= 0x010b0000
422   if (heif_has_compatible_brand(file_data,(int) length,"avif") != MagickFalse)
423     (void) CopyMagickString(image->magick,"AVIF",MagickPathExtent);
424 #endif
425   /*
426     Decode HEIF image.
427   */
428   heif_context=heif_context_alloc();
429   error=heif_context_read_from_memory_without_copy(heif_context,file_data,
430     length,NULL);
431   if (IsHeifSuccess(image,&error,exception) == MagickFalse)
432     {
433       heif_context_free(heif_context);
434       file_data=RelinquishMagickMemory(file_data);
435       return(DestroyImageList(image));
436     }
437   error=heif_context_get_primary_image_ID(heif_context,&primary_image_id);
438   if (IsHeifSuccess(image,&error,exception) == MagickFalse)
439     {
440       heif_context_free(heif_context);
441       file_data=RelinquishMagickMemory(file_data);
442       return(DestroyImageList(image));
443     }
444   error=heif_context_get_image_handle(heif_context,primary_image_id,
445     &image_handle);
446   if (IsHeifSuccess(image,&error,exception) == MagickFalse)
447     {
448       heif_context_free(heif_context);
449       file_data=RelinquishMagickMemory(file_data);
450       return(DestroyImageList(image));
451     }
452   status=ReadHEICImageByID(image_info,image,image_handle,exception);
453   image_ids=(heif_item_id *) NULL;
454   count=(size_t) heif_context_get_number_of_top_level_images(heif_context);
455   if ((status != MagickFalse) && (count > 1))
456     {
457       size_t
458         i;
459 
460       image_ids=(heif_item_id *) AcquireQuantumMemory((size_t) count,
461         sizeof(*image_ids));
462       if (image_ids == (heif_item_id *) NULL)
463         {
464           heif_image_handle_release(image_handle);
465           heif_context_free(heif_context);
466           file_data=RelinquishMagickMemory(file_data);
467           return(DestroyImageList(image));
468         }
469       (void) heif_context_get_list_of_top_level_image_IDs(heif_context,
470         image_ids,(int) count);
471       for (i=0; i < count; i++)
472       {
473         if (image_ids[i] == primary_image_id)
474           continue;
475         /*
476           Allocate next image structure.
477         */
478         AcquireNextImage(image_info,image,exception);
479         if (GetNextImageInList(image) == (Image *) NULL)
480           {
481             status=MagickFalse;
482             break;
483           }
484         image=SyncNextImageInList(image);
485         error=heif_context_get_image_handle(heif_context,primary_image_id,
486           &image_handle);
487         if (IsHeifSuccess(image,&error,exception) == MagickFalse)
488           {
489             status=MagickFalse;
490             break;
491           }
492         status=ReadHEICImageByID(image_info,image,image_handle,exception);
493         if (status == MagickFalse)
494           break;
495         if (image_info->number_scenes != 0)
496           if (image->scene >= (image_info->scene+image_info->number_scenes-1))
497             break;
498       }
499     }
500   heif_image_handle_release(image_handle);
501   error=heif_context_get_image_handle(heif_context,primary_image_id,
502     &image_handle);
503   if (IsHeifSuccess(image,&error,exception) == MagickFalse)
504     {
505       heif_context_free(heif_context);
506       file_data=RelinquishMagickMemory(file_data);
507       return(DestroyImageList(image));
508     }
509   if (heif_image_handle_has_depth_image(image_handle) != 0)
510     {
511       heif_item_id
512         depth_id;
513 
514       int
515         number_images;
516 
517       /*
518         Read depth image.
519       */
520       number_images=heif_image_handle_get_list_of_depth_image_IDs(image_handle,
521         &depth_id,1);
522       if (number_images > 0)
523         {
524           struct heif_image_handle
525             *depth_handle;
526 
527           error=heif_image_handle_get_depth_image_handle(image_handle,depth_id,
528             &depth_handle);
529           if (IsHeifSuccess(image,&error,exception) != MagickFalse)
530             {
531               AcquireNextImage(image_info,image,exception);
532               if (GetNextImageInList(image) == (Image *) NULL)
533                 status=MagickFalse;
534               image=SyncNextImageInList(image);
535               status=ReadHEICImageByID(image_info,image,depth_handle,
536                 exception);
537               heif_image_handle_release(depth_handle);
538             }
539        }
540     }
541   heif_image_handle_release(image_handle);
542   if (image_ids != (heif_item_id *) NULL)
543     (void) RelinquishMagickMemory(image_ids);
544   heif_context_free(heif_context);
545   file_data=RelinquishMagickMemory(file_data);
546   if (status == MagickFalse)
547     return(DestroyImageList(image));
548   return(GetFirstImageInList(image));
549 }
550 #endif
551 
552 /*
553 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
554 %                                                                             %
555 %                                                                             %
556 %                                                                             %
557 %   I s H E I C                                                               %
558 %                                                                             %
559 %                                                                             %
560 %                                                                             %
561 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
562 %
563 %  IsHEIC() returns MagickTrue if the image format type, identified by the
564 %  magick string, is Heic.
565 %
566 %  The format of the IsHEIC method is:
567 %
568 %      MagickBooleanType IsHEIC(const unsigned char *magick,const size_t length)
569 %
570 %  A description of each parameter follows:
571 %
572 %    o magick: compare image format pattern against these bytes.
573 %
574 %    o length: Specifies the length of the magick string.
575 %
576 */
IsHEIC(const unsigned char * magick,const size_t length)577 static MagickBooleanType IsHEIC(const unsigned char *magick,const size_t length)
578 {
579   if (length < 12)
580     return(MagickFalse);
581   if (LocaleNCompare((const char *) magick+4,"ftyp",4) != 0)
582     return(MagickFalse);
583   if (LocaleNCompare((const char *) magick+8,"avif",4) == 0)
584     return(MagickTrue);
585   if (LocaleNCompare((const char *) magick+8,"heic",4) == 0)
586     return(MagickTrue);
587   if (LocaleNCompare((const char *) magick+8,"heix",4) == 0)
588     return(MagickTrue);
589   if (LocaleNCompare((const char *) magick+8,"mif1",4) == 0)
590     return(MagickTrue);
591   return(MagickFalse);
592 }
593 
594 /*
595 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
596 %                                                                             %
597 %                                                                             %
598 %                                                                             %
599 %   R e g i s t e r H E I C I m a g e                                         %
600 %                                                                             %
601 %                                                                             %
602 %                                                                             %
603 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
604 %
605 %  RegisterHEICImage() adds attributes for the HEIC image format to the list of
606 %  supported formats.  The attributes include the image format tag, a method
607 %  to read and/or write the format, whether the format supports the saving of
608 %  more than one frame to the same file or blob, whether the format supports
609 %  native in-memory I/O, and a brief description of the format.
610 %
611 %  The format of the RegisterHEICImage method is:
612 %
613 %      size_t RegisterHEICImage(void)
614 %
615 */
RegisterHEICImage(void)616 ModuleExport size_t RegisterHEICImage(void)
617 {
618   MagickInfo
619     *entry;
620 
621   entry=AcquireMagickInfo("HEIC","HEIC","High Efficiency Image Format");
622 #if defined(MAGICKCORE_HEIC_DELEGATE)
623   entry->decoder=(DecodeImageHandler *) ReadHEICImage;
624 #if LIBHEIF_NUMERIC_VERSION >= 0x01030000
625   if (heif_have_encoder_for_format(heif_compression_HEVC))
626     entry->encoder=(EncodeImageHandler *) WriteHEICImage;
627 #else
628   entry->encoder=(EncodeImageHandler *) WriteHEICImage;
629 #endif
630 #endif
631   entry->magick=(IsImageFormatHandler *) IsHEIC;
632   entry->mime_type=ConstantString("image/heic");
633 #if defined(LIBHEIF_VERSION)
634   entry->version=ConstantString(LIBHEIF_VERSION);
635 #endif
636   entry->flags|=CoderDecoderSeekableStreamFlag;
637   (void) RegisterMagickInfo(entry);
638   entry=AcquireMagickInfo("HEIC","HEIF","High Efficiency Image Format");
639 #if defined(MAGICKCORE_HEIC_DELEGATE)
640   entry->decoder=(DecodeImageHandler *) ReadHEICImage;
641 #if LIBHEIF_NUMERIC_VERSION >= 0x01030000
642   if (heif_have_encoder_for_format(heif_compression_HEVC))
643     entry->encoder=(EncodeImageHandler *) WriteHEICImage;
644 #else
645   entry->encoder=(EncodeImageHandler *) WriteHEICImage;
646 #endif
647 #endif
648   entry->magick=(IsImageFormatHandler *) IsHEIC;
649   entry->mime_type=ConstantString("image/heif");
650 #if defined(LIBHEIF_VERSION)
651   entry->version=ConstantString(LIBHEIF_VERSION);
652 #endif
653   entry->flags|=CoderDecoderSeekableStreamFlag;
654   (void) RegisterMagickInfo(entry);
655 #if LIBHEIF_NUMERIC_VERSION > 0x01060200
656   entry=AcquireMagickInfo("HEIC","AVIF","AV1 Image File Format");
657 #if defined(MAGICKCORE_HEIC_DELEGATE)
658   if (heif_have_decoder_for_format(heif_compression_AV1))
659     entry->decoder=(DecodeImageHandler *) ReadHEICImage;
660   if (heif_have_encoder_for_format(heif_compression_AV1))
661     entry->encoder=(EncodeImageHandler *) WriteHEICImage;
662 #endif
663   entry->magick=(IsImageFormatHandler *) IsHEIC;
664   entry->mime_type=ConstantString("image/avif");
665 #if defined(LIBHEIF_VERSION)
666   entry->version=ConstantString(LIBHEIF_VERSION);
667 #endif
668   entry->flags|=CoderDecoderSeekableStreamFlag;
669   (void) RegisterMagickInfo(entry);
670 #endif
671   return(MagickImageCoderSignature);
672 }
673 
674 /*
675 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
676 %                                                                             %
677 %                                                                             %
678 %                                                                             %
679 %   U n r e g i s t e r H E I C I m a g e                                     %
680 %                                                                             %
681 %                                                                             %
682 %                                                                             %
683 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
684 %
685 %  UnregisterHEICImage() removes format registrations made by the HEIC module
686 %  from the list of supported formats.
687 %
688 %  The format of the UnregisterHEICImage method is:
689 %
690 %      UnregisterHEICImage(void)
691 %
692 */
UnregisterHEICImage(void)693 ModuleExport void UnregisterHEICImage(void)
694 {
695 #if LIBHEIF_NUMERIC_VERSION > 0x01060200
696   (void) UnregisterMagickInfo("AVIF");
697 #endif
698   (void) UnregisterMagickInfo("HEIC");
699   (void) UnregisterMagickInfo("HEIF");
700 }
701 
702 /*
703 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
704 %                                                                             %
705 %                                                                             %
706 %                                                                             %
707 %   W r i t e H E I C I m a g e                                               %
708 %                                                                             %
709 %                                                                             %
710 %                                                                             %
711 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
712 %
713 %  WriteHEICImage() writes an HEIF image using the libheif library.
714 %
715 %  The format of the WriteHEICImage method is:
716 %
717 %      MagickBooleanType WriteHEICImage(const ImageInfo *image_info,
718 %        Image *image)
719 %
720 %  A description of each parameter follows.
721 %
722 %    o image_info: the image info.
723 %
724 %    o image:  The image.
725 %
726 %    o exception:  return any errors or warnings in this structure.
727 %
728 */
729 
730 #if defined(MAGICKCORE_HEIC_DELEGATE)
731 #if LIBHEIF_NUMERIC_VERSION >= 0x01030000
WriteProfile(struct heif_context * context,Image * image,ExceptionInfo * exception)732 static void WriteProfile(struct heif_context *context,Image *image,
733   ExceptionInfo *exception)
734 {
735   const char
736     *name;
737 
738   const StringInfo
739     *profile;
740 
741   ssize_t
742     i;
743 
744   size_t
745     length;
746 
747   struct heif_error
748     error;
749 
750   struct heif_image_handle
751     *image_handle;
752 
753   /*
754     Get image handle.
755   */
756   image_handle=(struct heif_image_handle *) NULL;
757   error=heif_context_get_primary_image_handle(context,&image_handle);
758   if (error.code != 0)
759     return;
760   /*
761     Save image profile as a APP marker.
762   */
763   ResetImageProfileIterator(image);
764   for (name=GetNextImageProfile(image); name != (const char *) NULL; )
765   {
766     profile=GetImageProfile(image,name);
767     length=GetStringInfoLength(profile);
768     if (LocaleCompare(name,"EXIF") == 0)
769       {
770         length=GetStringInfoLength(profile);
771         if (length > 65533L)
772           {
773             (void) ThrowMagickException(exception,GetMagickModule(),
774               CoderWarning,"ExifProfileSizeExceedsLimit","`%s'",
775               image->filename);
776             length=65533L;
777           }
778         (void) heif_context_add_exif_metadata(context,image_handle,
779           (void*) GetStringInfoDatum(profile),(int) length);
780       }
781     if (LocaleCompare(name,"XMP") == 0)
782       {
783         StringInfo
784           *xmp_profile;
785 
786         xmp_profile=StringToStringInfo(xmp_namespace);
787         if (xmp_profile != (StringInfo *) NULL)
788           {
789             if (profile != (StringInfo *) NULL)
790               ConcatenateStringInfo(xmp_profile,profile);
791             GetStringInfoDatum(xmp_profile)[XmpNamespaceExtent]='\0';
792             for (i=0; i < (ssize_t) GetStringInfoLength(xmp_profile); i+=65533L)
793             {
794               length=MagickMin(GetStringInfoLength(xmp_profile)-i,65533L);
795               error=heif_context_add_XMP_metadata(context,image_handle,
796                 (void*) (GetStringInfoDatum(xmp_profile)+i),(int) length);
797               if (error.code != 0)
798                 break;
799             }
800             xmp_profile=DestroyStringInfo(xmp_profile);
801           }
802       }
803     if (image->debug != MagickFalse)
804       (void) LogMagickEvent(CoderEvent,GetMagickModule(),
805         "%s profile: %.20g bytes",name,(double) GetStringInfoLength(profile));
806     name=GetNextImageProfile(image);
807   }
808   heif_image_handle_release(image_handle);
809 }
810 #endif
811 
heif_write_func(struct heif_context * context,const void * data,size_t size,void * userdata)812 static struct heif_error heif_write_func(struct heif_context *context,
813   const void* data,size_t size,void* userdata)
814 {
815   Image
816     *image;
817 
818   struct heif_error
819     error_ok;
820 
821   (void) context;
822   image=(Image*) userdata;
823   (void) WriteBlob(image,size,(const unsigned char *) data);
824   error_ok.code=heif_error_Ok;
825   error_ok.subcode=heif_suberror_Unspecified;
826   error_ok.message="ok";
827   return(error_ok);
828 }
829 
WriteHEICImageYCbCr(Image * image,struct heif_image * heif_image,ExceptionInfo * exception)830 static MagickBooleanType WriteHEICImageYCbCr(Image *image,
831   struct heif_image *heif_image,ExceptionInfo *exception)
832 {
833   int
834     p_y,
835     p_cb,
836     p_cr;
837 
838   MagickBooleanType
839     status;
840 
841   ssize_t
842     y;
843 
844   struct heif_error
845     error;
846 
847   uint8_t
848     *q_y,
849     *q_cb,
850     *q_cr;
851 
852   status=MagickTrue;
853   error=heif_image_add_plane(heif_image,heif_channel_Y,(int) image->columns,
854     (int) image->rows,8);
855   status=IsHeifSuccess(image,&error,exception);
856   if (status == MagickFalse)
857     return(status);
858   error=heif_image_add_plane(heif_image,heif_channel_Cb,
859     ((int) image->columns+1)/2,((int) image->rows+1)/2,8);
860   status=IsHeifSuccess(image,&error,exception);
861   if (status == MagickFalse)
862     return(status);
863   error=heif_image_add_plane(heif_image,heif_channel_Cr,
864     ((int) image->columns+1)/2,((int) image->rows+1)/2,8);
865   status=IsHeifSuccess(image,&error,exception);
866   if (status == MagickFalse)
867     return(status);
868   q_y=heif_image_get_plane(heif_image,heif_channel_Y,&p_y);
869   q_cb=heif_image_get_plane(heif_image,heif_channel_Cb,&p_cb);
870   q_cr=heif_image_get_plane(heif_image,heif_channel_Cr,&p_cr);
871   /*
872     Copy image to heif_image
873   */
874   for (y=0; y < (ssize_t) image->rows; y++)
875   {
876     const Quantum
877       *p;
878 
879     ssize_t
880       x;
881 
882     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
883     if (p == (const Quantum *) NULL)
884       {
885         status=MagickFalse;
886         break;
887       }
888     if ((y & 0x01) != 0)
889       for (x=0; x < (ssize_t) image->columns; x++)
890       {
891         q_y[y*p_y+x]=ScaleQuantumToChar(GetPixelRed(image,p));
892         p+=GetPixelChannels(image);
893       }
894     else
895       for (x=0; x < (ssize_t) image->columns; x+=2)
896       {
897         q_y[y*p_y+x]=ScaleQuantumToChar(GetPixelRed(image,p));
898         q_cb[y/2*p_cb+x/2]=ScaleQuantumToChar(GetPixelGreen(image,p));
899         q_cr[y/2*p_cr+x/2]=ScaleQuantumToChar(GetPixelBlue(image,p));
900         p+=GetPixelChannels(image);
901         if ((x+1) < (ssize_t) image->columns)
902           {
903             q_y[y*p_y+x+1]=ScaleQuantumToChar(GetPixelRed(image,p));
904             p+=GetPixelChannels(image);
905           }
906       }
907     if (image->previous == (Image *) NULL)
908       {
909         status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
910           image->rows);
911         if (status == MagickFalse)
912           break;
913       }
914   }
915   return(status);
916 }
917 
WriteHEICImageRGBA(Image * image,struct heif_image * heif_image,ExceptionInfo * exception)918 static MagickBooleanType WriteHEICImageRGBA(Image *image,
919   struct heif_image *heif_image,ExceptionInfo *exception)
920 {
921   MagickBooleanType
922     status;
923 
924   ssize_t
925     y;
926 
927   const Quantum
928     *p;
929 
930   int
931     stride;
932 
933   struct heif_error
934     error;
935 
936   uint8_t
937     *q,
938     *plane;
939 
940   status=MagickTrue;
941   error=heif_image_add_plane(heif_image,heif_channel_interleaved,
942     (int) image->columns,(int) image->rows,8);
943   status=IsHeifSuccess(image,&error,exception);
944   if (status == MagickFalse)
945     return status;
946   plane=heif_image_get_plane(heif_image,heif_channel_interleaved,&stride);
947   /*
948     Copy image to heif_image
949   */
950   for (y=0; y < (ssize_t) image->rows; y++)
951   {
952     ssize_t
953       x;
954 
955     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
956     if (p == (const Quantum *) NULL)
957       {
958         status=MagickFalse;
959         break;
960       }
961     q=plane+(y*stride);
962     for (x=0; x < (ssize_t) image->columns; x++)
963       {
964         *(q++)=ScaleQuantumToChar(GetPixelRed(image,p));
965         *(q++)=ScaleQuantumToChar(GetPixelGreen(image,p));
966         *(q++)=ScaleQuantumToChar(GetPixelBlue(image,p));
967         if (image->alpha_trait != UndefinedPixelTrait)
968           *(q++)=ScaleQuantumToChar(GetPixelAlpha(image,p));
969 
970         p+=GetPixelChannels(image);
971       }
972     if (image->previous == (Image *) NULL)
973       {
974         status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
975           image->rows);
976         if (status == MagickFalse)
977           break;
978       }
979   }
980   return(status);
981 }
982 
WriteHEICImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)983 static MagickBooleanType WriteHEICImage(const ImageInfo *image_info,
984   Image *image,ExceptionInfo *exception)
985 {
986   MagickBooleanType
987     status;
988 
989   MagickOffsetType
990     scene;
991 
992   struct heif_context
993     *heif_context;
994 
995   struct heif_encoder
996     *heif_encoder;
997 
998   struct heif_error
999     error;
1000 
1001   struct heif_image
1002     *heif_image;
1003 
1004   struct heif_writer
1005     writer;
1006 
1007 #if LIBHEIF_NUMERIC_VERSION > 0x01060200
1008   MagickBooleanType
1009     encode_avif;
1010 #endif
1011 
1012   /*
1013     Open output image file.
1014   */
1015   assert(image_info != (const ImageInfo *) NULL);
1016   assert(image_info->signature == MagickCoreSignature);
1017   assert(image != (Image *) NULL);
1018   assert(image->signature == MagickCoreSignature);
1019   if (image->debug != MagickFalse)
1020     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1021   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
1022   if (status == MagickFalse)
1023     return(status);
1024   scene=0;
1025   heif_context=heif_context_alloc();
1026   heif_image=(struct heif_image*) NULL;
1027   heif_encoder=(struct heif_encoder*) NULL;
1028 #if LIBHEIF_NUMERIC_VERSION > 0x01060200
1029   encode_avif=(LocaleCompare(image_info->magick,"AVIF") == 0) ?
1030     MagickTrue : MagickFalse;
1031 #endif
1032   do
1033   {
1034 #if LIBHEIF_NUMERIC_VERSION >= 0x01040000
1035     const StringInfo
1036       *profile;
1037 #endif
1038 
1039     enum heif_colorspace
1040       colorspace;
1041 
1042     enum heif_chroma
1043       chroma;
1044 
1045     MagickBooleanType
1046       lossless;
1047 
1048     colorspace=heif_colorspace_YCbCr;
1049     lossless=image_info->quality == 100 ? MagickTrue : MagickFalse;
1050     chroma=lossless ? heif_chroma_444 : heif_chroma_420;
1051 
1052 
1053     /*
1054       Get encoder for the specified format.
1055     */
1056 #if LIBHEIF_NUMERIC_VERSION > 0x01060200
1057     if (encode_avif != MagickFalse)
1058       {
1059         error=heif_context_get_encoder_for_format(heif_context,
1060           heif_compression_AV1,&heif_encoder);
1061         if (IssRGBCompatibleColorspace(image->colorspace) != MagickFalse)
1062           {
1063             colorspace=heif_colorspace_RGB;
1064             chroma=(image->alpha_trait == UndefinedPixelTrait) ?
1065               heif_chroma_interleaved_RGB : heif_chroma_interleaved_RGBA;
1066           }
1067       }
1068     else
1069 #endif
1070       error=heif_context_get_encoder_for_format(heif_context,
1071         heif_compression_HEVC,&heif_encoder);
1072     status=IsHeifSuccess(image,&error,exception);
1073     if (status == MagickFalse)
1074       break;
1075     if ((colorspace == heif_colorspace_YCbCr) &&
1076         (image->colorspace != YCbCrColorspace))
1077       {
1078         status=TransformImageColorspace(image,YCbCrColorspace,exception);
1079         if (status == MagickFalse)
1080           break;
1081       }
1082     /*
1083       Initialize HEIF encoder context.
1084     */
1085     error=heif_image_create((int) image->columns,(int) image->rows,colorspace,
1086       chroma,&heif_image);
1087     status=IsHeifSuccess(image,&error,exception);
1088     if (status == MagickFalse)
1089       break;
1090 
1091 #if LIBHEIF_NUMERIC_VERSION >= 0x01040000
1092     profile=GetImageProfile(image,"icc");
1093     if (profile != (StringInfo *) NULL)
1094       (void) heif_image_set_raw_color_profile(heif_image,"prof",
1095         GetStringInfoDatum(profile),GetStringInfoLength(profile));
1096 #endif
1097     if (colorspace == heif_colorspace_YCbCr)
1098       status=WriteHEICImageYCbCr(image,heif_image,exception);
1099     else
1100       status=WriteHEICImageRGBA(image,heif_image,exception);
1101     if (status == MagickFalse)
1102       break;
1103 
1104     /*
1105       Code and actually write the HEIC image
1106     */
1107     if (lossless != MagickFalse)
1108       error=heif_encoder_set_lossless(heif_encoder, 1);
1109     else if (image_info->quality != UndefinedCompressionQuality)
1110       error=heif_encoder_set_lossy_quality(heif_encoder,(int)
1111         image_info->quality);
1112 
1113     status=IsHeifSuccess(image,&error,exception);
1114     if (status == MagickFalse)
1115       break;
1116 
1117 #if LIBHEIF_NUMERIC_VERSION > 0x01060200
1118     if (encode_avif != MagickFalse)
1119       {
1120         const char
1121           *option;
1122 
1123         option=GetImageOption(image_info,"heic:speed");
1124         if (option != (char *) NULL)
1125           {
1126             error=heif_encoder_set_parameter(heif_encoder,"speed",option);
1127             status=IsHeifSuccess(image,&error,exception);
1128             if (status == MagickFalse)
1129               break;
1130           }
1131 
1132         option=GetImageOption(image_info,"heic:chroma");
1133         if (option != (char *) NULL)
1134           {
1135             error=heif_encoder_set_parameter(heif_encoder,"chroma",option);
1136             status=IsHeifSuccess(image,&error,exception);
1137             if (status == MagickFalse)
1138               break;
1139           }
1140       }
1141 #endif
1142 
1143     error=heif_context_encode_image(heif_context,heif_image,heif_encoder,
1144       (const struct heif_encoding_options *) NULL,
1145       (struct heif_image_handle **) NULL);
1146     status=IsHeifSuccess(image,&error,exception);
1147     if (status == MagickFalse)
1148       break;
1149 
1150 #if LIBHEIF_NUMERIC_VERSION >= 0x01030000
1151     if (image->profiles != (void *) NULL)
1152       WriteProfile(heif_context,image,exception);
1153 #endif
1154     if (GetNextImageInList(image) == (Image *) NULL)
1155       break;
1156     image=SyncNextImageInList(image);
1157     status=SetImageProgress(image,SaveImagesTag,scene,
1158       GetImageListLength(image));
1159     if (status == MagickFalse)
1160       break;
1161     heif_encoder_release(heif_encoder);
1162     heif_encoder=(struct heif_encoder*) NULL;
1163     heif_image_release(heif_image);
1164     heif_image=(struct heif_image*) NULL;
1165     scene++;
1166   } while (image_info->adjoin != MagickFalse);
1167   if (status != MagickFalse)
1168     {
1169       writer.writer_api_version=1;
1170       writer.write=heif_write_func;
1171 #if LIBHEIF_NUMERIC_VERSION >= 0x01030000
1172       if (image->profiles != (void *) NULL)
1173         WriteProfile(heif_context,image,exception);
1174 #endif
1175       error=heif_context_write(heif_context,&writer,image);
1176       status=IsHeifSuccess(image,&error,exception);
1177     }
1178   if (heif_encoder != (struct heif_encoder*) NULL)
1179     heif_encoder_release(heif_encoder);
1180   if (heif_image != (struct heif_image*) NULL)
1181     heif_image_release(heif_image);
1182   heif_context_free(heif_context);
1183   (void) CloseBlob(image);
1184   return(status);
1185 }
1186 #endif
1187