Files
2026-05-21 09:11:32 +02:00

2190 lines
69 KiB
C

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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 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/license/ %
% %
% 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/channel.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/profile-private.h"
#include "MagickCore/quantum-private.h"
#include "MagickCore/resource_.h"
#include "MagickCore/static.h"
#include "MagickCore/string_.h"
#include "MagickCore/string-private.h"
#include "MagickCore/module.h"
#include "MagickCore/utility.h"
#define HEIC_COMPUTE_NUMERIC_VERSION(major,minor,patch) \
(((major) << 24) | ((minor) << 16) | ((patch) << 8) | 0)
#if defined(MAGICKCORE_HEIC_DELEGATE)
#include <libheif/heif.h>
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,17,0)
#include <libheif/heif_properties.h>
#endif
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,20,0)
#include <libheif/heif_sequences.h>
#endif
#endif
#if defined(MAGICKCORE_HEIC_DELEGATE)
/*
Forward declarations.
*/
static MagickBooleanType
WriteHEICImage(const ImageInfo *,Image *,ExceptionInfo *);
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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.
%
*/
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,19,0)
static inline void HEICSetUint32SecurityLimit(const ImageInfo *image_info,
const char *name,uint32_t *value)
{
const char
*option;
option=GetImageOption(image_info,name);
if (option == (const char*) NULL)
return;
*value=strtoul(option,(char **) NULL,10);
}
static inline void HEICSetUint64SecurityLimit(const ImageInfo *image_info,
const char *name,uint64_t *value)
{
const char
*option;
option=GetImageOption(image_info,name);
if (option == (const char*) NULL)
return;
*value=strtoull(option,(char **) NULL,10);
}
static inline void HEICSecurityLimits(const ImageInfo *image_info,
struct heif_context *heif_context)
{
int
height_limit,
max_profile_size,
width_limit;
struct heif_security_limits
*security_limits;
security_limits=heif_context_get_security_limits(heif_context);
width_limit=(int) MagickMin(GetMagickResourceLimit(HeightResource),INT_MAX);
height_limit=(int) MagickMin(GetMagickResourceLimit(WidthResource),INT_MAX);
if (width_limit != INT_MAX || height_limit != INT_MAX)
security_limits->max_image_size_pixels=(uint64_t) width_limit*height_limit;
max_profile_size=(int) MagickMin(GetMaxProfileSize(),INT_MAX);
if (max_profile_size != INT_MAX)
security_limits->max_color_profile_size=max_profile_size;
security_limits->max_memory_block_size=(uint64_t) GetMaxMemoryRequest();
HEICSetUint64SecurityLimit(image_info,"heic:max-number-of-tiles",
&security_limits->max_number_of_tiles);
HEICSetUint32SecurityLimit(image_info,"heic:max-bayer-pattern-pixels",
&security_limits->max_bayer_pattern_pixels);
HEICSetUint32SecurityLimit(image_info,"heic:max-items",
&security_limits->max_items);
HEICSetUint32SecurityLimit(image_info,"heic:max-components",
&security_limits->max_components);
HEICSetUint32SecurityLimit(image_info,"heic:max-iloc-extents-per-item",
&security_limits->max_iloc_extents_per_item);
HEICSetUint32SecurityLimit(image_info,"heic:max-size-entity-group",
&security_limits->max_size_entity_group);
HEICSetUint32SecurityLimit(image_info,"heic:max-children-per-box",
&security_limits->max_children_per_box);
}
#endif
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 inline MagickBooleanType IsHEIFSuccess(Image *image,
struct heif_error *error,ExceptionInfo *exception)
{
if (error->code == 0)
return(MagickTrue);
(void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
error->message,"(%d.%d) `%s'",error->code,error->subcode,image->filename);
return(MagickFalse);
}
static MagickBooleanType ReadHEICColorProfile(Image *image,
struct heif_image_handle *image_handle,ExceptionInfo *exception)
{
size_t
length;
struct heif_error
error;
unsigned char
*color_profile;
/*
Read color profile.
*/
length=heif_image_handle_get_raw_color_profile_size(image_handle);
if (length == 0)
return(MagickTrue);
if ((MagickSizeType) length > GetBlobSize(image))
ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
image->filename);
color_profile=(unsigned char *) AcquireQuantumMemory(1,length);
if (color_profile == (unsigned char *) NULL)
return(MagickFalse);
error=heif_image_handle_get_raw_color_profile(image_handle,color_profile);
if (IsHEIFSuccess(image,&error,exception) != MagickFalse)
{
StringInfo
*profile;
profile=BlobToProfileStringInfo("icc",color_profile,length,exception);
(void) SetImageProfilePrivate(image,profile,exception);
}
color_profile=(unsigned char *) RelinquishMagickMemory(color_profile);
return(MagickTrue);
}
static MagickBooleanType ReadHEICExifProfile(Image *image,
struct heif_image_handle *image_handle,ExceptionInfo *exception)
{
heif_item_id
id;
int
count;
size_t
length;
StringInfo
*exif_profile;
struct heif_error
error;
/*
Read Exif profile.
*/
count=heif_image_handle_get_list_of_metadata_block_IDs(image_handle,"Exif",
&id,1);
if (count != 1)
return(MagickTrue);
length=heif_image_handle_get_metadata_size(image_handle,id);
if (length <= 8)
return(MagickTrue);
if ((MagickSizeType) length > GetBlobSize(image))
ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
image->filename);
exif_profile=AcquireProfileStringInfo("exif",length,exception);
if (exif_profile == (StringInfo*) NULL)
return(MagickTrue);
error=heif_image_handle_get_metadata(image_handle,id,
GetStringInfoDatum(exif_profile));
if ((IsHEIFSuccess(image,&error,exception) != MagickFalse) && (length > 4))
{
StringInfo
*snippet = SplitStringInfo(exif_profile,4);
unsigned char
*datum;
unsigned int
offset = 0;
/*
Extract Exif profile.
*/
datum=GetStringInfoDatum(snippet);
offset|=(unsigned int) (*(datum++)) << 24;
offset|=(unsigned int) (*(datum++)) << 16;
offset|=(unsigned int) (*(datum++)) << 8;
offset|=(unsigned int) (*(datum++)) << 0;
snippet=DestroyStringInfo(snippet);
/*
Strip any EOI marker if payload starts with a JPEG marker.
*/
length=GetStringInfoLength(exif_profile);
datum=GetStringInfoDatum(exif_profile);
if ((length > 2) &&
((memcmp(datum,"\xff\xd8",2) == 0) ||
(memcmp(datum,"\xff\xe1",2) == 0)) &&
(memcmp(datum+length-2,"\xff\xd9",2) == 0))
SetStringInfoLength(exif_profile,length-2);
/*
Skip to actual Exif payload.
*/
if (offset < GetStringInfoLength(exif_profile))
{
(void) DestroyStringInfo(SplitStringInfo(exif_profile,offset));
(void) SetImageProfilePrivate(image,exif_profile,exception);
exif_profile=(StringInfo *) NULL;
}
}
if (exif_profile != (StringInfo *) NULL)
exif_profile=DestroyStringInfo(exif_profile);
return(MagickTrue);
}
static MagickBooleanType ReadHEICXMPProfile(Image *image,
struct heif_image_handle *image_handle,ExceptionInfo *exception)
{
heif_item_id
id;
int
count;
size_t
length;
struct heif_error
error;
unsigned char
*xmp_profile;
/*
Read XMP profile.
*/
count=heif_image_handle_get_list_of_metadata_block_IDs(image_handle,"mime",
&id,1);
if (count != 1)
return(MagickTrue);
length=heif_image_handle_get_metadata_size(image_handle,id);
if (length <= 8)
return(MagickTrue);
if ((MagickSizeType) length > GetBlobSize(image))
ThrowBinaryException(CorruptImageError,"InsufficientImageDataInFile",
image->filename);
xmp_profile=(unsigned char *) AcquireQuantumMemory(1,length);
if (xmp_profile == (unsigned char *) NULL)
return(MagickFalse);
error=heif_image_handle_get_metadata(image_handle,id,xmp_profile);
if (IsHEIFSuccess(image,&error,exception) != MagickFalse)
{
StringInfo
*profile;
profile=BlobToProfileStringInfo("xmp",xmp_profile,length,exception);
(void) SetImageProfilePrivate(image,profile,exception);
}
xmp_profile=(unsigned char *) RelinquishMagickMemory(xmp_profile);
return(MagickTrue);
}
static MagickBooleanType ReadHEICImageHandle(const ImageInfo *image_info,
Image *image,struct heif_context *heif_context,
struct heif_image_handle *image_handle,ExceptionInfo *exception)
{
const uint8_t
*p,
*pixels;
enum heif_channel
channel;
enum heif_chroma
chroma;
int
bits_per_pixel,
shift,
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);
if (heif_image_handle_has_alpha_channel(image_handle) != 0)
image->alpha_trait=BlendPixelTrait;
image->depth=8;
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;
preserve_orientation=IsStringTrue(GetImageOption(image_info,
"heic:preserve-orientation"));
if (preserve_orientation == MagickFalse)
(void) SetImageProperty(image,"exif:Orientation","1",exception);
else
{
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,17,0)
enum heif_item_property_type
type = heif_item_property_type_invalid;
heif_item_id
item_id;
heif_property_id
transforms[1];
int
count;
item_id=heif_image_handle_get_item_id(image_handle);
count=heif_item_get_transformation_properties(heif_context,item_id,
transforms,1);
if (count == 1)
type=heif_item_get_property_type(heif_context,item_id,transforms[0]);
if (count == 1 && ((type == heif_item_property_type_transform_mirror) ||
(type == heif_item_property_type_transform_rotation)))
{
enum heif_transform_mirror_direction
mirror;
int
rotation_ccw;
mirror=heif_item_get_property_transform_mirror(heif_context,item_id,
transforms[0]);
rotation_ccw=heif_item_get_property_transform_rotation_ccw(
heif_context,item_id,transforms[0]);
switch(mirror)
{
case heif_transform_mirror_direction_horizontal:
{
if (rotation_ccw == 0)
image->orientation=TopRightOrientation;
else if (rotation_ccw == 270)
image->orientation=LeftTopOrientation;
break;
}
case heif_transform_mirror_direction_vertical:
{
if (rotation_ccw == 0)
image->orientation=BottomLeftOrientation;
else if (rotation_ccw == 270)
image->orientation=RightBottomOrientation;
break;
}
case heif_transform_mirror_direction_invalid:
{
if (rotation_ccw == 0)
image->orientation=TopLeftOrientation;
else if (rotation_ccw == 90)
image->orientation=LeftBottomOrientation;
else if (rotation_ccw == 180)
image->orientation=BottomRightOrientation;
else if (rotation_ccw == 270)
image->orientation=RightTopOrientation;
break;
}
}
}
#endif
image->columns=(size_t) heif_image_handle_get_ispe_width(image_handle);
image->rows=(size_t) heif_image_handle_get_ispe_height(image_handle);
}
if (ReadHEICColorProfile(image,image_handle,exception) == MagickFalse)
return(MagickFalse);
if (ReadHEICExifProfile(image,image_handle,exception) == MagickFalse)
return(MagickFalse);
if (ReadHEICXMPProfile(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 >= HEIC_COMPUTE_NUMERIC_VERSION(1,16,0)
{
const char
*option;
option=GetImageOption(image_info,"heic:chroma-upsampling");
if (option != (char *) NULL)
{
if (LocaleCompare(option,"nearest-neighbor") == 0)
{
decode_options->color_conversion_options.
only_use_preferred_chroma_algorithm=1;
decode_options->color_conversion_options.
preferred_chroma_upsampling_algorithm=
heif_chroma_upsampling_nearest_neighbor;
}
else if (LocaleCompare(option,"bilinear") == 0)
{
decode_options->color_conversion_options.
only_use_preferred_chroma_algorithm=1;
decode_options->color_conversion_options.
preferred_chroma_upsampling_algorithm=
heif_chroma_upsampling_bilinear;
}
}
}
#endif
if (preserve_orientation != MagickFalse)
decode_options->ignore_transformations=1;
if (image->alpha_trait != UndefinedPixelTrait)
{
chroma=heif_chroma_interleaved_RGBA;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBBAA_LE;
}
else
{
chroma=heif_chroma_interleaved_RGB;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBB_LE;
}
error=heif_decode_image(image_handle,&heif_image,heif_colorspace_RGB,chroma,
decode_options);
heif_decoding_options_free(decode_options);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
return(MagickFalse);
channel=heif_channel_interleaved;
image->columns=(size_t) heif_image_get_width(heif_image,channel);
image->rows=(size_t) heif_image_get_height(heif_image,channel);
status=SetImageExtent(image,image->columns,image->rows,exception);
if (status == MagickFalse)
{
heif_image_release(heif_image);
return(MagickFalse);
}
pixels=heif_image_get_plane_readonly(heif_image,channel,&stride);
if (pixels == (const uint8_t *) NULL)
{
heif_image_release(heif_image);
return(MagickFalse);
}
shift=(int) (16-image->depth);
if (image->depth <= 8)
for (y=0; y < (ssize_t) image->rows; y++)
{
Quantum
*q;
ssize_t
x;
/*
Transform 8-bit image.
*/
q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
p=pixels+(y*stride);
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+=(ptrdiff_t) GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
}
else
for (y=0; y < (ssize_t) image->rows; y++)
{
Quantum
*q;
ssize_t
x;
/*
Transform 10-bit or 12-bit image.
*/
q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
p=pixels+(y*stride);
for (x=0; x < (ssize_t) image->columns; x++)
{
unsigned short pixel = (((unsigned short) *(p+1) << 8) |
(*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelRed(image,ScaleShortToQuantum(pixel),q);
pixel=(((unsigned short) *(p+1) << 8) | (*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelGreen(image,ScaleShortToQuantum(pixel),q);
pixel=(((unsigned short) *(p+1) << 8) | (*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelBlue(image,ScaleShortToQuantum(pixel),q);
if (image->alpha_trait != UndefinedPixelTrait)
{
pixel=(((unsigned short) *(p+1) << 8) | (*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelAlpha(image,ScaleShortToQuantum(pixel),q);
}
q+=(ptrdiff_t) GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
}
heif_image_release(heif_image);
return(MagickTrue);
}
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,20,0)
static MagickBooleanType ReadHEICSequenceFrames(const ImageInfo *image_info,
Image *image,struct heif_context *heif_context,ExceptionInfo *exception)
{
const uint8_t
*p,
*pixels;
enum heif_channel
channel;
enum heif_chroma
chroma;
heif_track
*track;
int
bits_per_pixel,
has_alpha = 0,
shift,
stride = 0;
MagickBooleanType
status;
size_t
scene;
struct heif_decoding_options
*decode_options;
struct heif_error
error;
struct heif_image
*heif_image;
uint16_t
track_width,
track_height;
uint32_t
timescale;
/*
Get the first visual track from the sequence.
*/
track=heif_context_get_track(heif_context,0);
if (track == (heif_track *) NULL)
return(MagickFalse);
error=heif_track_get_image_resolution(track,&track_width,&track_height);
if (error.code != heif_error_Ok)
{
heif_track_release(track);
return(MagickFalse);
}
timescale=heif_track_get_timescale(track);
if (timescale == 0)
timescale=1;
decode_options=heif_decoding_options_alloc();
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,21,0)
decode_options->ignore_sequence_editlist=1;
/*
Detect alpha from the track and set up chroma format.
*/
has_alpha=heif_track_has_alpha_channel(track);
image->alpha_trait=UndefinedPixelTrait;
if (has_alpha != 0)
image->alpha_trait=BlendPixelTrait;
#endif
image->depth=8;
if (image->alpha_trait != UndefinedPixelTrait)
{
chroma=heif_chroma_interleaved_RGBA;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBBAA_LE;
}
else
{
chroma=heif_chroma_interleaved_RGB;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBB_LE;
}
scene=0;
status=MagickTrue;
for ( ; ; )
{
ssize_t
y;
uint32_t
duration;
if (AcquireMagickResource(ListLengthResource,scene+1) == MagickFalse)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"ListLengthExceedsLimit","`%s'",
image_info->filename);
status=MagickFalse;
break;
}
heif_image=(struct heif_image *) NULL;
error=heif_track_decode_next_image(track,&heif_image,heif_colorspace_RGB,
chroma,decode_options);
if (error.code == heif_error_End_of_sequence)
break;
if (error.code != heif_error_Ok)
{
(void) ThrowMagickException(exception,GetMagickModule(),
CorruptImageError,error.message,"(%d.%d) `%s'",error.code,
error.subcode,image->filename);
status=MagickFalse;
break;
}
/*
Allocate next image for frames beyond the first.
*/
if (scene > 0)
{
AcquireNextImage(image_info,image,exception);
if (GetNextImageInList(image) == (Image *) NULL)
{
heif_image_release(heif_image);
status=MagickFalse;
break;
}
image=SyncNextImageInList(image);
}
image->scene=scene;
/*
Set frame timing: convert track timescale ticks to ticks_per_second.
*/
duration=heif_image_get_duration(heif_image);
image->ticks_per_second=(ssize_t) timescale;
image->delay=(size_t) duration;
image->iterations=0;
/*
Set image dimensions from the decoded frame.
*/
channel=heif_channel_interleaved;
image->columns=(size_t) heif_image_get_width(heif_image,channel);
image->rows=(size_t) heif_image_get_height(heif_image,channel);
bits_per_pixel=heif_image_get_bits_per_pixel_range(heif_image,channel);
if (bits_per_pixel > 0)
image->depth=(size_t) bits_per_pixel;
if (has_alpha != 0)
{
image->alpha_trait=BlendPixelTrait;
image->dispose=BackgroundDispose;
}
if ((image_info->ping != MagickFalse) ||
(HEICSkipImage(image_info,image) != MagickFalse))
{
heif_image_release(heif_image);
scene++;
if (image_info->number_scenes != 0)
if (image->scene >= (image_info->scene+image_info->number_scenes-1))
break;
continue;
}
status=SetImageExtent(image,image->columns,image->rows,exception);
if (status == MagickFalse)
{
heif_image_release(heif_image);
break;
}
pixels=heif_image_get_plane_readonly(heif_image,channel,&stride);
if (pixels == (const uint8_t *) NULL)
{
heif_image_release(heif_image);
status=MagickFalse;
break;
}
shift=(int) (16-image->depth);
if (image->depth <= 8)
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;
p=pixels+(y*stride);
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+=(ptrdiff_t) GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
}
else
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;
p=pixels+(y*stride);
for (x=0; x < (ssize_t) image->columns; x++)
{
unsigned short pixel = (((unsigned short) *(p+1) << 8) |
(*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelRed(image,ScaleShortToQuantum(pixel),q);
pixel=(((unsigned short) *(p+1) << 8) | (*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelGreen(image,ScaleShortToQuantum(pixel),q);
pixel=(((unsigned short) *(p+1) << 8) | (*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelBlue(image,ScaleShortToQuantum(pixel),q);
if (image->alpha_trait != UndefinedPixelTrait)
{
pixel=(((unsigned short) *(p+1) << 8) | (*(p+0))) << shift; p+=(ptrdiff_t) 2;
SetPixelAlpha(image,ScaleShortToQuantum(pixel),q);
}
q+=(ptrdiff_t) GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
}
heif_image_release(heif_image);
scene++;
if (image_info->number_scenes != 0)
if (image->scene >= (image_info->scene+image_info->number_scenes-1))
break;
}
heif_decoding_options_free(decode_options);
heif_track_release(track);
return(status);
}
#endif
static void ReadHEICDepthImage(const ImageInfo *image_info,Image *image,
struct heif_context *heif_context,struct heif_image_handle *image_handle,
ExceptionInfo *exception)
{
const char
*option;
heif_item_id
depth_id;
int
number_images;
struct heif_error
error;
struct heif_image_handle
*depth_handle;
/*
Read HEIF depth image.
*/
option=GetImageOption(image_info,"heic:depth-image");
if (IsStringTrue(option) == MagickFalse)
return;
if (heif_image_handle_has_depth_image(image_handle) == 0)
return;
number_images=heif_image_handle_get_list_of_depth_image_IDs(image_handle,
&depth_id,1);
if (number_images != 1)
return;
error=heif_image_handle_get_depth_image_handle(image_handle,depth_id,
&depth_handle);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
return;
AcquireNextImage(image_info,image,exception);
if (GetNextImageInList(image) != (Image *) NULL)
{
image=SyncNextImageInList(image);
(void) ReadHEICImageHandle(image_info,image,heif_context,depth_handle,exception);
}
heif_image_handle_release(depth_handle);
}
static Image *ReadHEICImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
heif_item_id
primary_image_id;
Image
*image;
MagickBooleanType
status;
ssize_t
count;
struct heif_context
*heif_context;
struct heif_error
error;
struct heif_image_handle
*image_handle;
unsigned char
magic[128];
/*
Open image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickCoreSignature);
if (IsEventLogging() != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
image_info->filename);
image=AcquireImage(image_info,exception);
status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
if (status == MagickFalse)
return(DestroyImageList(image));
if (ReadBlob(image,sizeof(magic),magic) != sizeof(magic))
ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,18,0)
error=heif_has_compatible_filetype(magic,sizeof(magic));
if (error.code != heif_error_Ok)
#else
if (heif_check_filetype(magic,sizeof(magic)) == heif_filetype_no)
#endif
ThrowReaderException(CoderError,"ImageTypeNotSupported");
(void) CloseBlob(image);
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,11,0)
if (heif_has_compatible_brand(magic,sizeof(magic), "avif") == 1)
(void) CopyMagickString(image->magick,"AVIF",MagickPathExtent);
#endif
/*
Decode HEIF image.
*/
heif_context=heif_context_alloc();
if (heif_context == (struct heif_context *) NULL)
ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,19,0)
HEICSecurityLimits(image_info,heif_context);
#endif
error=heif_context_read_from_file(heif_context,image->filename,
(const struct heif_reading_options *) NULL);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
{
heif_context_free(heif_context);
return(DestroyImageList(image));
}
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,20,0)
/*
Check for image sequence (animated AVIF) and decode via track API.
*/
if (heif_context_has_sequence(heif_context) != 0)
{
status=ReadHEICSequenceFrames(image_info,image,heif_context,exception);
heif_context_free(heif_context);
if (status == MagickFalse)
return(DestroyImageList(image));
return(GetFirstImageInList(image));
}
#endif
error=heif_context_get_primary_image_ID(heif_context,&primary_image_id);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
{
heif_context_free(heif_context);
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);
return(DestroyImageList(image));
}
status=ReadHEICImageHandle(image_info,image,heif_context,image_handle,
exception);
heif_image_handle_release(image_handle);
count=(ssize_t) heif_context_get_number_of_top_level_images(heif_context);
if ((status != MagickFalse) && (count > 1))
{
heif_item_id
*ids;
ssize_t
i;
ids=(heif_item_id *) AcquireQuantumMemory((size_t) count,sizeof(*ids));
if (ids == (heif_item_id *) NULL)
{
heif_context_free(heif_context);
return(DestroyImageList(image));
}
(void) heif_context_get_list_of_top_level_image_IDs(heif_context,ids,
(int) count);
for (i=0; i < count; i++)
{
if (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,ids[i],&image_handle);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
{
status=MagickFalse;
break;
}
status=ReadHEICImageHandle(image_info,image,heif_context,image_handle,
exception);
heif_image_handle_release(image_handle);
if (status == MagickFalse)
break;
if (image_info->number_scenes != 0)
if (image->scene >= (image_info->scene+image_info->number_scenes-1))
break;
}
ids=(heif_item_id *) RelinquishMagickMemory(ids);
}
error=heif_context_get_image_handle(heif_context,primary_image_id,
&image_handle);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
{
heif_context_free(heif_context);
return(DestroyImageList(image));
}
ReadHEICDepthImage(image_info,image,heif_context,image_handle,exception);
heif_image_handle_release(image_handle);
heif_context_free(heif_context);
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 defined(MAGICKCORE_HEIC_DELEGATE)
enum heif_filetype_result
type;
if (length < 12)
return(MagickFalse);
type=heif_check_filetype(magick,(int) length);
if (type == heif_filetype_yes_supported)
return(MagickTrue);
#else
magick_unreferenced(magick);
magick_unreferenced(length);
#endif
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;
#if defined(MAGICKCORE_HEIC_DELEGATE)
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,14,0)
heif_init((struct heif_init_params *) NULL);
#endif
#endif
entry=AcquireMagickInfo("HEIC","HEIC","High Efficiency Image Format");
#if defined(MAGICKCORE_HEIC_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadHEICImage;
if (heif_have_encoder_for_format(heif_compression_HEVC))
entry->encoder=(EncodeImageHandler *) WriteHEICImage;
#endif
entry->magick=(IsImageFormatHandler *) IsHEIC;
entry->mime_type=ConstantString("image/heic");
#if defined(LIBHEIF_VERSION)
entry->version=ConstantString(LIBHEIF_VERSION);
#endif
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderBlobSupportFlag;
(void) RegisterMagickInfo(entry);
entry=AcquireMagickInfo("HEIC","HEIF","High Efficiency Image Format");
#if defined(MAGICKCORE_HEIC_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadHEICImage;
if (heif_have_encoder_for_format(heif_compression_HEVC))
entry->encoder=(EncodeImageHandler *) WriteHEICImage;
#endif
entry->magick=(IsImageFormatHandler *) IsHEIC;
entry->mime_type=ConstantString("image/heif");
#if defined(LIBHEIF_VERSION)
entry->version=ConstantString(LIBHEIF_VERSION);
#endif
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderBlobSupportFlag;
(void) RegisterMagickInfo(entry);
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,7,0)
entry=AcquireMagickInfo("HEIC","AVCI","AVC Image File Format");
#if defined(MAGICKCORE_HEIC_DELEGATE)
if (heif_have_decoder_for_format(heif_compression_AVC))
entry->decoder=(DecodeImageHandler *) ReadHEICImage;
if (heif_have_encoder_for_format(heif_compression_AVC))
entry->encoder=(EncodeImageHandler *) WriteHEICImage;
#endif
entry->magick=(IsImageFormatHandler *) IsHEIC;
entry->mime_type=ConstantString("image/avci");
#if defined(LIBHEIF_VERSION)
entry->version=ConstantString(LIBHEIF_VERSION);
#endif
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderBlobSupportFlag;
(void) RegisterMagickInfo(entry);
#endif
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,7,0)
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;
entry->flags^=CoderBlobSupportFlag;
(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 >= HEIC_COMPUTE_NUMERIC_VERSION(1,7,0)
(void) UnregisterMagickInfo("AVIF");
#endif
(void) UnregisterMagickInfo("HEIC");
(void) UnregisterMagickInfo("HEIF");
#if defined(MAGICKCORE_HEIC_DELEGATE)
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,14,0)
heif_deinit();
#endif
#endif
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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)
static void WriteProfile(struct heif_context *context,Image *image,
ExceptionInfo *exception)
{
const char
*name;
const StringInfo
*profile;
size_t
length;
ssize_t
i;
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 (IsHEIFSuccess(image,&error,exception) == MagickFalse)
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)
(void) heif_context_add_exif_metadata(context,image_handle,
(void*) GetStringInfoDatum(profile),(int) length);
if (LocaleCompare(name,"XMP") == 0)
for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65533L)
{
length=(size_t) MagickMin((ssize_t) GetStringInfoLength(profile)-i,
65533L);
error=heif_context_add_XMP_metadata(context,image_handle,
(void*) (GetStringInfoDatum(profile)+i),(int) length);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
break;
}
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);
}
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_cb,
p_cr,
p_y;
MagickBooleanType
status;
ssize_t
y;
struct heif_error
error;
uint8_t
*q_cb,
*q_cr,
*q_y;
/*
Transform HEIF YCbCr image.
*/
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);
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+=(ptrdiff_t) 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+=(ptrdiff_t) GetPixelChannels(image);
if ((x+1) < (ssize_t) image->columns)
{
q_y[y*p_y+x+1]=ScaleQuantumToChar(GetPixelRed(image,p));
p+=(ptrdiff_t) 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)
{
const Quantum
*p;
enum heif_channel
channel;
int
stride;
MagickBooleanType
status;
ssize_t
y;
struct heif_error
error;
uint8_t
*pixels,
*q;
/*
Transform HEIF RGBA image.
*/
status=MagickTrue;
channel=heif_channel_interleaved;
if (GetPixelChannels(image) == 1)
channel=heif_channel_Y;
error=heif_image_add_plane(heif_image,channel,(int) image->columns,
(int) image->rows,8);
status=IsHEIFSuccess(image,&error,exception);
if (status == MagickFalse)
return(status);
pixels=heif_image_get_plane(heif_image,channel,&stride);
if (pixels == (uint8_t *) NULL)
return(MagickFalse);
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=pixels+(y*stride);
for (x=0; x < (ssize_t) image->columns; x++)
{
*(q++)=ScaleQuantumToChar(GetPixelRed(image,p));
if (GetPixelChannels(image) > 1)
{
*(q++)=ScaleQuantumToChar(GetPixelGreen(image,p));
*(q++)=ScaleQuantumToChar(GetPixelBlue(image,p));
if ((image->alpha_trait & BlendPixelTrait) != 0)
*(q++)=ScaleQuantumToChar(GetPixelAlpha(image,p));
}
p+=(ptrdiff_t) GetPixelChannels(image);
}
if (image->previous == (Image *) NULL)
{
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
}
return(status);
}
static MagickBooleanType WriteHEICImageRRGGBBAA(Image *image,
struct heif_image *heif_image,ExceptionInfo *exception)
{
const Quantum
*p;
enum heif_channel
channel = heif_channel_interleaved;
int
depth,
shift,
stride;
MagickBooleanType
status = MagickTrue;
ssize_t
y;
struct heif_error
error;
uint8_t
*pixels,
*q;
/*
Transform HEIF RGBA image with depth > 8.
*/
depth=image->depth > 10 ? 12 : 10;
if (GetPixelChannels(image) == 1)
channel=heif_channel_Y;
error=heif_image_add_plane(heif_image,channel,(int) image->columns,
(int) image->rows,depth);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
return(MagickFalse);
status=IsHEIFSuccess(image,&error,exception);
if (status == MagickFalse)
return(status);
pixels=heif_image_get_plane(heif_image,channel,&stride);
if (pixels == (uint8_t *) NULL)
return(MagickFalse);
shift=(int) (16-depth);
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=pixels+(y*stride);
for (x=0; x < (ssize_t) image->columns; x++)
{
int pixel=ScaleQuantumToShort(GetPixelRed(image,p)) >> shift;
*(q++)=(uint8_t) (pixel & 0xff);
*(q++)=(uint8_t) (pixel >> 8);
if (GetPixelChannels(image) > 1)
{
pixel=ScaleQuantumToShort(GetPixelGreen(image,p)) >> shift;
*(q++)=(uint8_t) (pixel & 0xff);
*(q++)=(uint8_t) (pixel >> 8);
pixel=ScaleQuantumToShort(GetPixelBlue(image,p)) >> shift;
*(q++)=(uint8_t) (pixel & 0xff);
*(q++)=(uint8_t) (pixel >> 8);
if ((image->alpha_trait & BlendPixelTrait) != 0)
{
pixel=ScaleQuantumToShort(GetPixelAlpha(image,p)) >> shift;
*(q++)=(uint8_t) (pixel & 0xff);
*(q++)=(uint8_t) (pixel >> 8);
}
}
p+=(ptrdiff_t) GetPixelChannels(image);
}
if (image->previous == (Image *) NULL)
{
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
}
return(status);
}
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,20,0)
static MagickBooleanType WriteHEICSequenceImage(const ImageInfo *image_info,
Image *image,ExceptionInfo *exception)
{
const char
*option;
enum heif_chroma
chroma;
enum heif_colorspace
colorspace;
heif_track
*track = (heif_track *) NULL;
MagickBooleanType
lossless,
status;
MagickOffsetType
scene;
struct heif_context
*heif_context;
struct heif_encoder
*heif_encoder = (struct heif_encoder *) NULL;
struct heif_error
error;
struct heif_image
*heif_image = (struct heif_image *) NULL;
struct heif_sequence_encoding_options
*seq_options = (struct heif_sequence_encoding_options *) NULL;
struct heif_track_options
*track_options;
uint32_t
timescale;
/*
Open output image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (IsEventLogging() != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
if (status == MagickFalse)
return(status);
heif_context=heif_context_alloc();
if (heif_context == (struct heif_context *) NULL)
ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
/*
Get encoder for AV1 (AVIF).
*/
error=heif_context_get_encoder_for_format(heif_context,
heif_compression_AV1,&heif_encoder);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
{
heif_context_free(heif_context);
return(MagickFalse);
}
lossless=image_info->quality >= 100 ? MagickTrue : MagickFalse;
if (lossless != MagickFalse)
(void) heif_encoder_set_lossless(heif_encoder,1);
else if (image_info->quality != UndefinedCompressionQuality)
(void) heif_encoder_set_lossy_quality(heif_encoder,(int)
image_info->quality);
option=GetImageOption(image_info,"heic:speed");
if (option != (char *) NULL)
(void) heif_encoder_set_parameter(heif_encoder,"speed",option);
option=GetImageOption(image_info,"heic:chroma");
if (option != (char *) NULL)
(void) heif_encoder_set_parameter(heif_encoder,"chroma",option);
/*
Determine track timescale from the first frame.
*/
if (image->ticks_per_second <= 0)
timescale=100;
else
timescale=(uint32_t) image->ticks_per_second;
heif_context_set_sequence_timescale(heif_context,timescale);
/*
Create the visual sequence track.
*/
if ((image->columns > 65535) || (image->rows > 65535))
{
heif_encoder_release(heif_encoder);
heif_context_free(heif_context);
ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
}
seq_options=heif_sequence_encoding_options_alloc();
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,21,0)
if (seq_options != (struct heif_sequence_encoding_options *) NULL)
seq_options->save_alpha_channel=1;
#endif
track_options=heif_track_options_alloc();
if (track_options != (struct heif_track_options *) NULL)
heif_track_options_set_timescale(track_options,timescale);
error=heif_context_add_visual_sequence_track(heif_context,
(uint16_t) image->columns,(uint16_t) image->rows,
heif_track_type_image_sequence,track_options,
seq_options,&track);
if (track_options != (struct heif_track_options *) NULL)
heif_track_options_release(track_options);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
{
if (seq_options != (struct heif_sequence_encoding_options *) NULL)
heif_sequence_encoding_options_release(seq_options);
heif_encoder_release(heif_encoder);
heif_context_free(heif_context);
return(MagickFalse);
}
{
Image
*frame;
MagickBooleanType
has_alpha;
size_t
depth;
depth=image->depth;
has_alpha=MagickFalse;
for (frame=image; frame != (Image *) NULL; frame=GetNextImageInList(frame))
{
if ((frame->alpha_trait & BlendPixelTrait) != 0)
has_alpha=MagickTrue;
if (frame->depth > depth)
depth=frame->depth;
}
for (frame=image; frame != (Image *) NULL; frame=GetNextImageInList(frame))
{
frame->depth=depth;
if (has_alpha != MagickFalse)
{
if ((frame->alpha_trait & BlendPixelTrait) == 0)
(void) SetImageAlphaChannel(frame,TransparentAlphaChannel,exception);
}
}
}
scene=0;
status=MagickTrue;
do
{
const StringInfo
*profile;
uint32_t
duration;
/*
Determine colorspace and chroma for this frame.
*/
colorspace=heif_colorspace_YCbCr;
chroma=lossless != MagickFalse ? heif_chroma_444 : heif_chroma_420;
if ((image->alpha_trait & BlendPixelTrait) != 0)
{
if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
status=TransformImageColorspace(image,sRGBColorspace,exception);
colorspace=heif_colorspace_RGB;
chroma=heif_chroma_interleaved_RGBA;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBBAA_LE;
}
else
if (IssRGBCompatibleColorspace(image->colorspace) != MagickFalse)
{
colorspace=heif_colorspace_RGB;
chroma=heif_chroma_interleaved_RGB;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBB_LE;
if (GetPixelChannels(image) == 1)
{
colorspace=heif_colorspace_monochrome;
chroma=heif_chroma_monochrome;
}
}
else
if (image->colorspace != YCbCrColorspace)
status=TransformImageColorspace(image,YCbCrColorspace,exception);
if (status == MagickFalse)
break;
/*
Create heif_image for this frame.
*/
error=heif_image_create((int) image->columns,(int) image->rows,colorspace,
chroma,&heif_image);
status=IsHEIFSuccess(image,&error,exception);
if (status == MagickFalse)
break;
profile=GetImageProfile(image,"icc");
if (profile != (StringInfo *) NULL)
(void) heif_image_set_raw_color_profile(heif_image,"prof",
GetStringInfoDatum(profile),GetStringInfoLength(profile));
/*
Fill heif_image pixels from ImageMagick image.
*/
if (colorspace == heif_colorspace_YCbCr)
status=WriteHEICImageYCbCr(image,heif_image,exception);
else
if (image->depth > 8)
status=WriteHEICImageRRGGBBAA(image,heif_image,exception);
else
status=WriteHEICImageRGBA(image,heif_image,exception);
if (status == MagickFalse)
{
heif_image_release(heif_image);
heif_image=(struct heif_image *) NULL;
break;
}
/*
Set frame duration and encode into the track.
*/
if (image->delay > (size_t) UINT32_MAX)
duration=UINT32_MAX;
else
duration=(uint32_t) image->delay;
if (duration == 0)
duration=timescale/10;
heif_image_set_duration(heif_image,duration);
error=heif_track_encode_sequence_image(track,heif_image,heif_encoder,
seq_options);
heif_image_release(heif_image);
heif_image=(struct heif_image *) NULL;
status=IsHEIFSuccess(image,&error,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;
scene++;
} while (image_info->adjoin != MagickFalse);
/*
Finalize the sequence and write to output.
*/
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,21,0)
if (status != MagickFalse)
{
struct heif_writer
writer;
error=heif_track_encode_end_of_sequence(track,heif_encoder);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
status=MagickFalse;
if (status != MagickFalse)
{
writer.writer_api_version=1;
writer.write=heif_write_func;
error=heif_context_write(heif_context,&writer,image);
status=IsHEIFSuccess(image,&error,exception);
}
}
#endif
if (seq_options != (struct heif_sequence_encoding_options *) NULL)
heif_sequence_encoding_options_release(seq_options);
if (track != (heif_track *) NULL)
heif_track_release(track);
heif_encoder_release(heif_encoder);
heif_context_free(heif_context);
if (CloseBlob(image) == MagickFalse)
status=MagickFalse;
return(status);
}
#endif
static MagickBooleanType WriteHEICImage(const ImageInfo *image_info,
Image *image,ExceptionInfo *exception)
{
MagickBooleanType
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,7,0)
encode_avif,
#endif
status;
MagickOffsetType
scene;
struct heif_context
*heif_context;
struct heif_encoder
*heif_encoder = (struct heif_encoder*) NULL;
struct heif_error
error;
struct heif_image
*heif_image = (struct heif_image*) NULL;
/*
Open output image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (IsEventLogging() != 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();
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,7,0)
encode_avif=(LocaleCompare(image_info->magick,"AVIF") == 0) ? MagickTrue :
MagickFalse;
#endif
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,20,0)
if ((encode_avif != MagickFalse) && (image_info->adjoin != MagickFalse) &&
(GetNextImageInList(image) != (Image *) NULL))
{
Image
*coalesce_image,
*frame;
(void) CloseBlob(image);
heif_context_free(heif_context);
coalesce_image=(Image *) NULL;
frame=image;
while (frame != (Image *) NULL)
{
if ((frame->rows != image->rows) || (frame->columns != image->columns) ||
(frame->page.x != image->page.x) || (frame->page.y != image->page.y) ||
(frame->dispose != UndefinedDispose) ||
((frame->alpha_trait & BlendPixelTrait) != 0))
{
coalesce_image=CoalesceImages(image,exception);
break;
}
frame=GetNextImageInList(frame);
}
if (coalesce_image != (Image *) NULL)
{
status=WriteHEICSequenceImage(image_info,coalesce_image,exception);
(void) DestroyImageList(coalesce_image);
return(status);
}
return(WriteHEICSequenceImage(image_info,image,exception));
}
#endif
do
{
const char
*option;
const StringInfo
*profile;
enum heif_chroma
chroma;
enum heif_colorspace
colorspace = heif_colorspace_YCbCr;
MagickBooleanType
lossless = image_info->quality >= 100 ? MagickTrue : MagickFalse;
struct heif_encoding_options
*options;
/*
Get encoder for the specified format.
*/
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,7,0)
if (encode_avif != MagickFalse)
error=heif_context_get_encoder_for_format(heif_context,
heif_compression_AV1,&heif_encoder);
else
#endif
error=heif_context_get_encoder_for_format(heif_context,
heif_compression_HEVC,&heif_encoder);
if (IsHEIFSuccess(image,&error,exception) == MagickFalse)
break;
status=IsHEIFSuccess(image,&error,exception);
if (status == MagickFalse)
break;
chroma=lossless != MagickFalse ? heif_chroma_444 : heif_chroma_420;
if ((image->alpha_trait & BlendPixelTrait) != 0)
{
if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
status=TransformImageColorspace(image,sRGBColorspace,exception);
colorspace=heif_colorspace_RGB;
chroma=heif_chroma_interleaved_RGBA;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBBAA_LE;
}
else
if (IssRGBCompatibleColorspace(image->colorspace) != MagickFalse)
{
colorspace=heif_colorspace_RGB;
chroma=heif_chroma_interleaved_RGB;
if (image->depth > 8)
chroma=heif_chroma_interleaved_RRGGBB_LE;
if (GetPixelChannels(image) == 1)
{
colorspace=heif_colorspace_monochrome;
chroma=heif_chroma_monochrome;
}
}
else
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,colorspace,
chroma,&heif_image);
status=IsHEIFSuccess(image,&error,exception);
if (status == MagickFalse)
break;
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,17,0)
option=GetImageOption(image_info,"heic:cicp");
if (option != (char *) NULL)
{
GeometryInfo
cicp;
struct heif_color_profile_nclx
*nclx_profile;
SetGeometryInfo(&cicp);
nclx_profile=heif_nclx_color_profile_alloc();
if (nclx_profile == (struct heif_color_profile_nclx *) NULL)
ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
cicp.rho=(double) nclx_profile->color_primaries;
cicp.sigma=(double) nclx_profile->transfer_characteristics;
cicp.xi=(double) nclx_profile->matrix_coefficients;
cicp.psi=(double) nclx_profile->full_range_flag;
(void) ParseGeometry(option,&cicp);
heif_nclx_color_profile_set_color_primaries(nclx_profile,
(uint16_t) cicp.rho);
heif_nclx_color_profile_set_transfer_characteristics(nclx_profile,
(uint16_t) cicp.sigma);
heif_nclx_color_profile_set_matrix_coefficients(nclx_profile,
(uint16_t) cicp.xi);
nclx_profile->full_range_flag=(uint8_t) cicp.psi;
heif_image_set_nclx_color_profile(heif_image,nclx_profile);
heif_nclx_color_profile_free(nclx_profile);
}
#endif
profile=GetImageProfile(image,"icc");
if (profile != (StringInfo *) NULL)
(void) heif_image_set_raw_color_profile(heif_image,"prof",
GetStringInfoDatum(profile),GetStringInfoLength(profile));
if (colorspace == heif_colorspace_YCbCr)
status=WriteHEICImageYCbCr(image,heif_image,exception);
else
if (image->depth > 8)
status=WriteHEICImageRRGGBBAA(image,heif_image,exception);
else
status=WriteHEICImageRGBA(image,heif_image,exception);
if (status == MagickFalse)
break;
/*
Encode 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;
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;
}
options=heif_encoding_options_alloc();
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,16,0)
option=GetImageOption(image_info,"heic:chroma-downsampling");
if (option != (char *) NULL)
{
if (LocaleCompare(option,"nearest-neighbor") == 0)
{
options->color_conversion_options.
only_use_preferred_chroma_algorithm=1;
options->color_conversion_options.
preferred_chroma_downsampling_algorithm=
heif_chroma_downsampling_nearest_neighbor;
}
else if (LocaleCompare(option,"average") == 0)
{
options->color_conversion_options.
only_use_preferred_chroma_algorithm=1;
options->color_conversion_options.
preferred_chroma_downsampling_algorithm=
heif_chroma_downsampling_average;
}
else if (LocaleCompare(option,"sharp-yuv") == 0)
{
options->color_conversion_options.
only_use_preferred_chroma_algorithm=1;
options->color_conversion_options.
preferred_chroma_downsampling_algorithm=
heif_chroma_downsampling_sharp_yuv;
}
}
#endif
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,14,0)
if (image->orientation != UndefinedOrientation)
options->image_orientation=(enum heif_orientation) image->orientation;
#endif
error=heif_context_encode_image(heif_context,heif_image,heif_encoder,
options,(struct heif_image_handle **) NULL);
heif_encoding_options_free(options);
status=IsHEIFSuccess(image,&error,exception);
if (status == MagickFalse)
break;
if (image->profiles != (void *) NULL)
WriteProfile(heif_context,image,exception);
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)
{
struct heif_writer
writer;
writer.writer_api_version=1;
writer.write=heif_write_func;
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);
if (CloseBlob(image) == MagickFalse)
status=MagickFalse;
return(status);
}
#endif