|
|
/* |
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
% % |
|
|
% % |
|
|
% % |
|
|
% 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-2019 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) |
|
|
|
|
|
/* |
|
|
Const declarations. |
|
|
*/ |
|
|
static const char *xmp_namespace = "http://ns.adobe.com/xap/1.0/ "; |
|
|
#define XmpNamespaceExtent 28 |
|
|
|
|
|
/* |
|
|
Forward declarations. |
|
|
*/ |
|
|
|
|
|
#if !defined(MAGICKCORE_WINDOWS_SUPPORT) |
|
|
static MagickBooleanType |
|
|
WriteHEICImage(const ImageInfo *,Image *,ExceptionInfo *); |
|
|
#endif |
|
|
|
|
|
/*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(struct heif_error *error,Image *image, |
|
|
ExceptionInfo *exception) |
|
|
{ |
|
|
if (error->code == 0) |
|
|
return(MagickTrue); |
|
|
|
|
|
ThrowBinaryException(CorruptImageError,error->message,image->filename); |
|
|
} |
|
|
|
|
|
static Image *ReadHEICImage(const ImageInfo *image_info, |
|
|
ExceptionInfo *exception) |
|
|
{ |
|
|
heif_item_id |
|
|
exif_id; |
|
|
|
|
|
Image |
|
|
*image; |
|
|
|
|
|
int |
|
|
count, |
|
|
stride_y, |
|
|
stride_cb, |
|
|
stride_cr; |
|
|
|
|
|
MagickBooleanType |
|
|
status; |
|
|
|
|
|
MagickSizeType |
|
|
length; |
|
|
|
|
|
ssize_t |
|
|
y; |
|
|
|
|
|
struct heif_context |
|
|
*heif_context; |
|
|
|
|
|
struct heif_error |
|
|
error; |
|
|
|
|
|
struct heif_image |
|
|
*heif_image; |
|
|
|
|
|
struct heif_image_handle |
|
|
*image_handle; |
|
|
|
|
|
struct heif_decoding_options |
|
|
*decode_options; |
|
|
|
|
|
uint8_t |
|
|
*p_y, |
|
|
*p_cb, |
|
|
*p_cr; |
|
|
|
|
|
void |
|
|
*file_data; |
|
|
|
|
|
const char |
|
|
*option; |
|
|
|
|
|
/* |
|
|
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)); |
|
|
length=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"); |
|
|
} |
|
|
/* |
|
|
Decode HEIF file |
|
|
*/ |
|
|
heif_context=heif_context_alloc(); |
|
|
error=heif_context_read_from_memory(heif_context,file_data,length,NULL); |
|
|
file_data=RelinquishMagickMemory(file_data); |
|
|
if (IsHeifSuccess(&error,image,exception) == MagickFalse) |
|
|
{ |
|
|
heif_context_free(heif_context); |
|
|
return(DestroyImageList(image)); |
|
|
} |
|
|
image_handle=(struct heif_image_handle *) NULL; |
|
|
error=heif_context_get_primary_image_handle(heif_context,&image_handle); |
|
|
if (IsHeifSuccess(&error,image,exception) == MagickFalse) |
|
|
{ |
|
|
heif_context_free(heif_context); |
|
|
return(DestroyImageList(image)); |
|
|
} |
|
|
/* |
|
|
Read Exif data from HEIC file |
|
|
*/ |
|
|
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 (exif_size > GetBlobSize(image)) |
|
|
{ |
|
|
heif_image_handle_release(image_handle); |
|
|
heif_context_free(heif_context); |
|
|
ThrowReaderException(CorruptImageError, |
|
|
"InsufficientImageDataInFile"); |
|
|
} |
|
|
exif_buffer=(unsigned char *) AcquireMagickMemory(exif_size); |
|
|
if (exif_buffer !=(unsigned char *) NULL) |
|
|
{ |
|
|
error=heif_image_handle_get_metadata(image_handle, |
|
|
exif_id,exif_buffer); |
|
|
if (error.code == 0) |
|
|
{ |
|
|
StringInfo |
|
|
*profile; |
|
|
|
|
|
// The first 4 byte should be skipped since they indicate the |
|
|
// offset to the start of the TIFF header of the Exif data. |
|
|
profile=(StringInfo*) NULL; |
|
|
if (exif_size > 8) |
|
|
profile=BlobToStringInfo(exif_buffer+4,exif_size-4); |
|
|
if (profile != (StringInfo*) NULL) |
|
|
{ |
|
|
SetImageProfile(image,"exif",profile,exception); |
|
|
profile=DestroyStringInfo(profile); |
|
|
} |
|
|
} |
|
|
} |
|
|
exif_buffer=RelinquishMagickMemory(exif_buffer); |
|
|
} |
|
|
/* |
|
|
Set image size |
|
|
*/ |
|
|
image->depth=8; |
|
|
image->columns=(size_t) heif_image_handle_get_width(image_handle); |
|
|
image->rows=(size_t) heif_image_handle_get_height(image_handle); |
|
|
if (image_info->ping != MagickFalse) |
|
|
{ |
|
|
image->colorspace=YCbCrColorspace; |
|
|
heif_image_handle_release(image_handle); |
|
|
heif_context_free(heif_context); |
|
|
return(GetFirstImageInList(image)); |
|
|
} |
|
|
status=SetImageExtent(image,image->columns,image->rows,exception); |
|
|
if (status == MagickFalse) |
|
|
{ |
|
|
heif_image_handle_release(image_handle); |
|
|
heif_context_free(heif_context); |
|
|
return(DestroyImageList(image)); |
|
|
} |
|
|
/* |
|
|
Copy HEIF image into ImageMagick data structures |
|
|
*/ |
|
|
(void) SetImageColorspace(image,YCbCrColorspace,exception); |
|
|
decode_options=(struct heif_decoding_options *) NULL; |
|
|
option=GetImageOption(image_info,"heic:preserve-orientation"); |
|
|
if (IsStringTrue(option) == MagickTrue) |
|
|
{ |
|
|
decode_options=heif_decoding_options_alloc(); |
|
|
decode_options->ignore_transformations=1; |
|
|
} |
|
|
else |
|
|
SetImageProperty(image,"exif:Orientation","1",exception); |
|
|
error=heif_decode_image(image_handle,&heif_image,heif_colorspace_YCbCr, |
|
|
heif_chroma_420,decode_options); |
|
|
if (decode_options != (struct heif_decoding_options *) NULL) |
|
|
{ |
|
|
/* Correct the width and height of the image */ |
|
|
image->columns=(size_t) heif_image_get_width(heif_image,heif_channel_Y); |
|
|
image->rows=(size_t) heif_image_get_height(heif_image,heif_channel_Y); |
|
|
status=SetImageExtent(image,image->columns,image->rows,exception); |
|
|
heif_decoding_options_free(decode_options); |
|
|
} |
|
|
if ((IsHeifSuccess(&error,image,exception) == MagickFalse) || |
|
|
(status == MagickFalse)) |
|
|
{ |
|
|
heif_image_handle_release(image_handle); |
|
|
heif_context_free(heif_context); |
|
|
return(DestroyImageList(image)); |
|
|
} |
|
|
p_y=heif_image_get_plane(heif_image,heif_channel_Y,&stride_y); |
|
|
p_cb=heif_image_get_plane(heif_image,heif_channel_Cb,&stride_cb); |
|
|
p_cr=heif_image_get_plane(heif_image,heif_channel_Cr,&stride_cr); |
|
|
for (y=0; y < (ssize_t) image->rows; y++) |
|
|
{ |
|
|
Quantum |
|
|
*q; |
|
|
|
|
|
register ssize_t |
|
|
x; |
|
|
|
|
|
q=QueueAuthenticPixels(image,0,y,image->columns,1,exception); |
|
|
if (q == (Quantum *) NULL) |
|
|
break; |
|
|
for (x=0; x < (long) image->columns; x++) |
|
|
{ |
|
|
SetPixelRed(image,ScaleCharToQuantum(p_y[y*stride_y + x]),q); |
|
|
SetPixelGreen(image,ScaleCharToQuantum(p_cb[(y/2)*stride_cb + x/2]),q); |
|
|
SetPixelBlue(image,ScaleCharToQuantum(p_cr[(y/2)*stride_cr + x/2]),q); |
|
|
q+=GetPixelChannels(image); |
|
|
} |
|
|
if (SyncAuthenticPixels(image,exception) == MagickFalse) |
|
|
break; |
|
|
} |
|
|
heif_image_release(heif_image); |
|
|
heif_image_handle_release(image_handle); |
|
|
heif_context_free(heif_context); |
|
|
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,"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 !defined(MAGICKCORE_WINDOWS_SUPPORT) |
|
|
entry->encoder=(EncodeImageHandler *) WriteHEICImage; |
|
|
#endif |
|
|
#endif |
|
|
entry->magick=(IsImageFormatHandler *) IsHEIC; |
|
|
entry->mime_type=ConstantString("image/x-heic"); |
|
|
#if defined(LIBHEIF_VERSION) |
|
|
entry->version=ConstantString(LIBHEIF_VERSION); |
|
|
#endif |
|
|
entry->flags|=CoderDecoderSeekableStreamFlag; |
|
|
entry->flags^=CoderAdjoinFlag; |
|
|
(void) RegisterMagickInfo(entry); |
|
|
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) |
|
|
{ |
|
|
(void) UnregisterMagickInfo("HEIC"); |
|
|
} |
|
|
|
|
|
/* |
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|
|
% % |
|
|
% % |
|
|
% % |
|
|
% 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) && !defined(MAGICKCORE_WINDOWS_SUPPORT) |
|
|
static void WriteProfile(struct heif_context* ctx,Image *image, |
|
|
ExceptionInfo *exception) |
|
|
{ |
|
|
const char |
|
|
*name; |
|
|
|
|
|
const StringInfo |
|
|
*profile; |
|
|
|
|
|
MagickBooleanType |
|
|
iptc; |
|
|
|
|
|
register ssize_t |
|
|
i; |
|
|
|
|
|
size_t |
|
|
length; |
|
|
|
|
|
StringInfo |
|
|
*custom_profile; |
|
|
|
|
|
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(ctx,&image_handle); |
|
|
if (error.code != 0) |
|
|
return; |
|
|
|
|
|
/* |
|
|
Save image profile as a APP marker. |
|
|
*/ |
|
|
iptc=MagickFalse; |
|
|
custom_profile=AcquireStringInfo(65535L); |
|
|
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(ctx,image_handle, |
|
|
(void*) GetStringInfoDatum(profile),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(ctx,image_handle, |
|
|
(void*) (GetStringInfoDatum(xmp_profile)+i),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); |
|
|
} |
|
|
custom_profile=DestroyStringInfo(custom_profile); |
|
|
heif_image_handle_release(image_handle); |
|
|
} |
|
|
|
|
|
static struct heif_error heif_write_func(struct heif_context *ctx,const void* data, |
|
|
size_t size,void* userdata) |
|
|
{ |
|
|
Image |
|
|
*image; |
|
|
|
|
|
struct heif_error |
|
|
error_ok; |
|
|
|
|
|
(void) ctx; |
|
|
image=(Image*) userdata; |
|
|
(void) WriteBlob(image,size,data); |
|
|
error_ok.code=heif_error_Ok; |
|
|
error_ok.subcode=heif_suberror_Unspecified; |
|
|
error_ok.message="ok"; |
|
|
return(error_ok); |
|
|
} |
|
|
|
|
|
static MagickBooleanType WriteHEICImage(const ImageInfo *image_info,Image *image, |
|
|
ExceptionInfo *exception) |
|
|
{ |
|
|
long |
|
|
x, |
|
|
y; |
|
|
|
|
|
MagickBooleanType |
|
|
status; |
|
|
|
|
|
MagickOffsetType |
|
|
scene; |
|
|
|
|
|
struct heif_context |
|
|
*heif_context; |
|
|
|
|
|
struct heif_encoder |
|
|
*heif_encoder; |
|
|
|
|
|
struct heif_image |
|
|
*heif_image; |
|
|
|
|
|
/* |
|
|
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; |
|
|
do |
|
|
{ |
|
|
const Quantum |
|
|
*p; |
|
|
|
|
|
int |
|
|
stride_y, |
|
|
stride_cb, |
|
|
stride_cr; |
|
|
|
|
|
struct heif_error |
|
|
error; |
|
|
|
|
|
struct heif_writer |
|
|
writer; |
|
|
|
|
|
uint8_t |
|
|
*p_y, |
|
|
*p_cb, |
|
|
*p_cr; |
|
|
|
|
|
/* |
|
|
Transform colorspace to YCbCr. |
|
|
*/ |
|
|
if (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, |
|
|
heif_colorspace_YCbCr,heif_chroma_420,&heif_image); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
error=heif_image_add_plane(heif_image,heif_channel_Y,(int) image->columns, |
|
|
(int) image->rows,8); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
error=heif_image_add_plane(heif_image,heif_channel_Cb, |
|
|
((int) image->columns+1)/2,((int) image->rows+1)/2,8); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
error=heif_image_add_plane(heif_image,heif_channel_Cr, |
|
|
((int) image->columns+1)/2,((int) image->rows+1)/2,8); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
p_y=heif_image_get_plane(heif_image,heif_channel_Y,&stride_y); |
|
|
p_cb=heif_image_get_plane(heif_image,heif_channel_Cb,&stride_cb); |
|
|
p_cr=heif_image_get_plane(heif_image,heif_channel_Cr,&stride_cr); |
|
|
/* |
|
|
Copy image to heif_image |
|
|
*/ |
|
|
for (y=0; y < (long) image->rows; y++) |
|
|
{ |
|
|
p=GetVirtualPixels(image,0,y,image->columns,1,exception); |
|
|
if (p == (const Quantum *) NULL) |
|
|
{ |
|
|
status=MagickFalse; |
|
|
break; |
|
|
} |
|
|
if ((y & 1)==0) |
|
|
{ |
|
|
for (x=0; x < (long) image->columns; x+=2) |
|
|
{ |
|
|
p_y[y*stride_y+x]=ScaleQuantumToChar(GetPixelRed(image,p)); |
|
|
p_cb[y/2*stride_cb+x/2]=ScaleQuantumToChar(GetPixelGreen(image, |
|
|
p)); |
|
|
p_cr[y/2*stride_cr+x/2]=ScaleQuantumToChar(GetPixelBlue(image, |
|
|
p)); |
|
|
p+=GetPixelChannels(image); |
|
|
|
|
|
if (x+1 < (long) image->columns) |
|
|
{ |
|
|
p_y[y*stride_y + x+1]=ScaleQuantumToChar(GetPixelRed(image, |
|
|
p)); |
|
|
p+=GetPixelChannels(image); |
|
|
} |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
for (x=0; x < (long) image->columns; x++) |
|
|
{ |
|
|
p_y[y*stride_y + x]=ScaleQuantumToChar(GetPixelRed(image,p)); |
|
|
p+=GetPixelChannels(image); |
|
|
} |
|
|
} |
|
|
if (image->previous == (Image *) NULL) |
|
|
{ |
|
|
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y, |
|
|
image->rows); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
} |
|
|
} |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
/* |
|
|
Code and actually write the HEIC image |
|
|
*/ |
|
|
error=heif_context_get_encoder_for_format(heif_context, |
|
|
heif_compression_HEVC,&heif_encoder); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
if (image_info->quality != UndefinedCompressionQuality) |
|
|
{ |
|
|
error=heif_encoder_set_lossy_quality(heif_encoder, |
|
|
(int) image_info->quality); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
} |
|
|
error=heif_context_encode_image(heif_context,heif_image,heif_encoder, |
|
|
(const struct heif_encoding_options*) NULL, |
|
|
(struct heif_image_handle**) NULL); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
writer.writer_api_version=1; |
|
|
writer.write=heif_write_func; |
|
|
|
|
|
if (image->profiles != (void *) NULL) |
|
|
WriteProfile(heif_context, image, exception); |
|
|
|
|
|
error=heif_context_write(heif_context,&writer,image); |
|
|
status=IsHeifSuccess(&error,image,exception); |
|
|
if (status == MagickFalse) |
|
|
break; |
|
|
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 (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
|
|
|
|