summaryrefslogtreecommitdiff
path: root/coders/heif.c
diff options
context:
space:
mode:
Diffstat (limited to 'coders/heif.c')
-rw-r--r--coders/heif.c594
1 files changed, 594 insertions, 0 deletions
diff --git a/coders/heif.c b/coders/heif.c
new file mode 100644
index 0000000..bc0d9a3
--- /dev/null
+++ b/coders/heif.c
@@ -0,0 +1,594 @@
+/*
+% Copyright (C) 2022 GraphicsMagick Group
+%
+% This program is covered by multiple licenses, which are described in
+% Copyright.txt. You should have received a copy of Copyright.txt with this
+% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% H H EEEEE I FFFFF %
+% H H E I F %
+% HHHHH EEEEE I FFFF %
+% H H E I F %
+% H H EEEEE I F %
+% %
+% Read Heif/Heic Image Format. %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+* Status: Support for reading a single image.
+*/
+
+#include "magick/studio.h"
+#include "magick/blob.h"
+#include "magick/colormap.h"
+#include "magick/log.h"
+#include "magick/constitute.h"
+#include "magick/magick.h"
+#include "magick/monitor.h"
+#include "magick/pixel_cache.h"
+#include "magick/profile.h"
+#include "magick/utility.h"
+#include "magick/resource.h"
+
+/* Set to 1 to enable the currently non-functional progress monitor callbacks */
+#define HEIF_ENABLE_PROGRESS_MONITOR 0
+
+#if defined(HasHEIF)
+#include <libheif/heif.h>
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% I s H E I F %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Method IsHEIF returns True if the image format type, identified by the
+% magick string, is supported by this HEIF reader.
+%
+% The format of the IsHEIF method is:
+%
+% unsigned int IsHEIF(const unsigned char *magick,const size_t length)
+%
+% A description of each parameter follows:
+%
+% o status: Method IsHEIF returns True if the image format type is HEIF.
+%
+% o magick: This string is generally the first few bytes of an image file
+% or blob.
+%
+% o length: Specifies the length of the magick string.
+%
+%
+*/
+static unsigned int IsHEIF(const unsigned char *magick,const size_t length)
+{
+ enum heif_filetype_result
+ heif_filetype;
+
+ if (length < 12)
+ return(False);
+
+ heif_filetype = heif_check_filetype(magick, (int) length);
+ if (heif_filetype == heif_filetype_yes_supported)
+ return True;
+
+ return(False);
+}
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% R e a d H E I F I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ReadHEIFImage() reads an image in the HEIF image format.
+%
+% The format of the ReadHEIFImage method is:
+%
+% Image *ReadHEIFImage(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.
+%
+*/
+
+#define HEIFReadCleanup() \
+ if (heif_image) heif_image_release(heif_image); \
+ if (heif_image_handle) heif_image_handle_release(heif_image_handle); \
+ if (heif) heif_context_free(heif); \
+ MagickFreeResourceLimitedMemory(in_buf)
+
+#define ThrowHEIFReaderException(code_,reason_,image_) \
+ { \
+ HEIFReadCleanup(); \
+ ThrowReaderException(code_,reason_,image_) \
+ }
+
+static Image *ReadMetadata(struct heif_image_handle *heif_image_handle,
+ Image *image, ExceptionInfo *exception)
+{
+ int
+ count,
+ i;
+
+ heif_item_id
+ *ids;
+
+ struct heif_error
+ err;
+
+ count=heif_image_handle_get_number_of_metadata_blocks(heif_image_handle, NULL);
+ if (count==0)
+ return image;
+
+ ids=MagickAllocateResourceLimitedArray(heif_item_id *,count,sizeof(*ids));
+ if (ids == (heif_item_id *) NULL)
+ ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+
+ count=heif_image_handle_get_list_of_metadata_block_IDs(heif_image_handle, NULL,
+ ids,count);
+
+ for (i=0; i<count; i++)
+ {
+ const char*
+ profile_name=heif_image_handle_get_metadata_type(heif_image_handle,ids[i]);
+
+ size_t
+ profile_size=heif_image_handle_get_metadata_size(heif_image_handle,ids[i]);
+
+ unsigned char*
+ profile;
+
+ if (image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "Profile \"%s\" with size %" MAGICK_SIZE_T_F "u bytes",
+ profile_name, (MAGICK_SIZE_T) profile_size);
+
+ profile=MagickAllocateResourceLimitedArray(unsigned char*,profile_size,
+ sizeof(*profile));
+ if (profile == (unsigned char*) NULL)
+ {
+ MagickFreeResourceLimitedMemory(ids);
+ ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+ }
+
+ err=heif_image_handle_get_metadata(heif_image_handle,ids[i],profile);
+
+ if (err.code != heif_error_Ok)
+ {
+ if (image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "heif_image_handle_get_metadata() reports error \"%s\"",
+ err.message);
+ MagickFreeResourceLimitedMemory(profile);
+ MagickFreeResourceLimitedMemory(ids);
+ ThrowReaderException(CorruptImageError,
+ AnErrorHasOccurredReadingFromFile,image);
+ }
+
+ if (strncmp(profile_name,"Exif",4) == 0 && profile_size > 4)
+ {
+ /* skip TIFF-Header */
+ SetImageProfile(image,profile_name,profile+4,profile_size-4);
+ }
+ else
+ {
+ SetImageProfile(image,profile_name,profile,profile_size);
+ }
+ MagickFreeResourceLimitedMemory(profile);
+ }
+ MagickFreeResourceLimitedMemory(ids);
+ return image;
+}
+
+/*
+ This progress monitor implementation is tentative since it is not invoked
+
+ According to libheif issue 161
+ (https://github.com/strukturag/libheif/issues/161) progress monitor
+ does not actually work since the decoders it depends on do not
+ support it.
+
+ Libheif issue 546 (https://github.com/strukturag/libheif/pull/546)
+ suggests changing the return type of on_progress and start_progress
+ to "bool" so that one can implement cancelation support.
+ */
+typedef struct ProgressUserData_
+{
+ ExceptionInfo *exception;
+ Image *image;
+ enum heif_progress_step step;
+ unsigned long int progress;
+ unsigned long int max_progress;
+
+} ProgressUserData;
+
+#if HEIF_ENABLE_PROGRESS_MONITOR
+/* Called when progress monitor starts. The 'max_progress' parameter indicates the maximum value of progress */
+static void start_progress(enum heif_progress_step step, int max_progress, void* progress_user_data)
+{
+ ProgressUserData *context= (ProgressUserData *) progress_user_data;
+ Image *image=context->image;
+ context->step = step;
+ context->progress = 0;
+ context->max_progress = max_progress;
+ if (context->image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "start_progress: step=%d, max_progress=%d",step, max_progress);
+ MagickMonitorFormatted(context->progress,context->max_progress,&image->exception,
+ "[%s] Loading image: %lux%lu... ",
+ image->filename,
+ image->columns,image->rows);
+}
+
+/* Called for each step of progress. The 'progress' parameter represents the progress within the span of 'max_progress' */
+static void on_progress(enum heif_progress_step step, int progress, void* progress_user_data)
+{
+ ProgressUserData *context = (ProgressUserData *) progress_user_data;
+ Image *image=context->image;
+ context->step = step;
+ context->progress = progress;
+ if (context->image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "on_progress: step=%d, progress=%d",step, progress);
+ MagickMonitorFormatted(context->progress,context->max_progress,&image->exception,
+ "[%s] Loading image: %lux%lu... ",
+ image->filename,
+ image->columns,image->rows);
+}
+
+/* Called when progress monitor stops */
+static void end_progress(enum heif_progress_step step, void* progress_user_data)
+{
+ ProgressUserData *context = (ProgressUserData *) progress_user_data;
+ context->step = step;
+ if (context->image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "end_progress: step=%d",step);
+}
+
+#endif /* if HEIF_ENABLE_PROGRESS_MONITOR */
+
+static Image *ReadHEIFImage(const ImageInfo *image_info,
+ ExceptionInfo *exception)
+{
+ Image
+ *image;
+
+ struct heif_context
+ *heif = NULL;
+
+ struct heif_error
+ heif_status;
+
+ struct heif_image_handle
+ *heif_image_handle = NULL;
+
+ struct heif_image
+ *heif_image = NULL;
+
+ struct heif_decoding_options
+ *decode_options;
+
+ ProgressUserData
+ progress_user_data;
+
+ size_t
+ in_len;
+
+ int
+ row_stride;
+
+ unsigned char
+ *in_buf = NULL;
+
+ const uint8_t
+ *pixels = NULL;
+
+ long
+ x,
+ y;
+
+ PixelPacket
+ *q;
+
+ assert(image_info != (const ImageInfo *) NULL);
+ assert(image_info->signature == MagickSignature);
+ assert(exception != (ExceptionInfo *) NULL);
+ assert(exception->signature == MagickSignature);
+
+ /*
+ Open image file.
+ */
+ image=AllocateImage(image_info);
+ if (image == (Image *) NULL)
+ ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+
+ if (OpenBlob(image_info,image,ReadBinaryBlobMode,exception) == MagickFail)
+ ThrowReaderException(FileOpenError,UnableToOpenFile,image);
+
+ in_len=GetBlobSize(image);
+ in_buf=MagickAllocateResourceLimitedArray(unsigned char *,in_len,sizeof(*in_buf));
+ if (in_buf == (unsigned char *) NULL)
+ ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+
+ if (ReadBlob(image,in_len,in_buf) != in_len)
+ ThrowHEIFReaderException(CorruptImageError, UnexpectedEndOfFile, image);
+
+ /* Init HEIF-Decoder handles */
+ heif=heif_context_alloc();
+
+ heif_status=heif_context_read_from_memory(heif, in_buf, in_len, NULL);
+ if (heif_status.code == heif_error_Unsupported_filetype
+ || heif_status.code == heif_error_Unsupported_feature)
+ ThrowHEIFReaderException(CoderError, ImageTypeNotSupported, image);
+ if (heif_status.code != heif_error_Ok)
+ {
+ if (image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "heif_context_read_from_memory() reports error \"%s\"",
+ heif_status.message);
+ ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image);
+ }
+
+ /* no support for reading multiple images but could be added */
+ if (heif_context_get_number_of_top_level_images(heif) != 1)
+ ThrowHEIFReaderException(CoderError, NumberOfImagesIsNotSupported, image);
+
+ heif_status=heif_context_get_primary_image_handle(heif, &heif_image_handle);
+ if (heif_status.code == heif_error_Memory_allocation_error)
+ ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+ if (heif_status.code != heif_error_Ok)
+ {
+ if (image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "heif_context_get_primary_image_handle() reports error \"%s\"",
+ heif_status.message);
+ ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image);
+ }
+
+ image->columns=heif_image_handle_get_width(heif_image_handle);
+ image->rows=heif_image_handle_get_height(heif_image_handle);
+ if (heif_image_handle_has_alpha_channel(heif_image_handle))
+ image->matte=MagickTrue;
+
+ if (image->logging)
+ {
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "Geometry: %lux%lu", image->columns, image->rows);
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "Matte: %s", image->matte ? "True" : "False");
+ }
+
+ if (!ReadMetadata(heif_image_handle, image, exception))
+ {
+ HEIFReadCleanup();
+ return NULL;
+ }
+
+ if (image_info->ping)
+ {
+ image->depth = 8;
+ HEIFReadCleanup();
+ CloseBlob(image);
+ return image;
+ }
+
+ if (CheckImagePixelLimits(image, exception) != MagickPass)
+ ThrowHEIFReaderException(ResourceLimitError,ImagePixelLimitExceeded,image);
+
+ /* Add decoding options support */
+ decode_options = heif_decoding_options_alloc();
+ if (decode_options == (struct heif_decoding_options*) NULL)
+ ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+
+ progress_user_data.exception = exception;
+ progress_user_data.image = image;
+ progress_user_data.max_progress = 0;
+ progress_user_data.progress = 0;
+
+ /* version 1 options */
+ decode_options->ignore_transformations = 0;
+#if HEIF_ENABLE_PROGRESS_MONITOR
+ decode_options->start_progress = start_progress;
+ decode_options->on_progress = on_progress;
+ decode_options->end_progress = end_progress;
+#endif /* if HEIF_ENABLE_PROGRESS_MONITOR */
+ decode_options->progress_user_data = &progress_user_data;
+
+ /* version 2 options */
+#if LIBHEIF_NUMERIC_VERSION > 0x01070000
+ decode_options->convert_hdr_to_8bit = 1;
+#endif /* if LIBHEIF_NUMERIC_VERSION > 0x01070000 */
+
+ /* version 3 options */
+
+ /* When enabled, an error is returned for invalid input. Otherwise, it will try its best and
+ add decoding warnings to the decoded heif_image. Default is non-strict. */
+ /* uint8_t strict_decoding; */
+
+ heif_status=heif_decode_image(heif_image_handle, &heif_image,
+ heif_colorspace_RGB, image->matte ? heif_chroma_interleaved_RGBA :
+ heif_chroma_interleaved_RGB,
+ decode_options);
+ heif_decoding_options_free(decode_options);
+ if (heif_status.code == heif_error_Memory_allocation_error)
+ ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+ if (heif_status.code != heif_error_Ok)
+ {
+ if (image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "heif_decode_image() reports error \"%s\"",
+ heif_status.message);
+ ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image);
+ }
+
+ image->depth=heif_image_get_bits_per_pixel(heif_image, heif_channel_interleaved);
+ /* the requested channel is interleaved there depth is a sum of all channels
+ split it up again: */
+ if (image->logging)
+ {
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "heif_image_get_bits_per_pixel: bits_per_pixel=%u", image->depth);
+ }
+ if (image->depth == 32 && image->matte)
+ image->depth = 8;
+ else if (image->depth == 24 && !image->matte)
+ image->depth = 8;
+ else
+ ThrowHEIFReaderException(CoderError, UnsupportedBitsPerSample, image);
+
+ pixels=heif_image_get_plane_readonly(heif_image, heif_channel_interleaved, &row_stride);
+ if (!pixels)
+ ThrowHEIFReaderException(CoderError, NoDataReturned, image);
+
+ if (image->logging)
+ (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+ "heif_image_get_plane_readonly: bytes-per-line=%d",
+ row_stride);
+
+ /* Transfer pixels to image, using row stride to find start of each row. */
+ for (y=0; y < (long)image->rows; y++)
+ {
+ const uint8_t *line;
+ q=SetImagePixelsEx(image,0,y,image->columns,1,exception);
+ if (q == (PixelPacket *) NULL)
+ ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+ line=pixels+y*row_stride;
+ for (x=0; x < (long)image->columns; x++)
+ {
+ SetRedSample(q,ScaleCharToQuantum(*line++));
+ SetGreenSample(q,ScaleCharToQuantum(*line++));
+ SetBlueSample(q,ScaleCharToQuantum(*line++));
+ if (image->matte) {
+ SetOpacitySample(q,MaxRGB-ScaleCharToQuantum(*line++));
+ } else {
+ SetOpacitySample(q,OpaqueOpacity);
+ }
+ q++;
+ }
+ if (!SyncImagePixels(image))
+ ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
+ }
+
+ HEIFReadCleanup();
+ CloseBlob(image);
+ return image;
+}
+
+#endif
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% R e g i s t e r H E I F I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Method RegisterHEIFImage adds attributes for the HEIF image format to
+% the list of supported formats. The attributes include the image format
+% tag, a method to read and/or write the format and a brief
+% description of the format.
+%
+% The format of the RegisterHEIFImage method is:
+%
+% RegisterHEIFImage(void)
+%
+*/
+ModuleExport void RegisterHEIFImage(void)
+{
+ static const char
+ description[] = "HEIF Image Format";
+
+ static char
+ version[20];
+
+ MagickInfo
+ *entry;
+
+ unsigned int
+ heif_major,
+ heif_minor,
+ heif_revision;
+
+ int encoder_version=heif_get_version_number();
+ heif_major=(encoder_version >> 16) & 0xff;
+ heif_minor=(encoder_version >> 8) & 0xff;
+ heif_revision=encoder_version & 0xff;
+ *version='\0';
+ (void) sprintf(version,
+ "heif v%u.%u.%u", heif_major,
+ heif_minor, heif_revision);
+
+ entry=SetMagickInfo("HEIF");
+#if defined(HasHEIF)
+ entry->decoder=(DecoderHandler) ReadHEIFImage;
+ entry->magick=(MagickHandler) IsHEIF;
+#endif
+ entry->description=description;
+ entry->adjoin=False;
+ entry->seekable_stream=MagickTrue;
+ if (*version != '\0')
+ entry->version=version;
+ entry->module="HEIF";
+ entry->coder_class=PrimaryCoderClass;
+ (void) RegisterMagickInfo(entry);
+
+ entry=SetMagickInfo("HEIC");
+#if defined(HasHEIF)
+ entry->decoder=(DecoderHandler) ReadHEIFImage;
+ entry->magick=(MagickHandler) IsHEIF;
+#endif
+ entry->description=description;
+ entry->adjoin=False;
+ entry->seekable_stream=MagickTrue;
+ if (*version != '\0')
+ entry->version=version;
+ entry->module="HEIF";
+ entry->coder_class=PrimaryCoderClass;
+ (void) RegisterMagickInfo(entry);
+}
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% U n r e g i s t e r H E I F I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% Method UnregisterHEIFImage removes format registrations made by the
+% HEIF module from the list of supported formats.
+%
+% The format of the UnregisterHEIFImage method is:
+%
+% UnregisterHEIFImage(void)
+%
+*/
+ModuleExport void UnregisterHEIFImage(void)
+{
+ (void) UnregisterMagickInfo("HEIF");
+ (void) UnregisterMagickInfo("HEIC");
+}