You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1187 lines
38 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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