Coverage Report

Created: 2025-07-23 08:18

/src/graphicsmagick/coders/heif.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
% Copyright (C) 2023-2025 GraphicsMagick Group
3
%
4
% This program is covered by multiple licenses, which are described in
5
% Copyright.txt. You should have received a copy of Copyright.txt with this
6
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
7
%
8
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
9
%                                                                             %
10
%                                                                             %
11
%                       H   H  EEEEE  I  FFFFF                                %
12
%                       H   H  E      I  F                                    %
13
%                       HHHHH  EEEEE  I  FFFF                                 %
14
%                       H   H  E      I  F                                    %
15
%                       H   H  EEEEE  I  F                                    %
16
%                                                                             %
17
%           Read HEIF/HEIC/AVIF image formats using libheif.                  %
18
%                                                                             %
19
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
20
* Status: Support for reading a single image.
21
*/
22
23
#include "magick/studio.h"
24
#include "magick/attribute.h"
25
#include "magick/blob.h"
26
#include "magick/colormap.h"
27
#include "magick/log.h"
28
#include "magick/constitute.h"
29
#include "magick/magick.h"
30
#include "magick/monitor.h"
31
#include "magick/pixel_cache.h"
32
#include "magick/profile.h"
33
#include "magick/resource.h"
34
#include "magick/utility.h"
35
#include "magick/resource.h"
36
37
#if defined(HasHEIF)
38
39
/* Set to 1 to enable the currently non-functional progress monitor callbacks */
40
#define HEIF_ENABLE_PROGRESS_MONITOR 0
41
42
#include <libheif/heif.h>
43
#ifdef HAVE_HEIF_INIT
44
static MagickBool heif_initialized = MagickFalse;
45
#endif /* ifdef HAVE_HEIF_INIT */
46

47
/*
48
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
49
%                                                                             %
50
%                                                                             %
51
%                                                                             %
52
%   I s H E I F                                                               %
53
%                                                                             %
54
%                                                                             %
55
%                                                                             %
56
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
57
%
58
%  Method IsHEIF returns True if the image format type, identified by the
59
%  magick string, is supported by this HEIF reader.
60
%
61
%  The format of the IsHEIF  method is:
62
%
63
%      MagickBool IsHEIF(const unsigned char *magick,const size_t length)
64
%
65
%  A description of each parameter follows:
66
%
67
%    o status:  Method IsHEIF returns MagickTrue if the image format type is HEIF.
68
%
69
%    o magick: This string is generally the first few bytes of an image file
70
%      or blob.
71
%
72
%    o length: Specifies the length of the magick string.
73
%
74
%
75
*/
76
static MagickBool IsHEIF(const unsigned char *magick,const size_t length)
77
0
{
78
0
  enum heif_filetype_result
79
0
    heif_filetype;
80
81
0
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
82
0
                        "Testing header for supported HEIF format");
83
84
0
  if (length < 12)
85
0
    return(MagickFalse);
86
87
0
  heif_filetype = heif_check_filetype(magick, (int) length);
88
0
  if (heif_filetype == heif_filetype_yes_supported)
89
0
    return MagickTrue;
90
91
0
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
92
0
                        "Not a supported HEIF format");
93
94
0
  return(MagickFalse);
95
0
}
96
/*
97
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98
%                                                                             %
99
%                                                                             %
100
%                                                                             %
101
%   R e a d H E I F I m a g e                                                 %
102
%                                                                             %
103
%                                                                             %
104
%                                                                             %
105
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
106
%
107
%  ReadHEIFImage() reads an image in the HEIF image format.
108
%
109
%  The format of the ReadHEIFImage method is:
110
%
111
%      Image *ReadHEIFImage(const ImageInfo *image_info,
112
%        ExceptionInfo *exception)
113
%
114
%  A description of each parameter follows:
115
%
116
%    o image_info: the image info.
117
%
118
%    o exception: return any errors or warnings in this structure.
119
%
120
*/
121
122
#define HEIFReadCleanup()                                              \
123
52.4k
  do                                                                   \
124
52.4k
    {                                                                  \
125
52.4k
      if (heif_image)                                                  \
126
52.4k
        heif_image_release(heif_image);                                \
127
52.4k
      if (heif_image_handle)                                           \
128
52.4k
        heif_image_handle_release(heif_image_handle);                  \
129
52.4k
      if (heif)                                                        \
130
52.4k
        heif_context_free(heif);                                       \
131
52.4k
      MagickFreeResourceLimitedMemory(in_buf);                         \
132
52.4k
    } while (0);
133
134
#define ThrowHEIFReaderException(code_,reason_,image_) \
135
2.43k
  do                                                   \
136
47.2k
    {                                                  \
137
47.2k
      HEIFReadCleanup();                               \
138
47.2k
      ThrowReaderException(code_,reason_,image_);      \
139
0
    } while (0);
140
141
/*
142
  Read metadata (Exif and XMP)
143
*/
144
static Image *ReadMetadata(const ImageInfo *image_info,
145
                           struct heif_image_handle *heif_image_handle,
146
                           Image *image, ExceptionInfo *exception)
147
38.6k
{
148
38.6k
  int
149
38.6k
    count,
150
38.6k
    i;
151
152
38.6k
  heif_item_id
153
38.6k
    *ids;
154
155
38.6k
  struct heif_error
156
38.6k
    err;
157
158
  /* Get number of metadata blocks attached to image */
159
38.6k
  count=heif_image_handle_get_number_of_metadata_blocks(heif_image_handle,
160
                                                        /*type_filter*/ NULL);
161
38.6k
  if (count==0)
162
38.5k
    return image;
163
164
69
  ids=MagickAllocateResourceLimitedArray(heif_item_id *,count,sizeof(*ids));
165
69
  if (ids == (heif_item_id *) NULL)
166
69
    ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
167
168
  /* Get list of metadata block ids */
169
69
  count=heif_image_handle_get_list_of_metadata_block_IDs(heif_image_handle, NULL,
170
69
                                                         ids,count);
171
172
  /* For each block id ... */
173
138
  for (i=0; i < count; i++)
174
69
    {
175
69
      const char
176
69
        *content_type,
177
69
        *profile_name;
178
179
69
      size_t
180
69
        exif_pad = 0,
181
69
        profile_size;
182
183
69
      unsigned char
184
69
        *profile;
185
186
      /* Access string indicating the type of the metadata (e.g. "Exif") */
187
69
      profile_name=heif_image_handle_get_metadata_type(heif_image_handle,ids[i]);
188
189
      /* Access string indicating the content type */
190
69
      content_type=heif_image_handle_get_metadata_content_type(heif_image_handle,ids[i]);
191
192
      /* Get the size of the raw metadata, as stored in the HEIF file */
193
69
      profile_size=heif_image_handle_get_metadata_size(heif_image_handle,ids[i]);
194
195
69
      if (image->logging)
196
69
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
197
69
                              "Profile \"%s\" with content type \"%s\""
198
69
                              " and size %" MAGICK_SIZE_T_F "u bytes",
199
69
                              profile_name ? profile_name : "(null)",
200
69
                              content_type ? content_type : "(null)",
201
69
                              (MAGICK_SIZE_T) profile_size);
202
203
69
      if (NULL != profile_name && profile_size > 0)
204
63
        {
205
63
          if (strncmp(profile_name,"Exif",4) == 0)
206
59
            exif_pad=2;
207
208
          /* Allocate memory for profile */
209
63
          profile=MagickAllocateResourceLimitedArray(unsigned char*,profile_size+exif_pad,
210
63
                                                     sizeof(*profile));
211
63
          if (profile == (unsigned char*) NULL)
212
0
            {
213
0
              MagickFreeResourceLimitedMemory(ids);
214
0
              ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
215
0
            }
216
217
          /*
218
            Copy metadata into 'profile' buffer. For Exif data, you
219
            probably have to skip the first four bytes of the data,
220
            since they indicate the offset to the start of the TIFF
221
            header of the Exif data.
222
          */
223
63
          err=heif_image_handle_get_metadata(heif_image_handle,ids[i],profile+exif_pad);
224
225
63
          if (err.code != heif_error_Ok)
226
0
            {
227
0
              if (image->logging)
228
0
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
229
0
                                      "heif_image_handle_get_metadata() reports error \"%s\"",
230
0
                                      err.message);
231
0
              MagickFreeResourceLimitedMemory(profile);
232
0
              MagickFreeResourceLimitedMemory(ids);
233
0
              ThrowReaderException(CorruptImageError,
234
0
                                   AnErrorHasOccurredReadingFromFile,image);
235
0
            }
236
237
63
          if (strncmp(profile_name,"Exif",4) == 0 && profile_size > 4)
238
53
            {
239
              /* Parse and skip offset to TIFF header */
240
53
              unsigned char *p = profile;
241
53
              magick_uint32_t offset;
242
243
              /* Big-endian offset decoding */
244
53
              offset = (magick_uint32_t) p[exif_pad+0] << 24 |
245
53
                (magick_uint32_t) p[exif_pad+1] << 16 |
246
53
                (magick_uint32_t) p[exif_pad+2] << 8 |
247
53
                (magick_uint32_t) p[exif_pad+3];
248
249
              /*
250
                If the TIFF header offset is not zero, then need to
251
                move the TIFF data forward to the correct offset.
252
              */
253
53
              profile_size -= 4;
254
53
              if (offset > 0 && offset < profile_size)
255
7
                {
256
7
                  profile_size -= offset;
257
258
                  /* Strip any EOI marker if payload starts with a JPEG marker */
259
7
                  if (profile_size > 2 &&
260
7
                      (memcmp(p+exif_pad+4,"\xff\xd8",2) == 0 ||
261
5
                       memcmp(p+exif_pad+4,"\xff\xe1",2) == 0) &&
262
7
                      memcmp(p+exif_pad+4+profile_size-2,"\xff\xd9",2) == 0)
263
0
                    profile_size -= 2;
264
265
7
                  (void) memmove(p+exif_pad+4,p+exif_pad+4+offset,profile_size);
266
7
                }
267
268
53
              p[0]='E';
269
53
              p[1]='x';
270
53
              p[2]='i';
271
53
              p[3]='f';
272
53
              p[4]='\0';
273
53
              p[5]='\0';
274
275
53
              SetImageProfile(image,"EXIF",profile,profile_size+exif_pad+4);
276
277
              /*
278
                Retrieve image orientation from EXIF and store in
279
                image.
280
              */
281
53
              {
282
53
                const ImageAttribute
283
53
                  *attribute;
284
285
53
                attribute = GetImageAttribute(image,"EXIF:Orientation");
286
53
                if ((attribute != (const ImageAttribute *) NULL) &&
287
53
                    (attribute->value != (char *) NULL))
288
17
                  {
289
17
                    int
290
17
                      orientation;
291
292
17
                    orientation=MagickAtoI(attribute->value);
293
17
                    if ((orientation > UndefinedOrientation) &&
294
17
                        (orientation <= LeftBottomOrientation))
295
0
                      image->orientation=(OrientationType) orientation;
296
17
                  }
297
53
              }
298
53
            }
299
10
          else
300
10
            {
301
10
              if (NULL != content_type && strncmp(content_type,"application/rdf+xml",19) == 0)
302
0
                SetImageProfile(image,"XMP",profile,profile_size);
303
10
            }
304
          /*
305
            Only apply Exif orientation if ignore-transformations is true
306
            since HEIF native transformations will handle orientation otherwise
307
          */
308
63
          if (strncmp(profile_name,"Exif",4) == 0)
309
59
            {
310
59
              const char *value;
311
59
              MagickBool ignore_transformations = MagickFalse;
312
59
              if ((value=AccessDefinition(image_info,"heif","ignore-transformations")))
313
0
                if (LocaleCompare(value,"TRUE") == 0)
314
0
                  ignore_transformations = MagickTrue;
315
316
59
              if (!ignore_transformations)
317
59
                {
318
59
                  const ImageAttribute *attribute = GetImageAttribute(image,"EXIF:Orientation");
319
59
                  if (attribute && attribute->value)
320
17
                    {
321
17
                      SetImageAttribute(image,"EXIF:Orientation","1");
322
17
                      image->orientation = UndefinedOrientation;
323
17
                    }
324
59
                }
325
59
            }
326
63
          MagickFreeResourceLimitedMemory(profile);
327
63
        }
328
69
    }
329
69
  MagickFreeResourceLimitedMemory(ids);
330
69
  return image;
331
69
}
332
333
334
/*
335
  Read Color Profile
336
*/
337
static Image *ReadColorProfile(struct heif_image_handle *heif_image_handle,
338
                               Image *image, ExceptionInfo *exception)
339
38.6k
{
340
38.6k
  struct heif_error
341
38.6k
    err;
342
343
38.6k
  enum heif_color_profile_type
344
38.6k
    profile_type; /* 4 chars encoded into enum by 'heif_fourcc()' */
345
346
38.6k
  unsigned char
347
38.6k
    *profile;
348
349
38.6k
  profile_type = heif_image_handle_get_color_profile_type(heif_image_handle);
350
351
38.6k
  if (heif_color_profile_type_not_present == profile_type)
352
33.5k
    return image;
353
354
5.06k
  if (image->logging && (heif_color_profile_type_not_present !=profile_type))
355
5.06k
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
356
5.06k
                          "Found color profile of type \"%c%c%c%c\")",
357
5.06k
                          ((char) ((unsigned int) profile_type >> 24) & 0xff),
358
5.06k
                          ((char) ((unsigned int) profile_type >> 16) & 0xff),
359
5.06k
                          ((char) ((unsigned int) profile_type >> 8) & 0xff),
360
5.06k
                          ((char) ((unsigned int) profile_type) & 0xff));
361
362
5.06k
  if (heif_color_profile_type_prof == profile_type)
363
848
    {
364
848
      size_t profile_size;
365
366
848
      profile_size = heif_image_handle_get_raw_color_profile_size(heif_image_handle);
367
368
848
      if (image->logging)
369
848
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
370
848
                              "Reading ICC profile with size %" MAGICK_SIZE_T_F "u bytes",
371
848
                              (MAGICK_SIZE_T) profile_size);
372
373
848
      if (profile_size > 0)
374
848
        {
375
          /* Allocate 'profile' buffer for profile */
376
848
          profile=MagickAllocateResourceLimitedArray(unsigned char*,profile_size,
377
848
                                                     sizeof(*profile));
378
379
848
          if (profile == (unsigned char*) NULL)
380
848
            ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
381
382
          /* Copy ICC profile to 'profile' buffer */
383
848
          err = heif_image_handle_get_raw_color_profile(heif_image_handle,
384
848
                                                        profile);
385
848
          if (err.code != heif_error_Ok)
386
0
            {
387
0
              if (image->logging)
388
0
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
389
0
                                      "heif_image_handle_get_raw_color_profile() reports error \"%s\"",
390
0
                                      err.message);
391
0
              MagickFreeResourceLimitedMemory(profile);
392
0
              ThrowReaderException(CorruptImageError,
393
0
                                   AnErrorHasOccurredReadingFromFile,image);
394
395
0
            }
396
848
          SetImageProfile(image,"ICM",profile,profile_size);
397
848
          MagickFreeResourceLimitedMemory(profile);
398
848
        }
399
848
    }
400
5.06k
  return image;
401
5.06k
}
402
403
/*
404
  This progress monitor implementation is tentative since it is not invoked
405
406
  According to libheif issue 161
407
  (https://github.com/strukturag/libheif/issues/161) progress monitor
408
  does not actually work since the decoders it depends on do not
409
  support it.
410
411
  Libheif issue 546 (https://github.com/strukturag/libheif/pull/546)
412
  suggests changing the return type of on_progress and start_progress
413
  to "bool" so that one can implement cancellation support.
414
 */
415
typedef struct ProgressUserData_
416
{
417
  ExceptionInfo *exception;
418
  Image *image;
419
  enum heif_progress_step step;
420
  unsigned long int progress;
421
  unsigned long int max_progress;
422
423
} ProgressUserData;
424
425
#if HEIF_ENABLE_PROGRESS_MONITOR
426
/* Called when progress monitor starts.  The 'max_progress' parameter indicates the maximum value of progress */
427
static void start_progress(enum heif_progress_step step, int max_progress, void* progress_user_data)
428
{
429
  ProgressUserData *context= (ProgressUserData *) progress_user_data;
430
  Image *image=context->image;
431
  context->step = step;
432
  context->progress = 0;
433
  context->max_progress = max_progress;
434
  if (context->image->logging)
435
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
436
                          "start_progress: step=%d, max_progress=%d",step, max_progress);
437
  MagickMonitorFormatted(context->progress,context->max_progress,&image->exception,
438
                         "[%s] Loading image: %lux%lu...  ",
439
                         image->filename,
440
                         image->columns,image->rows);
441
}
442
443
/* Called for each step of progress.  The 'progress' parameter represents the progress within the span of 'max_progress' */
444
static void on_progress(enum heif_progress_step step, int progress, void* progress_user_data)
445
{
446
  ProgressUserData *context = (ProgressUserData *) progress_user_data;
447
  Image *image=context->image;
448
  context->step = step;
449
  context->progress = progress;
450
  if (context->image->logging)
451
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
452
                          "on_progress: step=%d, progress=%d",step, progress);
453
  MagickMonitorFormatted(context->progress,context->max_progress,&image->exception,
454
                         "[%s] Loading image: %lux%lu...  ",
455
                         image->filename,
456
                         image->columns,image->rows);
457
}
458
459
/* Called when progress monitor stops */
460
static void end_progress(enum heif_progress_step step, void* progress_user_data)
461
{
462
  ProgressUserData *context = (ProgressUserData *) progress_user_data;
463
  context->step = step;
464
  if (context->image->logging)
465
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
466
                          "end_progress: step=%d",step);
467
}
468
469
#endif /* if HEIF_ENABLE_PROGRESS_MONITOR */
470
471
static Image *ReadHEIFImage(const ImageInfo *image_info,
472
                            ExceptionInfo *exception)
473
52.4k
{
474
52.4k
  Image
475
52.4k
    *image;
476
477
52.4k
  struct heif_context
478
52.4k
    *heif = NULL;
479
480
52.4k
  struct heif_error
481
52.4k
    heif_status;
482
483
52.4k
  struct heif_image_handle
484
52.4k
    *heif_image_handle = NULL;
485
486
52.4k
  struct heif_image
487
52.4k
    *heif_image = NULL;
488
489
52.4k
  struct heif_decoding_options
490
52.4k
    *decode_options;
491
492
52.4k
  ProgressUserData
493
52.4k
    progress_user_data;
494
495
52.4k
  size_t
496
52.4k
    in_len;
497
498
52.4k
  int
499
52.4k
    row_stride;
500
501
52.4k
  unsigned char
502
52.4k
    *in_buf = NULL;
503
504
52.4k
  const uint8_t
505
52.4k
    *pixels = NULL;
506
507
52.4k
  long
508
52.4k
    x,
509
52.4k
    y;
510
511
52.4k
  PixelPacket
512
52.4k
    *q;
513
514
52.4k
  MagickBool
515
52.4k
    ignore_transformations;
516
517
52.4k
  assert(image_info != (const ImageInfo *) NULL);
518
52.4k
  assert(image_info->signature == MagickSignature);
519
52.4k
  assert(exception != (ExceptionInfo *) NULL);
520
52.4k
  assert(exception->signature == MagickSignature);
521
522
52.4k
#ifdef HAVE_HEIF_INIT
523
52.4k
  if (!heif_initialized)
524
4
    {
525
      /* heif_init() accepts a 'struct heif_init_params *' argument */
526
4
      heif_init((struct heif_init_params *) NULL);
527
4
      heif_initialized = MagickTrue;
528
4
    }
529
52.4k
#endif /* HAVE_HEIF_INIT */
530
531
  /*
532
    Open image file.
533
  */
534
52.4k
  image=AllocateImage(image_info);
535
52.4k
  if (image == (Image *) NULL)
536
52.4k
    ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
537
538
52.4k
  if (OpenBlob(image_info,image,ReadBinaryBlobMode,exception) == MagickFail)
539
52.4k
    ThrowReaderException(FileOpenError,UnableToOpenFile,image);
540
541
52.4k
  in_len=GetBlobSize(image);
542
52.4k
  in_buf=MagickAllocateResourceLimitedArray(unsigned char *,in_len,sizeof(*in_buf));
543
52.4k
  if (in_buf == (unsigned char *) NULL)
544
52.4k
    ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
545
546
52.4k
  if (ReadBlob(image,in_len,in_buf) != in_len)
547
52.4k
    ThrowHEIFReaderException(CorruptImageError, UnexpectedEndOfFile, image);
548
549
52.4k
#if LIBHEIF_NUMERIC_VERSION >= 0x01090000
550
52.4k
  ignore_transformations = MagickFalse;
551
52.4k
  {
552
52.4k
    const char
553
52.4k
      *value;
554
555
52.4k
    if ((value=AccessDefinition(image_info,"heif","ignore-transformations")))
556
0
      if (LocaleCompare(value,"TRUE") == 0)
557
0
        ignore_transformations = MagickTrue;
558
52.4k
  }
559
#else
560
  /* Older versions are missing required function to get real width/height. */
561
  ignore_transformations = MagickTrue;
562
#endif
563
564
  /* Init HEIF-Decoder handles */
565
52.4k
  heif=heif_context_alloc();
566
567
52.4k
#if defined(HAVE_HEIF_CONTEXT_SET_MAXIMUM_IMAGE_SIZE_LIMIT)
568
52.4k
  {
569
    /* Add an image size limit */
570
52.4k
    magick_int64_t width_limit = GetMagickResourceLimit(WidthResource);
571
52.4k
    if (MagickResourceInfinity != width_limit)
572
52.4k
      {
573
52.4k
        if (width_limit > INT_MAX)
574
0
          width_limit =  INT_MAX;
575
52.4k
        heif_context_set_maximum_image_size_limit(heif, (int) width_limit);
576
52.4k
      }
577
52.4k
  }
578
52.4k
#endif /* if defined(HAVE_HEIF_CONTEXT_SET_MAXIMUM_IMAGE_SIZE_LIMIT) */
579
580
  /* FIXME: DEPRECATED: use heif_context_read_from_memory_without_copy() instead. */
581
52.4k
  heif_status=heif_context_read_from_memory(heif, in_buf, in_len, NULL);
582
52.4k
  if (heif_status.code == heif_error_Unsupported_filetype
583
52.4k
      || heif_status.code == heif_error_Unsupported_feature)
584
50.9k
    ThrowHEIFReaderException(CoderError, ImageTypeNotSupported, image);
585
50.9k
  if (heif_status.code != heif_error_Ok)
586
12.2k
    {
587
12.2k
      if (image->logging)
588
12.2k
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
589
12.2k
                              "heif_context_read_from_memory() reports error \"%s\"",
590
12.2k
                              heif_status.message);
591
12.2k
      ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image);
592
0
    }
593
594
  /* FIXME: no support for reading multiple images but should be
595
     added, if multiple images will use primary image */
596
38.6k
  {
597
38.6k
    int number_of_top_level_images;
598
38.6k
    number_of_top_level_images=heif_context_get_number_of_top_level_images(heif);
599
38.6k
    if (image->logging)
600
38.6k
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
601
38.6k
                            "Number of top level images: %d (reading primary image only)",
602
38.6k
                            number_of_top_level_images);
603
38.6k
    if (number_of_top_level_images == 0)
604
38.6k
      ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image);
605
606
38.6k
    heif_status=heif_context_get_primary_image_handle(heif, &heif_image_handle);
607
38.6k
    if (heif_status.code == heif_error_Memory_allocation_error)
608
38.6k
      ThrowHEIFReaderException(ResourceLimitError, MemoryAllocationFailed, image);
609
38.6k
    if (heif_status.code != heif_error_Ok)
610
0
      {
611
0
        if (image->logging)
612
0
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
613
0
                                "heif_context_get_primary_image_handle() reports error \"%s\"",
614
0
                                heif_status.message);
615
0
        ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image);
616
0
      }
617
38.6k
  }
618
619
  /*
620
    Note: Those values are preliminary but likely the upper bound
621
    The real image values might be rotated or cropped due to transformations
622
  */
623
38.6k
  image->columns=heif_image_handle_get_width(heif_image_handle);
624
38.6k
  image->rows=heif_image_handle_get_height(heif_image_handle);
625
38.6k
  if (heif_image_handle_has_alpha_channel(heif_image_handle))
626
57
    image->matte=MagickTrue;
627
628
38.6k
  if (image->logging)
629
38.6k
    {
630
38.6k
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
631
38.6k
                            "Geometry: %lux%lu", image->columns, image->rows);
632
38.6k
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
633
38.6k
                            "Matte: %s", image->matte ? "True" : "False");
634
38.6k
    }
635
636
  /* Read EXIF and XMP profile */
637
38.6k
  if (!ReadMetadata(image_info, heif_image_handle, image, exception))
638
0
    {
639
0
      HEIFReadCleanup();
640
0
      return NULL;
641
0
    }
642
643
  /* Read ICC profile */
644
38.6k
  if (!ReadColorProfile(heif_image_handle, image, exception))
645
0
    {
646
0
      HEIFReadCleanup();
647
0
      return NULL;
648
0
    }
649
650
  /*
651
    When apply transformations (the default) the whole image has to be
652
    read to get the real dimensions.
653
  */
654
38.6k
  if (image_info->ping && ignore_transformations)
655
0
    {
656
0
      image->depth = 8;
657
0
      HEIFReadCleanup();
658
0
      CloseBlob(image);
659
0
      return image;
660
0
    }
661
662
38.6k
  if (CheckImagePixelLimits(image, exception) != MagickPass)
663
38.2k
    ThrowHEIFReaderException(ResourceLimitError,ImagePixelLimitExceeded,image);
664
665
  /* Add decoding options support */
666
38.2k
  decode_options = heif_decoding_options_alloc();
667
38.2k
  if (decode_options == (struct heif_decoding_options*) NULL)
668
38.2k
    ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
669
670
38.2k
  progress_user_data.exception = exception;
671
38.2k
  progress_user_data.image = image;
672
38.2k
  progress_user_data.max_progress = 0;
673
38.2k
  progress_user_data.progress = 0;
674
675
  /* version 1 options */
676
38.2k
#if LIBHEIF_NUMERIC_VERSION >= 0x01090000
677
38.2k
  decode_options->ignore_transformations = (ignore_transformations == MagickTrue) ? 1 : 0;
678
#else
679
  decode_options->ignore_transformations = 1;
680
#endif
681
#if HEIF_ENABLE_PROGRESS_MONITOR
682
  decode_options->start_progress = start_progress;
683
  decode_options->on_progress = on_progress;
684
  decode_options->end_progress = end_progress;
685
#endif /* if HEIF_ENABLE_PROGRESS_MONITOR */
686
38.2k
  decode_options->progress_user_data = &progress_user_data;
687
688
  /* version 2 options */
689
38.2k
#if LIBHEIF_NUMERIC_VERSION > 0x01070000
690
38.2k
  decode_options->convert_hdr_to_8bit = 1;
691
38.2k
#endif /* if LIBHEIF_NUMERIC_VERSION > 0x01070000 */
692
693
  /* version 3 options */
694
695
  /* When enabled, an error is returned for invalid input. Otherwise, it will try its best and
696
     add decoding warnings to the decoded heif_image. Default is non-strict. */
697
  /* uint8_t strict_decoding; */
698
699
38.2k
  heif_status=heif_decode_image(heif_image_handle, &heif_image,
700
38.2k
                                heif_colorspace_RGB, image->matte ? heif_chroma_interleaved_RGBA :
701
38.2k
                                heif_chroma_interleaved_RGB,
702
38.2k
                                decode_options);
703
38.2k
  heif_decoding_options_free(decode_options);
704
38.2k
  if (heif_status.code == heif_error_Memory_allocation_error)
705
38.2k
    ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
706
38.2k
  if (heif_status.code != heif_error_Ok)
707
32.6k
    {
708
32.6k
      if (image->logging)
709
32.6k
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
710
32.6k
                              "heif_decode_image() reports error \"%s\"",
711
32.6k
                              heif_status.message);
712
32.6k
      ThrowHEIFReaderException(CorruptImageError, AnErrorHasOccurredReadingFromFile, image);
713
0
    }
714
715
  /*
716
    Update with final values, see preliminary note above
717
718
    These functions are apparently added in libheif 1.9
719
  */
720
5.63k
#if LIBHEIF_NUMERIC_VERSION >= 0x01090000
721
5.63k
  image->columns=heif_image_get_primary_width(heif_image);
722
5.63k
  image->rows=heif_image_get_primary_height(heif_image);
723
724
5.63k
  if (image_info->ping)
725
0
    {
726
0
      image->depth = 8;
727
0
      HEIFReadCleanup();
728
0
      CloseBlob(image);
729
0
      return image;
730
0
    }
731
5.63k
#endif
732
733
5.63k
  image->depth=heif_image_get_bits_per_pixel(heif_image, heif_channel_interleaved);
734
  /* The requested channel is interleaved there depth is a sum of all channels
735
     split it up again: */
736
5.63k
  if (image->logging)
737
5.63k
    {
738
5.63k
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
739
5.63k
                            "heif_image_get_bits_per_pixel: bits_per_pixel=%u", image->depth);
740
5.63k
    }
741
5.63k
  if (image->depth == 32 && image->matte)
742
19
    image->depth = 8;
743
5.61k
  else if (image->depth == 24 && !image->matte)
744
5.61k
    image->depth = 8;
745
0
  else
746
5.63k
    ThrowHEIFReaderException(CoderError, UnsupportedBitsPerSample, image);
747
748
5.63k
  pixels=heif_image_get_plane_readonly(heif_image, heif_channel_interleaved, &row_stride);
749
5.63k
  if (!pixels)
750
5.63k
    ThrowHEIFReaderException(CoderError, NoDataReturned, image);
751
752
5.63k
  if (image->logging)
753
5.63k
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
754
5.63k
                          "heif_image_get_plane_readonly: bytes-per-line=%d",
755
5.63k
                          row_stride);
756
757
  /* Transfer pixels to image, using row stride to find start of each row. */
758
748k
  for (y=0; y < (long)image->rows; y++)
759
743k
    {
760
743k
      const uint8_t *line;
761
743k
      q=SetImagePixelsEx(image,0,y,image->columns,1,exception);
762
743k
      if (q == (PixelPacket *) NULL)
763
742k
        ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
764
742k
      line=pixels+y*row_stride;
765
401M
      for (x=0; x < (long)image->columns; x++)
766
400M
        {
767
400M
          SetRedSample(q,ScaleCharToQuantum(*line++));
768
400M
          SetGreenSample(q,ScaleCharToQuantum(*line++));
769
400M
          SetBlueSample(q,ScaleCharToQuantum(*line++));
770
400M
          if (image->matte) {
771
135k
            SetOpacitySample(q,MaxRGB-ScaleCharToQuantum(*line++));
772
400M
          } else {
773
400M
            SetOpacitySample(q,OpaqueOpacity);
774
400M
          }
775
400M
          q++;
776
400M
        }
777
742k
      if (!SyncImagePixels(image))
778
742k
        ThrowHEIFReaderException(ResourceLimitError,MemoryAllocationFailed,image);
779
742k
    }
780
781
5.16k
  HEIFReadCleanup();
782
5.16k
  CloseBlob(image);
783
5.16k
  return image;
784
5.63k
}
785
786
#endif /* HasHEIF */
787
788
/*
789
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
790
%                                                                             %
791
%                                                                             %
792
%                                                                             %
793
%   R e g i s t e r H E I F I m a g e                                         %
794
%                                                                             %
795
%                                                                             %
796
%                                                                             %
797
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
798
%
799
%  Method RegisterHEIFImage adds attributes for the HEIF image format to
800
%  the list of supported formats.  The attributes include the image format
801
%  tag, a method to read and/or write the format and a brief
802
%  description of the format.
803
%
804
%  The format of the RegisterHEIFImage method is:
805
%
806
%      RegisterHEIFImage(void)
807
%
808
*/
809
ModuleExport void RegisterHEIFImage(void)
810
4
{
811
4
#if defined(HasHEIF)
812
4
  static const char
813
4
    description[] = "HEIF Image Format";
814
815
4
  static char
816
4
    version[20];
817
818
4
  MagickInfo
819
4
    *entry;
820
821
4
  unsigned int
822
4
    heif_major,
823
4
    heif_minor,
824
4
    heif_revision;
825
826
4
  int encoder_version=heif_get_version_number();
827
4
  heif_major=(encoder_version >> 16) & 0xff;
828
4
  heif_minor=(encoder_version >> 8) & 0xff;
829
4
  heif_revision=encoder_version & 0xff;
830
4
  *version='\0';
831
4
  (void) snprintf(version, sizeof(version),
832
4
                  "heif v%u.%u.%u", heif_major,
833
4
                  heif_minor, heif_revision);
834
835
4
  entry=SetMagickInfo("AVIF");
836
4
  entry->decoder=(DecoderHandler) ReadHEIFImage;
837
4
  entry->magick=(MagickHandler) IsHEIF;
838
4
  entry->description=description;
839
4
  entry->adjoin=False;
840
4
  entry->seekable_stream=MagickTrue;
841
4
  if (*version != '\0')
842
4
    entry->version=version;
843
4
  entry->module="HEIF";
844
4
  entry->coder_class=PrimaryCoderClass;
845
4
  (void) RegisterMagickInfo(entry);
846
847
4
  entry=SetMagickInfo("HEIF");
848
4
  entry->decoder=(DecoderHandler) ReadHEIFImage;
849
4
  entry->magick=(MagickHandler) IsHEIF;
850
4
  entry->description=description;
851
4
  entry->adjoin=False;
852
4
  entry->seekable_stream=MagickTrue;
853
4
  if (*version != '\0')
854
4
    entry->version=version;
855
4
  entry->module="HEIF";
856
4
  entry->coder_class=PrimaryCoderClass;
857
4
  (void) RegisterMagickInfo(entry);
858
859
4
  entry=SetMagickInfo("HEIC");
860
4
  entry->decoder=(DecoderHandler) ReadHEIFImage;
861
4
  entry->magick=(MagickHandler) IsHEIF;
862
4
  entry->description=description;
863
4
  entry->adjoin=False;
864
4
  entry->seekable_stream=MagickTrue;
865
4
  if (*version != '\0')
866
4
    entry->version=version;
867
4
  entry->module="HEIF";
868
4
  entry->coder_class=PrimaryCoderClass;
869
4
  (void) RegisterMagickInfo(entry);
870
4
#endif /* HasHEIF */
871
4
}
872
873
/*
874
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
875
%                                                                             %
876
%                                                                             %
877
%                                                                             %
878
%   U n r e g i s t e r H E I F I m a g e                                     %
879
%                                                                             %
880
%                                                                             %
881
%                                                                             %
882
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
883
%
884
%  Method UnregisterHEIFImage removes format registrations made by the
885
%  HEIF module from the list of supported formats.
886
%
887
%  The format of the UnregisterHEIFImage method is:
888
%
889
%      UnregisterHEIFImage(void)
890
%
891
*/
892
ModuleExport void UnregisterHEIFImage(void)
893
0
{
894
0
#if defined(HasHEIF)
895
0
  (void) UnregisterMagickInfo("AVIF");
896
0
  (void) UnregisterMagickInfo("HEIF");
897
0
  (void) UnregisterMagickInfo("HEIC");
898
0
#ifdef HAVE_HEIF_DEINIT
899
0
  if (heif_initialized)
900
0
    heif_deinit();
901
0
#endif /* HAVE_HEIF_DEINIT */
902
0
#endif /* HasHEIF */
903
0
}