mirror of
https://github.com/ImageMagick/ImageMagick.git
synced 2026-05-31 11:18:42 +02:00
09e0ad32e6
* Fix AVIF/HEIC animation export error on sequences with mixed alpha bounds - Normalizes alpha traits via TransparentAlphaChannel prior to heif frame processing, similar to JXL, fulfilling libheif's strict requirement to uniformly present either an alpha channel across every frame or entirely omit alpha tracking. * Variable name frame makes more sense than next in this context
2191 lines
69 KiB
C
2191 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 != 0)
|
|
{
|
|
heif_track_release(track);
|
|
return(MagickFalse);
|
|
}
|
|
timescale=heif_track_get_timescale(track);
|
|
if (timescale == 0)
|
|
timescale=1;
|
|
decode_options=heif_decoding_options_alloc();
|
|
/*
|
|
Detect alpha from the track and set up chroma format.
|
|
*/
|
|
#if LIBHEIF_NUMERIC_VERSION >= HEIC_COMPUTE_NUMERIC_VERSION(1,21,0)
|
|
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)
|
|
{
|
|
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 != 0)
|
|
{
|
|
(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)
|
|
{
|
|
heif_image_release(heif_image);
|
|
scene++;
|
|
continue;
|
|
}
|
|
if (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)
|
|
{
|
|
enum heif_filetype_result
|
|
filetype_check;
|
|
|
|
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");
|
|
filetype_check=heif_check_filetype(magic,sizeof(magic));
|
|
if (filetype_check == heif_filetype_no)
|
|
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
|