| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % H H EEEEE IIIII CCCC % |
| % H H E I C % |
| % HHHHH EEE I C % |
| % H H E I C % |
| % H H EEEEE IIIII CCCC % |
| % % |
| % % |
| % Read/Write Heic Image Format % |
| % % |
| % Dirk Farin % |
| % April 2018 % |
| % % |
| % Copyright 2018 Struktur AG % |
| % % |
| % Anton Kortunov % |
| % December 2017 % |
| % % |
| % Copyright 2017-2018 YANDEX LLC. % |
| % % |
| % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization % |
| % dedicated to making software imaging solutions freely available. % |
| % % |
| % You may not use this file except in compliance with the License. You may % |
| % obtain a copy of the License at % |
| % % |
| % https://imagemagick.org/script/license.php % |
| % % |
| % Unless required by applicable law or agreed to in writing, software % |
| % distributed under the License is distributed on an "AS IS" BASIS, % |
| % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % |
| % See the License for the specific language governing permissions and % |
| % limitations under the License. % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % |
| */ |
| |
| /* |
| Include declarations. |
| */ |
| #include "MagickCore/studio.h" |
| #include "MagickCore/artifact.h" |
| #include "MagickCore/blob.h" |
| #include "MagickCore/blob-private.h" |
| #include "MagickCore/client.h" |
| #include "MagickCore/colorspace-private.h" |
| #include "MagickCore/property.h" |
| #include "MagickCore/display.h" |
| #include "MagickCore/exception.h" |
| #include "MagickCore/exception-private.h" |
| #include "MagickCore/image.h" |
| #include "MagickCore/image-private.h" |
| #include "MagickCore/list.h" |
| #include "MagickCore/magick.h" |
| #include "MagickCore/monitor.h" |
| #include "MagickCore/monitor-private.h" |
| #include "MagickCore/montage.h" |
| #include "MagickCore/transform.h" |
| #include "MagickCore/distort.h" |
| #include "MagickCore/memory_.h" |
| #include "MagickCore/memory-private.h" |
| #include "MagickCore/option.h" |
| #include "MagickCore/pixel-accessor.h" |
| #include "MagickCore/quantum-private.h" |
| #include "MagickCore/static.h" |
| #include "MagickCore/string_.h" |
| #include "MagickCore/string-private.h" |
| #include "MagickCore/module.h" |
| #include "MagickCore/utility.h" |
| #if defined(MAGICKCORE_HEIC_DELEGATE) |
| #if defined(MAGICKCORE_WINDOWS_SUPPORT) |
| #include <heif.h> |
| #else |
| #include <libheif/heif.h> |
| #endif |
| #endif |
| |
| #if defined(MAGICKCORE_HEIC_DELEGATE) |
| /* |
| Define declarations. |
| */ |
| #define XmpNamespaceExtent 28 |
| |
| /* |
| Const declarations. |
| */ |
| static const char |
| xmp_namespace[] = "http://ns.adobe.com/xap/1.0/ "; |
| |
| /* |
| Forward declarations. |
| */ |
| static MagickBooleanType |
| WriteHEICImage(const ImageInfo *,Image *,ExceptionInfo *); |
| |
| /*x |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % R e a d H E I C I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % ReadHEICImage retrieves an image via a file descriptor, decodes the image, |
| % and returns it. It allocates the memory necessary for the new Image |
| % structure and returns a pointer to the new image. |
| % |
| % The format of the ReadHEICImage method is: |
| % |
| % Image *ReadHEICImage(const ImageInfo *image_info, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image_info: the image info. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| static MagickBooleanType IsHeifSuccess(Image *image,struct heif_error *error, |
| ExceptionInfo *exception) |
| { |
| if (error->code == 0) |
| return(MagickTrue); |
| ThrowBinaryException(CorruptImageError,error->message,image->filename); |
| } |
| |
| static MagickBooleanType ReadHEICColorProfile(Image *image, |
| struct heif_image_handle *image_handle,ExceptionInfo *exception) |
| { |
| size_t |
| length; |
| |
| /* |
| Read color profile. |
| */ |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01040000 |
| length=heif_image_handle_get_raw_color_profile_size(image_handle); |
| if (length > 0) |
| { |
| unsigned char |
| *color_buffer; |
| |
| if ((MagickSizeType) length > GetBlobSize(image)) |
| ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile", |
| image->filename); |
| color_buffer=(unsigned char *) AcquireQuantumMemory(1,length); |
| if (color_buffer != (unsigned char *) NULL) |
| { |
| struct heif_error |
| error; |
| |
| error=heif_image_handle_get_raw_color_profile(image_handle, |
| color_buffer); |
| if (error.code == 0) |
| { |
| StringInfo |
| *profile; |
| |
| profile=BlobToStringInfo(color_buffer,length); |
| if (profile != (StringInfo*) NULL) |
| { |
| (void) SetImageProfile(image,"icc",profile,exception); |
| profile=DestroyStringInfo(profile); |
| } |
| } |
| } |
| color_buffer=(unsigned char *) RelinquishMagickMemory(color_buffer); |
| } |
| #endif |
| return(MagickTrue); |
| } |
| |
| static MagickBooleanType ReadHEICExifProfile(Image *image, |
| struct heif_image_handle *image_handle,ExceptionInfo *exception) |
| { |
| heif_item_id |
| exif_id; |
| |
| int |
| count; |
| |
| /* |
| Read Exif profile. |
| */ |
| count=heif_image_handle_get_list_of_metadata_block_IDs(image_handle,"Exif", |
| &exif_id,1); |
| if (count > 0) |
| { |
| size_t |
| exif_size; |
| |
| unsigned char |
| *exif_buffer; |
| |
| exif_size=heif_image_handle_get_metadata_size(image_handle,exif_id); |
| if ((MagickSizeType) exif_size > GetBlobSize(image)) |
| ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile", |
| image->filename); |
| exif_buffer=(unsigned char *) AcquireQuantumMemory(1,exif_size); |
| if (exif_buffer != (unsigned char *) NULL) |
| { |
| struct heif_error |
| error; |
| |
| error=heif_image_handle_get_metadata(image_handle, |
| exif_id,exif_buffer); |
| if (error.code == 0) |
| { |
| StringInfo |
| *profile; |
| |
| /* |
| Skip first 4 bytes, the offset to the TIFF header. |
| */ |
| profile=(StringInfo*) NULL; |
| if (exif_size > 8) |
| profile=BlobToStringInfo(exif_buffer+4,(size_t) exif_size-4); |
| if (profile != (StringInfo*) NULL) |
| { |
| (void) SetImageProfile(image,"exif",profile,exception); |
| profile=DestroyStringInfo(profile); |
| } |
| } |
| } |
| exif_buffer=(unsigned char *) RelinquishMagickMemory(exif_buffer); |
| } |
| return(MagickTrue); |
| } |
| |
| static inline MagickBooleanType HEICSkipImage(const ImageInfo *image_info, |
| Image *image) |
| { |
| if (image_info->number_scenes == 0) |
| return(MagickFalse); |
| if (image->scene == 0) |
| return(MagickFalse); |
| if (image->scene < image_info->scene) |
| return(MagickTrue); |
| if (image->scene > image_info->scene+image_info->number_scenes-1) |
| return(MagickTrue); |
| return(MagickFalse); |
| } |
| |
| static MagickBooleanType ReadHEICImageByID(const ImageInfo *image_info, |
| Image *image,struct heif_image_handle *image_handle, |
| ExceptionInfo *exception) |
| { |
| const uint8_t |
| *p; |
| |
| int |
| stride = 0; |
| |
| MagickBooleanType |
| preserve_orientation, |
| status; |
| |
| ssize_t |
| y; |
| |
| struct heif_decoding_options |
| *decode_options; |
| |
| struct heif_error |
| error; |
| |
| struct heif_image |
| *heif_image; |
| |
| /* |
| Read HEIC image from container. |
| */ |
| image->columns=(size_t) heif_image_handle_get_width(image_handle); |
| image->rows=(size_t) heif_image_handle_get_height(image_handle); |
| image->depth=8; |
| #if LIBHEIF_NUMERIC_VERSION > 0x01040000 |
| { |
| int |
| bits_per_pixel; |
| |
| bits_per_pixel=heif_image_handle_get_luma_bits_per_pixel(image_handle); |
| if (bits_per_pixel != -1) |
| image->depth=(size_t) bits_per_pixel; |
| } |
| #endif |
| if (heif_image_handle_has_alpha_channel(image_handle)) |
| image->alpha_trait=BlendPixelTrait; |
| preserve_orientation=IsStringTrue(GetImageOption(image_info, |
| "heic:preserve-orientation")); |
| if (preserve_orientation == MagickFalse) |
| (void) SetImageProperty(image,"exif:Orientation","1",exception); |
| if (ReadHEICColorProfile(image,image_handle,exception) == MagickFalse) |
| return(MagickFalse); |
| if (ReadHEICExifProfile(image,image_handle,exception) == MagickFalse) |
| return(MagickFalse); |
| if (image_info->ping != MagickFalse) |
| return(MagickTrue); |
| if (HEICSkipImage(image_info,image) != MagickFalse) |
| return(MagickTrue); |
| status=SetImageExtent(image,image->columns,image->rows,exception); |
| if (status == MagickFalse) |
| return(MagickFalse); |
| decode_options=heif_decoding_options_alloc(); |
| #if LIBHEIF_NUMERIC_VERSION > 0x01070000 |
| decode_options->convert_hdr_to_8bit=1; |
| #endif |
| if (preserve_orientation == MagickTrue) |
| decode_options->ignore_transformations=1; |
| error=heif_decode_image(image_handle,&heif_image,heif_colorspace_RGB, |
| image->alpha_trait != UndefinedPixelTrait ? heif_chroma_interleaved_RGBA : |
| heif_chroma_interleaved_RGB,decode_options); |
| heif_decoding_options_free(decode_options); |
| if (IsHeifSuccess(image,&error,exception) == MagickFalse) |
| return(MagickFalse); |
| image->columns=(size_t) heif_image_get_width(heif_image, |
| heif_channel_interleaved); |
| image->rows=(size_t) heif_image_get_height(heif_image |
| ,heif_channel_interleaved); |
| status=SetImageExtent(image,image->columns,image->rows,exception); |
| if (status == MagickFalse) |
| { |
| heif_image_release(heif_image); |
| return(MagickFalse); |
| } |
| p=heif_image_get_plane_readonly(heif_image,heif_channel_interleaved,&stride); |
| stride-=(int) (image->columns * (image->alpha_trait != UndefinedPixelTrait ? |
| 4 : 3)); |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *q; |
| |
| ssize_t |
| x; |
| |
| q=QueueAuthenticPixels(image,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| break; |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| SetPixelRed(image,ScaleCharToQuantum((unsigned char) *(p++)),q); |
| SetPixelGreen(image,ScaleCharToQuantum((unsigned char) *(p++)),q); |
| SetPixelBlue(image,ScaleCharToQuantum((unsigned char) *(p++)),q); |
| if (image->alpha_trait != UndefinedPixelTrait) |
| SetPixelAlpha(image,ScaleCharToQuantum((unsigned char) *(p++)),q); |
| q+=GetPixelChannels(image); |
| } |
| p+=stride; |
| if (SyncAuthenticPixels(image,exception) == MagickFalse) |
| break; |
| } |
| heif_image_release(heif_image); |
| return(MagickTrue); |
| } |
| |
| static Image *ReadHEICImage(const ImageInfo *image_info, |
| ExceptionInfo *exception) |
| { |
| heif_item_id |
| *image_ids, |
| primary_image_id; |
| |
| Image |
| *image; |
| |
| MagickBooleanType |
| status; |
| |
| size_t |
| count, |
| length; |
| |
| struct heif_context |
| *heif_context; |
| |
| struct heif_error |
| error; |
| |
| struct heif_image_handle |
| *image_handle; |
| |
| void |
| *file_data; |
| |
| /* |
| Open image file. |
| */ |
| assert(image_info != (const ImageInfo *) NULL); |
| assert(image_info->signature == MagickCoreSignature); |
| if (image_info->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", |
| image_info->filename); |
| assert(exception != (ExceptionInfo *) NULL); |
| assert(exception->signature == MagickCoreSignature); |
| image=AcquireImage(image_info,exception); |
| status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
| if (status == MagickFalse) |
| return(DestroyImageList(image)); |
| if (GetBlobSize(image) > (MagickSizeType) MAGICK_SSIZE_MAX) |
| ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed"); |
| length=(size_t) GetBlobSize(image); |
| file_data=AcquireMagickMemory(length); |
| if (file_data == (void *) NULL) |
| ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed"); |
| if (ReadBlob(image,length,file_data) != (ssize_t) length) |
| { |
| file_data=RelinquishMagickMemory(file_data); |
| ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile"); |
| } |
| #if LIBHEIF_NUMERIC_VERSION >= 0x010b0000 |
| if (heif_has_compatible_brand(file_data,(int) length,"avif") != MagickFalse) |
| (void) CopyMagickString(image->magick,"AVIF",MagickPathExtent); |
| #endif |
| /* |
| Decode HEIF image. |
| */ |
| heif_context=heif_context_alloc(); |
| error=heif_context_read_from_memory_without_copy(heif_context,file_data, |
| length,NULL); |
| if (IsHeifSuccess(image,&error,exception) == MagickFalse) |
| { |
| heif_context_free(heif_context); |
| file_data=RelinquishMagickMemory(file_data); |
| return(DestroyImageList(image)); |
| } |
| error=heif_context_get_primary_image_ID(heif_context,&primary_image_id); |
| if (IsHeifSuccess(image,&error,exception) == MagickFalse) |
| { |
| heif_context_free(heif_context); |
| file_data=RelinquishMagickMemory(file_data); |
| return(DestroyImageList(image)); |
| } |
| error=heif_context_get_image_handle(heif_context,primary_image_id, |
| &image_handle); |
| if (IsHeifSuccess(image,&error,exception) == MagickFalse) |
| { |
| heif_context_free(heif_context); |
| file_data=RelinquishMagickMemory(file_data); |
| return(DestroyImageList(image)); |
| } |
| status=ReadHEICImageByID(image_info,image,image_handle,exception); |
| image_ids=(heif_item_id *) NULL; |
| count=(size_t) heif_context_get_number_of_top_level_images(heif_context); |
| if ((status != MagickFalse) && (count > 1)) |
| { |
| size_t |
| i; |
| |
| image_ids=(heif_item_id *) AcquireQuantumMemory((size_t) count, |
| sizeof(*image_ids)); |
| if (image_ids == (heif_item_id *) NULL) |
| { |
| heif_image_handle_release(image_handle); |
| heif_context_free(heif_context); |
| file_data=RelinquishMagickMemory(file_data); |
| return(DestroyImageList(image)); |
| } |
| (void) heif_context_get_list_of_top_level_image_IDs(heif_context, |
| image_ids,(int) count); |
| for (i=0; i < count; i++) |
| { |
| if (image_ids[i] == primary_image_id) |
| continue; |
| /* |
| Allocate next image structure. |
| */ |
| AcquireNextImage(image_info,image,exception); |
| if (GetNextImageInList(image) == (Image *) NULL) |
| { |
| status=MagickFalse; |
| break; |
| } |
| image=SyncNextImageInList(image); |
| error=heif_context_get_image_handle(heif_context,primary_image_id, |
| &image_handle); |
| if (IsHeifSuccess(image,&error,exception) == MagickFalse) |
| { |
| status=MagickFalse; |
| break; |
| } |
| status=ReadHEICImageByID(image_info,image,image_handle,exception); |
| if (status == MagickFalse) |
| break; |
| if (image_info->number_scenes != 0) |
| if (image->scene >= (image_info->scene+image_info->number_scenes-1)) |
| break; |
| } |
| } |
| heif_image_handle_release(image_handle); |
| error=heif_context_get_image_handle(heif_context,primary_image_id, |
| &image_handle); |
| if (IsHeifSuccess(image,&error,exception) == MagickFalse) |
| { |
| heif_context_free(heif_context); |
| file_data=RelinquishMagickMemory(file_data); |
| return(DestroyImageList(image)); |
| } |
| if (heif_image_handle_has_depth_image(image_handle) != 0) |
| { |
| heif_item_id |
| depth_id; |
| |
| int |
| number_images; |
| |
| /* |
| Read depth image. |
| */ |
| number_images=heif_image_handle_get_list_of_depth_image_IDs(image_handle, |
| &depth_id,1); |
| if (number_images > 0) |
| { |
| struct heif_image_handle |
| *depth_handle; |
| |
| error=heif_image_handle_get_depth_image_handle(image_handle,depth_id, |
| &depth_handle); |
| if (IsHeifSuccess(image,&error,exception) != MagickFalse) |
| { |
| AcquireNextImage(image_info,image,exception); |
| if (GetNextImageInList(image) == (Image *) NULL) |
| status=MagickFalse; |
| image=SyncNextImageInList(image); |
| status=ReadHEICImageByID(image_info,image,depth_handle, |
| exception); |
| heif_image_handle_release(depth_handle); |
| } |
| } |
| } |
| heif_image_handle_release(image_handle); |
| if (image_ids != (heif_item_id *) NULL) |
| (void) RelinquishMagickMemory(image_ids); |
| heif_context_free(heif_context); |
| file_data=RelinquishMagickMemory(file_data); |
| if (status == MagickFalse) |
| return(DestroyImageList(image)); |
| return(GetFirstImageInList(image)); |
| } |
| #endif |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % I s H E I C % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % IsHEIC() returns MagickTrue if the image format type, identified by the |
| % magick string, is Heic. |
| % |
| % The format of the IsHEIC method is: |
| % |
| % MagickBooleanType IsHEIC(const unsigned char *magick,const size_t length) |
| % |
| % A description of each parameter follows: |
| % |
| % o magick: compare image format pattern against these bytes. |
| % |
| % o length: Specifies the length of the magick string. |
| % |
| */ |
| static MagickBooleanType IsHEIC(const unsigned char *magick,const size_t length) |
| { |
| if (length < 12) |
| return(MagickFalse); |
| if (LocaleNCompare((const char *) magick+4,"ftyp",4) != 0) |
| return(MagickFalse); |
| if (LocaleNCompare((const char *) magick+8,"avif",4) == 0) |
| return(MagickTrue); |
| if (LocaleNCompare((const char *) magick+8,"heic",4) == 0) |
| return(MagickTrue); |
| if (LocaleNCompare((const char *) magick+8,"heix",4) == 0) |
| return(MagickTrue); |
| if (LocaleNCompare((const char *) magick+8,"mif1",4) == 0) |
| return(MagickTrue); |
| return(MagickFalse); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % R e g i s t e r H E I C I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % RegisterHEICImage() adds attributes for the HEIC image format to the list of |
| % supported formats. The attributes include the image format tag, a method |
| % to read and/or write the format, whether the format supports the saving of |
| % more than one frame to the same file or blob, whether the format supports |
| % native in-memory I/O, and a brief description of the format. |
| % |
| % The format of the RegisterHEICImage method is: |
| % |
| % size_t RegisterHEICImage(void) |
| % |
| */ |
| ModuleExport size_t RegisterHEICImage(void) |
| { |
| MagickInfo |
| *entry; |
| |
| entry=AcquireMagickInfo("HEIC","HEIC","High Efficiency Image Format"); |
| #if defined(MAGICKCORE_HEIC_DELEGATE) |
| entry->decoder=(DecodeImageHandler *) ReadHEICImage; |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01030000 |
| if (heif_have_encoder_for_format(heif_compression_HEVC)) |
| entry->encoder=(EncodeImageHandler *) WriteHEICImage; |
| #else |
| entry->encoder=(EncodeImageHandler *) WriteHEICImage; |
| #endif |
| #endif |
| entry->magick=(IsImageFormatHandler *) IsHEIC; |
| entry->mime_type=ConstantString("image/heic"); |
| #if defined(LIBHEIF_VERSION) |
| entry->version=ConstantString(LIBHEIF_VERSION); |
| #endif |
| entry->flags|=CoderDecoderSeekableStreamFlag; |
| (void) RegisterMagickInfo(entry); |
| entry=AcquireMagickInfo("HEIC","HEIF","High Efficiency Image Format"); |
| #if defined(MAGICKCORE_HEIC_DELEGATE) |
| entry->decoder=(DecodeImageHandler *) ReadHEICImage; |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01030000 |
| if (heif_have_encoder_for_format(heif_compression_HEVC)) |
| entry->encoder=(EncodeImageHandler *) WriteHEICImage; |
| #else |
| entry->encoder=(EncodeImageHandler *) WriteHEICImage; |
| #endif |
| #endif |
| entry->magick=(IsImageFormatHandler *) IsHEIC; |
| entry->mime_type=ConstantString("image/heif"); |
| #if defined(LIBHEIF_VERSION) |
| entry->version=ConstantString(LIBHEIF_VERSION); |
| #endif |
| entry->flags|=CoderDecoderSeekableStreamFlag; |
| (void) RegisterMagickInfo(entry); |
| #if LIBHEIF_NUMERIC_VERSION > 0x01060200 |
| entry=AcquireMagickInfo("HEIC","AVIF","AV1 Image File Format"); |
| #if defined(MAGICKCORE_HEIC_DELEGATE) |
| if (heif_have_decoder_for_format(heif_compression_AV1)) |
| entry->decoder=(DecodeImageHandler *) ReadHEICImage; |
| if (heif_have_encoder_for_format(heif_compression_AV1)) |
| entry->encoder=(EncodeImageHandler *) WriteHEICImage; |
| #endif |
| entry->magick=(IsImageFormatHandler *) IsHEIC; |
| entry->mime_type=ConstantString("image/avif"); |
| #if defined(LIBHEIF_VERSION) |
| entry->version=ConstantString(LIBHEIF_VERSION); |
| #endif |
| entry->flags|=CoderDecoderSeekableStreamFlag; |
| (void) RegisterMagickInfo(entry); |
| #endif |
| return(MagickImageCoderSignature); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % U n r e g i s t e r H E I C I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % UnregisterHEICImage() removes format registrations made by the HEIC module |
| % from the list of supported formats. |
| % |
| % The format of the UnregisterHEICImage method is: |
| % |
| % UnregisterHEICImage(void) |
| % |
| */ |
| ModuleExport void UnregisterHEICImage(void) |
| { |
| #if LIBHEIF_NUMERIC_VERSION > 0x01060200 |
| (void) UnregisterMagickInfo("AVIF"); |
| #endif |
| (void) UnregisterMagickInfo("HEIC"); |
| (void) UnregisterMagickInfo("HEIF"); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % W r i t e H E I C I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % WriteHEICImage() writes an HEIF image using the libheif library. |
| % |
| % The format of the WriteHEICImage method is: |
| % |
| % MagickBooleanType WriteHEICImage(const ImageInfo *image_info, |
| % Image *image) |
| % |
| % A description of each parameter follows. |
| % |
| % o image_info: the image info. |
| % |
| % o image: The image. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| #if defined(MAGICKCORE_HEIC_DELEGATE) |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01030000 |
| static void WriteProfile(struct heif_context *context,Image *image, |
| ExceptionInfo *exception) |
| { |
| const char |
| *name; |
| |
| const StringInfo |
| *profile; |
| |
| ssize_t |
| i; |
| |
| size_t |
| length; |
| |
| struct heif_error |
| error; |
| |
| struct heif_image_handle |
| *image_handle; |
| |
| /* |
| Get image handle. |
| */ |
| image_handle=(struct heif_image_handle *) NULL; |
| error=heif_context_get_primary_image_handle(context,&image_handle); |
| if (error.code != 0) |
| return; |
| /* |
| Save image profile as a APP marker. |
| */ |
| ResetImageProfileIterator(image); |
| for (name=GetNextImageProfile(image); name != (const char *) NULL; ) |
| { |
| profile=GetImageProfile(image,name); |
| length=GetStringInfoLength(profile); |
| if (LocaleCompare(name,"EXIF") == 0) |
| { |
| length=GetStringInfoLength(profile); |
| if (length > 65533L) |
| { |
| (void) ThrowMagickException(exception,GetMagickModule(), |
| CoderWarning,"ExifProfileSizeExceedsLimit","`%s'", |
| image->filename); |
| length=65533L; |
| } |
| (void) heif_context_add_exif_metadata(context,image_handle, |
| (void*) GetStringInfoDatum(profile),(int) length); |
| } |
| if (LocaleCompare(name,"XMP") == 0) |
| { |
| StringInfo |
| *xmp_profile; |
| |
| xmp_profile=StringToStringInfo(xmp_namespace); |
| if (xmp_profile != (StringInfo *) NULL) |
| { |
| if (profile != (StringInfo *) NULL) |
| ConcatenateStringInfo(xmp_profile,profile); |
| GetStringInfoDatum(xmp_profile)[XmpNamespaceExtent]='\0'; |
| for (i=0; i < (ssize_t) GetStringInfoLength(xmp_profile); i+=65533L) |
| { |
| length=MagickMin(GetStringInfoLength(xmp_profile)-i,65533L); |
| error=heif_context_add_XMP_metadata(context,image_handle, |
| (void*) (GetStringInfoDatum(xmp_profile)+i),(int) length); |
| if (error.code != 0) |
| break; |
| } |
| xmp_profile=DestroyStringInfo(xmp_profile); |
| } |
| } |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
| "%s profile: %.20g bytes",name,(double) GetStringInfoLength(profile)); |
| name=GetNextImageProfile(image); |
| } |
| heif_image_handle_release(image_handle); |
| } |
| #endif |
| |
| static struct heif_error heif_write_func(struct heif_context *context, |
| const void* data,size_t size,void* userdata) |
| { |
| Image |
| *image; |
| |
| struct heif_error |
| error_ok; |
| |
| (void) context; |
| image=(Image*) userdata; |
| (void) WriteBlob(image,size,(const unsigned char *) data); |
| error_ok.code=heif_error_Ok; |
| error_ok.subcode=heif_suberror_Unspecified; |
| error_ok.message="ok"; |
| return(error_ok); |
| } |
| |
| static MagickBooleanType WriteHEICImageYCbCr(Image *image, |
| struct heif_image *heif_image,ExceptionInfo *exception) |
| { |
| int |
| p_y, |
| p_cb, |
| p_cr; |
| |
| MagickBooleanType |
| status; |
| |
| ssize_t |
| y; |
| |
| struct heif_error |
| error; |
| |
| uint8_t |
| *q_y, |
| *q_cb, |
| *q_cr; |
| |
| status=MagickTrue; |
| error=heif_image_add_plane(heif_image,heif_channel_Y,(int) image->columns, |
| (int) image->rows,8); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| return(status); |
| error=heif_image_add_plane(heif_image,heif_channel_Cb, |
| ((int) image->columns+1)/2,((int) image->rows+1)/2,8); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| return(status); |
| error=heif_image_add_plane(heif_image,heif_channel_Cr, |
| ((int) image->columns+1)/2,((int) image->rows+1)/2,8); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| return(status); |
| q_y=heif_image_get_plane(heif_image,heif_channel_Y,&p_y); |
| q_cb=heif_image_get_plane(heif_image,heif_channel_Cb,&p_cb); |
| q_cr=heif_image_get_plane(heif_image,heif_channel_Cr,&p_cr); |
| /* |
| Copy image to heif_image |
| */ |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *p; |
| |
| ssize_t |
| x; |
| |
| p=GetVirtualPixels(image,0,y,image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| break; |
| } |
| if ((y & 0x01) != 0) |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| q_y[y*p_y+x]=ScaleQuantumToChar(GetPixelRed(image,p)); |
| p+=GetPixelChannels(image); |
| } |
| else |
| for (x=0; x < (ssize_t) image->columns; x+=2) |
| { |
| q_y[y*p_y+x]=ScaleQuantumToChar(GetPixelRed(image,p)); |
| q_cb[y/2*p_cb+x/2]=ScaleQuantumToChar(GetPixelGreen(image,p)); |
| q_cr[y/2*p_cr+x/2]=ScaleQuantumToChar(GetPixelBlue(image,p)); |
| p+=GetPixelChannels(image); |
| if ((x+1) < (ssize_t) image->columns) |
| { |
| q_y[y*p_y+x+1]=ScaleQuantumToChar(GetPixelRed(image,p)); |
| p+=GetPixelChannels(image); |
| } |
| } |
| if (image->previous == (Image *) NULL) |
| { |
| status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y, |
| image->rows); |
| if (status == MagickFalse) |
| break; |
| } |
| } |
| return(status); |
| } |
| |
| static MagickBooleanType WriteHEICImageRGBA(Image *image, |
| struct heif_image *heif_image,ExceptionInfo *exception) |
| { |
| MagickBooleanType |
| status; |
| |
| ssize_t |
| y; |
| |
| const Quantum |
| *p; |
| |
| int |
| stride; |
| |
| struct heif_error |
| error; |
| |
| uint8_t |
| *q, |
| *plane; |
| |
| status=MagickTrue; |
| error=heif_image_add_plane(heif_image,heif_channel_interleaved, |
| (int) image->columns,(int) image->rows,8); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| return status; |
| plane=heif_image_get_plane(heif_image,heif_channel_interleaved,&stride); |
| /* |
| Copy image to heif_image |
| */ |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| ssize_t |
| x; |
| |
| p=GetVirtualPixels(image,0,y,image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| break; |
| } |
| q=plane+(y*stride); |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| *(q++)=ScaleQuantumToChar(GetPixelRed(image,p)); |
| *(q++)=ScaleQuantumToChar(GetPixelGreen(image,p)); |
| *(q++)=ScaleQuantumToChar(GetPixelBlue(image,p)); |
| if (image->alpha_trait != UndefinedPixelTrait) |
| *(q++)=ScaleQuantumToChar(GetPixelAlpha(image,p)); |
| |
| p+=GetPixelChannels(image); |
| } |
| if (image->previous == (Image *) NULL) |
| { |
| status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y, |
| image->rows); |
| if (status == MagickFalse) |
| break; |
| } |
| } |
| return(status); |
| } |
| |
| static MagickBooleanType WriteHEICImage(const ImageInfo *image_info, |
| Image *image,ExceptionInfo *exception) |
| { |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| scene; |
| |
| struct heif_context |
| *heif_context; |
| |
| struct heif_encoder |
| *heif_encoder; |
| |
| struct heif_error |
| error; |
| |
| struct heif_image |
| *heif_image; |
| |
| struct heif_writer |
| writer; |
| |
| #if LIBHEIF_NUMERIC_VERSION > 0x01060200 |
| MagickBooleanType |
| encode_avif; |
| #endif |
| |
| /* |
| Open output image file. |
| */ |
| assert(image_info != (const ImageInfo *) NULL); |
| assert(image_info->signature == MagickCoreSignature); |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception); |
| if (status == MagickFalse) |
| return(status); |
| scene=0; |
| heif_context=heif_context_alloc(); |
| heif_image=(struct heif_image*) NULL; |
| heif_encoder=(struct heif_encoder*) NULL; |
| #if LIBHEIF_NUMERIC_VERSION > 0x01060200 |
| encode_avif=(LocaleCompare(image_info->magick,"AVIF") == 0) ? |
| MagickTrue : MagickFalse; |
| #endif |
| do |
| { |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01040000 |
| const StringInfo |
| *profile; |
| #endif |
| |
| enum heif_colorspace |
| colorspace; |
| |
| enum heif_chroma |
| chroma; |
| |
| MagickBooleanType |
| lossless; |
| |
| colorspace=heif_colorspace_YCbCr; |
| lossless=image_info->quality == 100 ? MagickTrue : MagickFalse; |
| chroma=lossless ? heif_chroma_444 : heif_chroma_420; |
| |
| |
| /* |
| Get encoder for the specified format. |
| */ |
| #if LIBHEIF_NUMERIC_VERSION > 0x01060200 |
| if (encode_avif != MagickFalse) |
| { |
| error=heif_context_get_encoder_for_format(heif_context, |
| heif_compression_AV1,&heif_encoder); |
| if (IssRGBCompatibleColorspace(image->colorspace) != MagickFalse) |
| { |
| colorspace=heif_colorspace_RGB; |
| chroma=(image->alpha_trait == UndefinedPixelTrait) ? |
| heif_chroma_interleaved_RGB : heif_chroma_interleaved_RGBA; |
| } |
| } |
| else |
| #endif |
| error=heif_context_get_encoder_for_format(heif_context, |
| heif_compression_HEVC,&heif_encoder); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| break; |
| if ((colorspace == heif_colorspace_YCbCr) && |
| (image->colorspace != YCbCrColorspace)) |
| { |
| status=TransformImageColorspace(image,YCbCrColorspace,exception); |
| if (status == MagickFalse) |
| break; |
| } |
| /* |
| Initialize HEIF encoder context. |
| */ |
| error=heif_image_create((int) image->columns,(int) image->rows,colorspace, |
| chroma,&heif_image); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| break; |
| |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01040000 |
| profile=GetImageProfile(image,"icc"); |
| if (profile != (StringInfo *) NULL) |
| (void) heif_image_set_raw_color_profile(heif_image,"prof", |
| GetStringInfoDatum(profile),GetStringInfoLength(profile)); |
| #endif |
| if (colorspace == heif_colorspace_YCbCr) |
| status=WriteHEICImageYCbCr(image,heif_image,exception); |
| else |
| status=WriteHEICImageRGBA(image,heif_image,exception); |
| if (status == MagickFalse) |
| break; |
| |
| /* |
| Code and actually write the HEIC image |
| */ |
| if (lossless != MagickFalse) |
| error=heif_encoder_set_lossless(heif_encoder, 1); |
| else if (image_info->quality != UndefinedCompressionQuality) |
| error=heif_encoder_set_lossy_quality(heif_encoder,(int) |
| image_info->quality); |
| |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| break; |
| |
| #if LIBHEIF_NUMERIC_VERSION > 0x01060200 |
| if (encode_avif != MagickFalse) |
| { |
| const char |
| *option; |
| |
| option=GetImageOption(image_info,"heic:speed"); |
| if (option != (char *) NULL) |
| { |
| error=heif_encoder_set_parameter(heif_encoder,"speed",option); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| break; |
| } |
| |
| option=GetImageOption(image_info,"heic:chroma"); |
| if (option != (char *) NULL) |
| { |
| error=heif_encoder_set_parameter(heif_encoder,"chroma",option); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| break; |
| } |
| } |
| #endif |
| |
| error=heif_context_encode_image(heif_context,heif_image,heif_encoder, |
| (const struct heif_encoding_options *) NULL, |
| (struct heif_image_handle **) NULL); |
| status=IsHeifSuccess(image,&error,exception); |
| if (status == MagickFalse) |
| break; |
| |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01030000 |
| if (image->profiles != (void *) NULL) |
| WriteProfile(heif_context,image,exception); |
| #endif |
| if (GetNextImageInList(image) == (Image *) NULL) |
| break; |
| image=SyncNextImageInList(image); |
| status=SetImageProgress(image,SaveImagesTag,scene, |
| GetImageListLength(image)); |
| if (status == MagickFalse) |
| break; |
| heif_encoder_release(heif_encoder); |
| heif_encoder=(struct heif_encoder*) NULL; |
| heif_image_release(heif_image); |
| heif_image=(struct heif_image*) NULL; |
| scene++; |
| } while (image_info->adjoin != MagickFalse); |
| if (status != MagickFalse) |
| { |
| writer.writer_api_version=1; |
| writer.write=heif_write_func; |
| #if LIBHEIF_NUMERIC_VERSION >= 0x01030000 |
| if (image->profiles != (void *) NULL) |
| WriteProfile(heif_context,image,exception); |
| #endif |
| error=heif_context_write(heif_context,&writer,image); |
| status=IsHeifSuccess(image,&error,exception); |
| } |
| if (heif_encoder != (struct heif_encoder*) NULL) |
| heif_encoder_release(heif_encoder); |
| if (heif_image != (struct heif_image*) NULL) |
| heif_image_release(heif_image); |
| heif_context_free(heif_context); |
| (void) CloseBlob(image); |
| return(status); |
| } |
| #endif |