1 /* load heif images with libheif
2 *
3 * 19/1/19
4 * - from niftiload.c
5 * 24/7/19 [zhoux2016]
6 * - always fetch metadata from the main image (thumbs don't have it)
7 * 24/7/19
8 * - close early on minimise
9 * - close early on error
10 * 1/9/19 [meyermarcel]
11 * - handle alpha
12 * 30/9/19
13 * - much faster handling of thumbnail=TRUE and missing thumbnail ... we
14 * were reselecting the image for each scanline
15 * 3/10/19
16 * - restart after minimise
17 * 15/3/20
18 * - revise for new VipsSource API
19 * 10/5/20
20 * - deprecate autorotate -- it's too difficult to support properly
21 * 31/7/20
22 * - block broken thumbnails, if we can
23 * 14/2/21 kleisauke
24 * - move GObject part to heif2vips.c
25 */
26
27 /*
28
29 This file is part of VIPS.
30
31 VIPS is free software; you can redistribute it and/or modify
32 it under the terms of the GNU Lesser General Public License as published by
33 the Free Software Foundation; either version 2 of the License, or
34 (at your option) any later version.
35
36 This program is distributed in the hope that it will be useful,
37 but WITHOUT ANY WARRANTY; without even the implied warranty of
38 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 GNU Lesser General Public License for more details.
40
41 You should have received a copy of the GNU Lesser General Public License
42 along with this program; if not, write to the Free Software
43 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
44 02110-1301 USA
45
46 */
47
48 /*
49
50 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
51
52 */
53
54 /*
55 #define DEBUG_VERBOSE
56 #define VIPS_DEBUG
57 #define DEBUG
58 */
59
60 #ifdef HAVE_CONFIG_H
61 #include <config.h>
62 #endif /*HAVE_CONFIG_H*/
63 #include <vips/intl.h>
64
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68
69 #include <vips/vips.h>
70 #include <vips/debug.h>
71 #include <vips/internal.h>
72
73 /* These are shared with the encoder.
74 */
75 #if defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER)
76
77 #include "pforeign.h"
78
79 const char *vips__heic_suffs[] = {
80 ".heic",
81 ".heif",
82 NULL
83 };
84
85 const char *vips__avif_suffs[] = {
86 ".avif",
87 NULL
88 };
89
90 const char *vips__heif_suffs[] = {
91 ".heic",
92 ".heif",
93 ".avif",
94 NULL
95 };
96
97 #endif /*defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER)*/
98
99 #ifdef HAVE_HEIF_DECODER
100
101 #include <libheif/heif.h>
102
103 #define VIPS_TYPE_FOREIGN_LOAD_HEIF (vips_foreign_load_heif_get_type())
104 #define VIPS_FOREIGN_LOAD_HEIF( obj ) \
105 (G_TYPE_CHECK_INSTANCE_CAST( (obj), \
106 VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeif ))
107 #define VIPS_FOREIGN_LOAD_HEIF_CLASS( klass ) \
108 (G_TYPE_CHECK_CLASS_CAST( (klass), \
109 VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeifClass))
110 #define VIPS_IS_FOREIGN_LOAD_HEIF( obj ) \
111 (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_HEIF ))
112 #define VIPS_IS_FOREIGN_LOAD_HEIF_CLASS( klass ) \
113 (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_HEIF ))
114 #define VIPS_FOREIGN_LOAD_HEIF_GET_CLASS( obj ) \
115 (G_TYPE_INSTANCE_GET_CLASS( (obj), \
116 VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeifClass ))
117
118 typedef struct _VipsForeignLoadHeif {
119 VipsForeignLoad parent_object;
120
121 /* Pages to load.
122 */
123 int page;
124 int n;
125
126 /* Fetch the thumbnail instead of the image. If there is no thumbnail,
127 * just fetch the image.
128 */
129 gboolean thumbnail;
130
131 /* Apply any orientation tags in the header.
132 *
133 * This is deprecated and does nothing. Non-autorotated reads from
134 * libheif are surprisingly hard to support well, since orientation can
135 * be represented in several different ways in HEIC files and devices
136 * vary in how they do this.
137 */
138 gboolean autorotate;
139
140 /* Context for this image.
141 */
142 struct heif_context *ctx;
143
144 /* Number of top-level images in this file.
145 */
146 int n_top;
147
148 /* TRUE for RGBA ... otherwise, RGB.
149 */
150 gboolean has_alpha;
151
152 /* Size of final output image.
153 */
154 int width;
155 int height;
156
157 /* Size of each page.
158 */
159 int page_width;
160 int page_height;
161
162 /* The page number currently in @handle.
163 */
164 int page_no;
165
166 /* TRUE if @handle has selected the thumbnail rather than the main
167 * image.
168 */
169 gboolean thumbnail_set;
170
171 /* The page number of the primary image.
172 */
173 int primary_page;
174
175 /* Array of top-level image IDs.
176 */
177 heif_item_id *id;
178
179 /* Handle for the currently selected image.
180 */
181 struct heif_image_handle *handle;
182
183 /* Decoded pixel data for the current image.
184 */
185 struct heif_image *img;
186
187 /* Valid until img is released.
188 */
189 int stride;
190 const uint8_t *data;
191
192 /* Set from subclasses.
193 */
194 VipsSource *source;
195
196 /* The reader struct. We use this to attach to our VipsSource. This
197 * has to be alloced rather than in our struct, since it may change
198 * size in libheif API versions.
199 */
200 struct heif_reader *reader;
201
202 } VipsForeignLoadHeif;
203
204 void
vips__heif_error(struct heif_error * error)205 vips__heif_error( struct heif_error *error )
206 {
207 if( error->code )
208 vips_error( "heif", "%s (%d.%d)", error->message, error->code,
209 error->subcode );
210 }
211
212 typedef struct _VipsForeignLoadHeifClass {
213 VipsForeignLoadClass parent_class;
214
215 } VipsForeignLoadHeifClass;
216
217 G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadHeif, vips_foreign_load_heif,
218 VIPS_TYPE_FOREIGN_LOAD );
219
220 static void
vips_foreign_load_heif_dispose(GObject * gobject)221 vips_foreign_load_heif_dispose( GObject *gobject )
222 {
223 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) gobject;
224
225 heif->data = NULL;
226 VIPS_FREEF( heif_image_release, heif->img );
227 VIPS_FREEF( heif_image_handle_release, heif->handle );
228 VIPS_FREEF( heif_context_free, heif->ctx );
229 VIPS_FREE( heif->id );
230 VIPS_FREE( heif->reader );
231 VIPS_UNREF( heif->source );
232
233 G_OBJECT_CLASS( vips_foreign_load_heif_parent_class )->
234 dispose( gobject );
235 }
236
237 static int
vips_foreign_load_heif_build(VipsObject * object)238 vips_foreign_load_heif_build( VipsObject *object )
239 {
240 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object;
241
242 #ifdef DEBUG
243 printf( "vips_foreign_load_heif_build:\n" );
244 #endif /*DEBUG*/
245
246 if( heif->source &&
247 vips_source_rewind( heif->source ) )
248 return( -1 );
249
250 if( !heif->ctx ) {
251 struct heif_error error;
252
253 heif->ctx = heif_context_alloc();
254 error = heif_context_read_from_reader( heif->ctx,
255 heif->reader, heif, NULL );
256 if( error.code ) {
257 vips__heif_error( &error );
258 return( -1 );
259 }
260 }
261
262 if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_parent_class )->
263 build( object ) )
264 return( -1 );
265
266 return( 0 );
267 }
268
269 static const char *heif_magic[] = {
270 "ftypheic", /* A regular heif image */
271 "ftypheix", /* Extended range (>8 bit) image */
272 "ftyphevc", /* Image sequence */
273 "ftypheim", /* Image sequence */
274 "ftypheis", /* Scaleable image */
275 "ftyphevm", /* Multiview sequence */
276 "ftyphevs", /* Scaleable sequence */
277 "ftypmif1", /* Nokia alpha_ image */
278 "ftypmsf1", /* Nokia animation image */
279 "ftypavif" /* AV1 image format */
280 };
281
282 /* The API has:
283 *
284 * enum heif_filetype_result result = heif_check_filetype( buf, 12 );
285 *
286 * but it's very conservative and seems to be missing some of the Nokia heif
287 * types.
288 */
289 static int
vips_foreign_load_heif_is_a(const char * buf,int len)290 vips_foreign_load_heif_is_a( const char *buf, int len )
291 {
292 if( len >= 12 ) {
293 const guint32 chunk_len =
294 (guint32) buf[0] << 24 |
295 (guint32) buf[1] << 16 |
296 (guint32) buf[2] << 8 |
297 (guint32) buf[3];
298
299 int i;
300
301 /* We've seen real files with 36 here, so 64 should be
302 * plenty.
303 */
304 if( chunk_len > 64 ||
305 chunk_len % 4 != 0 )
306 return( 0 );
307
308 for( i = 0; i < VIPS_NUMBER( heif_magic ); i++ )
309 if( strncmp( buf + 4, heif_magic[i], 8 ) == 0 )
310 return( 1 );
311 }
312
313 return( 0 );
314 }
315
316 static VipsForeignFlags
vips_foreign_load_heif_get_flags(VipsForeignLoad * load)317 vips_foreign_load_heif_get_flags( VipsForeignLoad *load )
318 {
319 /* FIXME .. could support random access for grid images.
320 */
321 return( VIPS_FOREIGN_SEQUENTIAL );
322 }
323
324 /* We've selected the page. Try to select the associated thumbnail instead,
325 * if we can.
326 */
327 static int
vips_foreign_load_heif_set_thumbnail(VipsForeignLoadHeif * heif)328 vips_foreign_load_heif_set_thumbnail( VipsForeignLoadHeif *heif )
329 {
330 heif_item_id thumb_ids[1];
331 int n_thumbs;
332 struct heif_image_handle *thumb_handle;
333 struct heif_image *thumb_img;
334 struct heif_error error;
335 double main_aspect;
336 double thumb_aspect;
337
338 #ifdef DEBUG
339 printf( "vips_foreign_load_heif_set_thumbnail:\n" );
340 #endif /*DEBUG*/
341
342 n_thumbs = heif_image_handle_get_list_of_thumbnail_IDs(
343 heif->handle, thumb_ids, 1 );
344 if( n_thumbs == 0 )
345 return( 0 );
346
347 error = heif_image_handle_get_thumbnail( heif->handle,
348 thumb_ids[0], &thumb_handle );
349 if( error.code ) {
350 vips__heif_error( &error );
351 return( -1 );
352 }
353
354 /* Just checking the width and height of the handle isn't
355 * enough -- we have to experimentally decode it and test the
356 * decoded dimensions.
357 */
358 error = heif_decode_image( thumb_handle, &thumb_img,
359 heif_colorspace_RGB,
360 heif_chroma_interleaved_RGB,
361 NULL );
362 if( error.code ) {
363 VIPS_FREEF( heif_image_handle_release, thumb_handle );
364 vips__heif_error( &error );
365 return( -1 );
366 }
367
368 thumb_aspect = (double)
369 heif_image_get_width( thumb_img, heif_channel_interleaved ) /
370 heif_image_get_height( thumb_img, heif_channel_interleaved );
371
372 VIPS_FREEF( heif_image_release, thumb_img );
373
374 main_aspect = (double)
375 heif_image_handle_get_width( heif->handle ) /
376 heif_image_handle_get_height( heif->handle );
377
378 /* The bug we are working around has decoded thumbs as 512x512
379 * with the main image as 6kx4k, so a 0.1 threshold is more
380 * than tight enough to spot the error.
381 */
382 if( fabs( main_aspect - thumb_aspect ) > 0.1 ) {
383 VIPS_FREEF( heif_image_handle_release, thumb_handle );
384 return( 0 );
385 }
386
387 VIPS_FREEF( heif_image_handle_release, heif->handle );
388 heif->handle = thumb_handle;
389
390 return( 0 );
391 }
392
393 /* Select a page. If thumbnail is set, select the thumbnail for that page, if
394 * there is one.
395 */
396 static int
vips_foreign_load_heif_set_page(VipsForeignLoadHeif * heif,int page_no,gboolean thumbnail)397 vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif,
398 int page_no, gboolean thumbnail )
399 {
400 if( !heif->handle ||
401 page_no != heif->page_no ||
402 thumbnail != heif->thumbnail_set ) {
403 struct heif_error error;
404
405 #ifdef DEBUG
406 printf( "vips_foreign_load_heif_set_page: %d, thumbnail = %d\n",
407 page_no, thumbnail );
408 #endif /*DEBUG*/
409
410 VIPS_FREEF( heif_image_handle_release, heif->handle );
411 VIPS_FREEF( heif_image_release, heif->img );
412 heif->data = NULL;
413 heif->thumbnail_set = FALSE;
414
415 error = heif_context_get_image_handle( heif->ctx,
416 heif->id[page_no], &heif->handle );
417 if( error.code ) {
418 vips__heif_error( &error );
419 return( -1 );
420 }
421
422 if( thumbnail ) {
423 if( vips_foreign_load_heif_set_thumbnail( heif ) )
424 return( -1 );
425
426 /* If we were asked to select the thumbnail, say we
427 * did, even if there are no thumbnails and we just
428 * selected the main image.
429 *
430 * If we don't do this, next time around in _generate
431 * we'll try to select the thumbnail again, which will
432 * be horribly slow.
433 */
434 heif->thumbnail_set = TRUE;
435 }
436
437 heif->page_no = page_no;
438 }
439
440 return( 0 );
441 }
442
443 static int
vips_foreign_load_heif_set_header(VipsForeignLoadHeif * heif,VipsImage * out)444 vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out )
445 {
446 VipsForeignLoad *load = (VipsForeignLoad *) heif;
447
448 int bands;
449 int i;
450 /* Surely, 16 metadata items will be enough for anyone.
451 */
452 heif_item_id id[16];
453 int n_metadata;
454 struct heif_error error;
455 VipsForeignHeifCompression compression;
456
457 /* We take the metadata from the non-thumbnail first page. HEIC
458 * thumbnails don't have metadata.
459 */
460 if( vips_foreign_load_heif_set_page( heif, heif->page, FALSE ) )
461 return( -1 );
462
463 /* Verify dimensions
464 */
465 if ( heif->page_width < 1 || heif->page_height < 1 ) {
466 vips_error( "heifload", "%s", _( "bad dimensions" ) );
467 return( -1 );
468 }
469
470 heif->has_alpha = heif_image_handle_has_alpha_channel( heif->handle );
471 #ifdef DEBUG
472 printf( "heif_image_handle_has_alpha_channel() = %d\n",
473 heif->has_alpha );
474 #endif /*DEBUG*/
475 bands = heif->has_alpha ? 4 : 3;
476
477 /* FIXME .. IPTC as well?
478 */
479 n_metadata = heif_image_handle_get_list_of_metadata_block_IDs(
480 heif->handle, NULL, id, VIPS_NUMBER( id ) );
481 for( i = 0; i < n_metadata; i++ ) {
482 size_t length = heif_image_handle_get_metadata_size(
483 heif->handle, id[i] );
484 const char *type = heif_image_handle_get_metadata_type(
485 heif->handle, id[i] );
486
487 unsigned char *data;
488 char name[256];
489
490 #ifdef DEBUG
491 printf( "metadata type = %s, length = %zu\n", type, length );
492 #endif /*DEBUG*/
493
494 if( !length )
495 continue;
496 if( !(data = VIPS_ARRAY( out, length, unsigned char )) )
497 return( -1 );
498 error = heif_image_handle_get_metadata(
499 heif->handle, id[i], data );
500 if( error.code ) {
501 vips__heif_error( &error );
502 return( -1 );
503 }
504
505 /* We need to skip the first four bytes of EXIF, they just
506 * contain the offset.
507 */
508 if( length > 4 &&
509 g_ascii_strcasecmp( type, "exif" ) == 0 ) {
510 data += 4;
511 length -= 4;
512 }
513
514 /* exif has a special name.
515 *
516 * XMP metadata is just attached with the "mime" type, and
517 * usually start with "<x:xmpmeta".
518 */
519 if( g_ascii_strcasecmp( type, "exif" ) == 0 )
520 vips_snprintf( name, 256, VIPS_META_EXIF_NAME );
521 else if( g_ascii_strcasecmp( type, "mime" ) == 0 &&
522 length > 10 &&
523 vips_isprefix( "<x:xmpmeta", (const char *) data ) )
524 vips_snprintf( name, 256, VIPS_META_XMP_NAME );
525 else
526 vips_snprintf( name, 256, "heif-%s-%d", type, i );
527
528 vips_image_set_blob( out, name,
529 (VipsCallbackFn) NULL, data, length );
530
531 /* image_set will automatically parse EXIF, if necessary.
532 */
533 }
534
535 /* We use libheif's autorotate, so we need to remove any EXIF
536 * orientaion tags.
537 *
538 * According to the HEIF standard, EXIF orientation tags are only
539 * informational and images should not be rotated because of them.
540 * Unless we strip these tags, there's a danger downstream processing
541 * could double-rotate.
542 */
543 vips_autorot_remove_angle( out );
544
545 #ifdef HAVE_HEIF_COLOR_PROFILE
546 enum heif_color_profile_type profile_type =
547 heif_image_handle_get_color_profile_type( heif->handle );
548
549 #ifdef DEBUG
550 {
551 printf( "profile type = " );
552 switch( profile_type ) {
553 case heif_color_profile_type_not_present:
554 printf( "none" );
555 break;
556
557 case heif_color_profile_type_nclx:
558 printf( "nclx" );
559 break;
560
561 case heif_color_profile_type_rICC:
562 printf( "rICC" );
563 break;
564
565 case heif_color_profile_type_prof:
566 printf( "prof" );
567 break;
568
569 default:
570 printf( "unknown" );
571 break;
572 }
573 printf( "\n" );
574 }
575 #endif /*DEBUG*/
576
577 /* lcms can load standard (prof) and reduced (rICC) profiles
578 */
579 if( profile_type == heif_color_profile_type_prof ||
580 profile_type == heif_color_profile_type_rICC ) {
581 size_t length = heif_image_handle_get_raw_color_profile_size(
582 heif->handle );
583
584 unsigned char *data;
585
586 if( !(data = VIPS_ARRAY( out, length, unsigned char )) )
587 return( -1 );
588 error = heif_image_handle_get_raw_color_profile(
589 heif->handle, data );
590 if( error.code ) {
591 vips__heif_error( &error );
592 return( -1 );
593 }
594
595 #ifdef DEBUG
596 printf( "profile data, length = %zd\n", length );
597 #endif /*DEBUG*/
598
599 vips_image_set_blob( out, VIPS_META_ICC_NAME,
600 (VipsCallbackFn) NULL, data, length );
601 }
602 else if( profile_type == heif_color_profile_type_nclx ) {
603 g_warning( "heifload: ignoring nclx profile" );
604 }
605 #endif /*HAVE_HEIF_COLOR_PROFILE*/
606
607 vips_image_set_int( out, "heif-primary", heif->primary_page );
608 vips_image_set_int( out, VIPS_META_N_PAGES, heif->n_top );
609
610 /* Only set page-height if we have more than one page, or this could
611 * accidentally turn into an animated image later.
612 */
613 if( heif->n > 1 )
614 vips_image_set_int( out,
615 VIPS_META_PAGE_HEIGHT, heif->page_height );
616
617 /* Determine compression from HEIF "brand". heif_avif and heif_avis
618 * were added in v1.7.
619 */
620 compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC;
621
622 #ifdef HAVE_HEIF_AVIF
623 {
624 const unsigned char *brand_data;
625
626 if( (brand_data = vips_source_sniff( heif->source, 12 )) ) {
627 enum heif_brand brand;
628 brand = heif_main_brand( brand_data, 12 );
629 if( brand == heif_avif ||
630 brand == heif_avis )
631 compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
632 }
633 }
634 #endif /*HAVE_HEIF_AVIF*/
635
636 vips_image_set_string( out, "heif-compression",
637 vips_enum_nick( VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
638 compression ) );
639
640 /* FIXME .. we always decode to RGB in generate. We should check for
641 * all grey images, perhaps.
642 */
643 if( vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ) )
644 return( -1 );
645 vips_image_init_fields( out,
646 heif->page_width, heif->page_height * heif->n, bands,
647 VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB,
648 1.0, 1.0 );
649
650 VIPS_SETSTR( load->out->filename,
651 vips_connection_filename( VIPS_CONNECTION( heif->source ) ) );
652
653 return( 0 );
654 }
655
656 static int
vips_foreign_load_heif_header(VipsForeignLoad * load)657 vips_foreign_load_heif_header( VipsForeignLoad *load )
658 {
659 VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load );
660 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) load;
661
662 struct heif_error error;
663 heif_item_id primary_id;
664 int i;
665
666 #ifdef DEBUG
667 printf( "vips_foreign_load_heif_header:\n" );
668 #endif /*DEBUG*/
669
670 heif->n_top = heif_context_get_number_of_top_level_images( heif->ctx );
671 heif->id = VIPS_ARRAY( NULL, heif->n_top, heif_item_id );
672 heif_context_get_list_of_top_level_image_IDs( heif->ctx,
673 heif->id, heif->n_top );
674
675 /* Note page number of primary image.
676 */
677 error = heif_context_get_primary_image_ID( heif->ctx, &primary_id );
678 if( error.code ) {
679 vips__heif_error( &error );
680 return( -1 );
681 }
682 for( i = 0; i < heif->n_top; i++ )
683 if( heif->id[i] == primary_id )
684 heif->primary_page = i;
685
686 /* If @n and @page have not been set, @page defaults to the primary
687 * page.
688 */
689 if( !vips_object_argument_isset( VIPS_OBJECT( load ), "page" ) &&
690 !vips_object_argument_isset( VIPS_OBJECT( load ), "n" ) )
691 heif->page = heif->primary_page;
692
693 if( heif->n == -1 )
694 heif->n = heif->n_top - heif->page;
695 if( heif->page < 0 ||
696 heif->n <= 0 ||
697 heif->page + heif->n > heif->n_top ) {
698 vips_error( class->nickname, "%s", _( "bad page number" ) );
699 return( -1 );
700 }
701
702 #ifdef DEBUG
703 for( i = heif->page; i < heif->page + heif->n; i++ ) {
704 heif_item_id thumb_ids[1];
705 int n_items;
706 int n_thumbs;
707 int j;
708
709 if( vips_foreign_load_heif_set_page( heif, i, FALSE ) )
710 return( -1 );
711
712 n_thumbs = heif_image_handle_get_number_of_thumbnails(
713 heif->handle );
714 n_items = heif_image_handle_get_list_of_thumbnail_IDs(
715 heif->handle, thumb_ids, 1 );
716
717 printf( "page = %d\n", i );
718 printf( "n_thumbs = %d\n", n_thumbs );
719 printf( "n_items = %d\n", n_items );
720
721 for( j = 0; j < n_items; j++ ) {
722 struct heif_image_handle *thumb_handle;
723
724 error = heif_image_handle_get_thumbnail( heif->handle,
725 thumb_ids[j], &thumb_handle );
726 if( error.code ) {
727 vips__heif_error( &error );
728 return( -1 );
729 }
730
731 printf( " thumb %d\n", j );
732 printf( " width = %d\n",
733 heif_image_handle_get_width( thumb_handle ) );
734 printf( " height = %d\n",
735 heif_image_handle_get_height( thumb_handle ) );
736 }
737 }
738 #endif /*DEBUG*/
739
740 /* All pages must be the same size for libvips toilet roll images.
741 */
742 if( vips_foreign_load_heif_set_page( heif,
743 heif->page, heif->thumbnail ) )
744 return( -1 );
745 heif->page_width = heif_image_handle_get_width( heif->handle );
746 heif->page_height = heif_image_handle_get_height( heif->handle );
747 for( i = heif->page + 1; i < heif->page + heif->n; i++ ) {
748 if( vips_foreign_load_heif_set_page( heif,
749 i, heif->thumbnail ) )
750 return( -1 );
751 if( heif_image_handle_get_width( heif->handle )
752 != heif->page_width ||
753 heif_image_handle_get_height( heif->handle )
754 != heif->page_height ) {
755 vips_error( class->nickname, "%s",
756 _( "not all pages are the same size" ) );
757 return( -1 );
758 }
759 }
760
761 #ifdef DEBUG
762 printf( "page_width = %d\n", heif->page_width );
763 printf( "page_height = %d\n", heif->page_height );
764
765 printf( "n_top = %d\n", heif->n_top );
766 for( i = 0; i < heif->n_top; i++ ) {
767 printf( " id[%d] = %d\n", i, heif->id[i] );
768 if( vips_foreign_load_heif_set_page( heif, i, FALSE ) )
769 return( -1 );
770 printf( " width = %d\n",
771 heif_image_handle_get_width( heif->handle ) );
772 printf( " height = %d\n",
773 heif_image_handle_get_height( heif->handle ) );
774 printf( " has_depth = %d\n",
775 heif_image_handle_has_depth_image( heif->handle ) );
776 printf( " has_alpha = %d\n",
777 heif_image_handle_has_alpha_channel( heif->handle ) );
778 printf( " n_metadata = %d\n",
779 heif_image_handle_get_number_of_metadata_blocks(
780 heif->handle, NULL ) );
781 #ifdef HAVE_HEIF_COLOR_PROFILE
782 printf( " colour profile type = 0x%xd\n",
783 heif_image_handle_get_color_profile_type(
784 heif->handle ) );
785 #endif /*HAVE_HEIF_COLOR_PROFILE*/
786 }
787 #endif /*DEBUG*/
788
789 if( vips_foreign_load_heif_set_header( heif, load->out ) )
790 return( -1 );
791
792 vips_source_minimise( heif->source );
793
794 return( 0 );
795 }
796
797 #ifdef DEBUG
798 void
vips__heif_image_print(struct heif_image * img)799 vips__heif_image_print( struct heif_image *img )
800 {
801 const static enum heif_channel channel[] = {
802 heif_channel_Y,
803 heif_channel_Cb,
804 heif_channel_Cr,
805 heif_channel_R,
806 heif_channel_G,
807 heif_channel_B,
808 heif_channel_Alpha,
809 heif_channel_interleaved
810 };
811
812 const static char *channel_name[] = {
813 "heif_channel_Y",
814 "heif_channel_Cb",
815 "heif_channel_Cr",
816 "heif_channel_R",
817 "heif_channel_G",
818 "heif_channel_B",
819 "heif_channel_Alpha",
820 "heif_channel_interleaved"
821 };
822
823 int i;
824
825 printf( "vips__heif_image_print:\n" );
826 for( i = 0; i < VIPS_NUMBER( channel ); i++ ) {
827 if( !heif_image_has_channel( img, channel[i] ) )
828 continue;
829
830 printf( "\t%s:\n", channel_name[i] );
831 printf( "\t\twidth = %d\n",
832 heif_image_get_width( img, channel[i] ) );
833 printf( "\t\theight = %d\n",
834 heif_image_get_height( img, channel[i] ) );
835 printf( "\t\tbits = %d\n",
836 heif_image_get_bits_per_pixel( img, channel[i] ) );
837 }
838 }
839 #endif /*DEBUG*/
840
841 static int
vips_foreign_load_heif_generate(VipsRegion * or,void * seq,void * a,void * b,gboolean * stop)842 vips_foreign_load_heif_generate( VipsRegion *or,
843 void *seq, void *a, void *b, gboolean *stop )
844 {
845 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) a;
846 VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( heif );
847 VipsRect *r = &or->valid;
848
849 int page = r->top / heif->page_height + heif->page;
850 int line = r->top % heif->page_height;
851
852 #ifdef DEBUG_VERBOSE
853 printf( "vips_foreign_load_heif_generate: line %d\n", r->top );
854 #endif /*DEBUG_VERBOSE*/
855
856 g_assert( r->height == 1 );
857
858 if( vips_foreign_load_heif_set_page( heif, page, heif->thumbnail ) )
859 return( -1 );
860
861 if( !heif->img ) {
862 struct heif_error error;
863 struct heif_decoding_options *options;
864 enum heif_chroma chroma = heif->has_alpha ?
865 heif_chroma_interleaved_RGBA :
866 heif_chroma_interleaved_RGB;
867
868 options = heif_decoding_options_alloc();
869 #ifdef HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT
870 /* VIPS_FORMAT_UCHAR is assumed so downsample HDR to 8bpc
871 */
872 options->convert_hdr_to_8bit = TRUE;
873 #endif /*HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT*/
874 error = heif_decode_image( heif->handle, &heif->img,
875 heif_colorspace_RGB, chroma,
876 options );
877 heif_decoding_options_free( options );
878 if( error.code ) {
879 vips__heif_error( &error );
880 return( -1 );
881 }
882
883 #ifdef DEBUG
884 vips__heif_image_print( heif->img );
885 #endif /*DEBUG*/
886 }
887
888 if( !heif->data ) {
889 int image_width = heif_image_get_width( heif->img,
890 heif_channel_interleaved );
891 int image_height = heif_image_get_height( heif->img,
892 heif_channel_interleaved );
893
894 /* We can sometimes get inconsistency between the dimensions
895 * reported on the handle, and the final image we fetch. Error
896 * out to prevent a segv.
897 */
898 if( image_width != heif->page_width ||
899 image_height != heif->page_height ) {
900 vips_error( class->nickname,
901 "%s", _( "bad image dimensions on decode" ) );
902 return( -1 );
903 }
904
905 if( !(heif->data = heif_image_get_plane_readonly( heif->img,
906 heif_channel_interleaved, &heif->stride )) ) {
907 vips_error( class->nickname,
908 "%s", _( "unable to get image data" ) );
909 return( -1 );
910 }
911 }
912
913 memcpy( VIPS_REGION_ADDR( or, 0, r->top ),
914 heif->data + heif->stride * line,
915 VIPS_IMAGE_SIZEOF_LINE( or->im ) );
916
917 return( 0 );
918 }
919
920 static void
vips_foreign_load_heif_minimise(VipsObject * object,VipsForeignLoadHeif * heif)921 vips_foreign_load_heif_minimise( VipsObject *object, VipsForeignLoadHeif *heif )
922 {
923 vips_source_minimise( heif->source );
924 }
925
926 static int
vips_foreign_load_heif_load(VipsForeignLoad * load)927 vips_foreign_load_heif_load( VipsForeignLoad *load )
928 {
929 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) load;
930
931 VipsImage **t = (VipsImage **)
932 vips_object_local_array( VIPS_OBJECT( load ), 3 );
933
934 #ifdef DEBUG
935 printf( "vips_foreign_load_heif_load: loading image\n" );
936 #endif /*DEBUG*/
937
938 t[0] = vips_image_new();
939 if( vips_foreign_load_heif_set_header( heif, t[0] ) )
940 return( -1 );
941
942 /* CLose input immediately at end of read.
943 */
944 g_signal_connect( t[0], "minimise",
945 G_CALLBACK( vips_foreign_load_heif_minimise ), heif );
946
947 if( vips_image_generate( t[0],
948 NULL, vips_foreign_load_heif_generate, NULL, heif, NULL ) ||
949 vips_sequential( t[0], &t[1], NULL ) ||
950 vips_image_write( t[1], load->real ) )
951 return( -1 );
952
953 if( vips_source_decode( heif->source ) )
954 return( -1 );
955
956 return( 0 );
957 }
958
959 static void
vips_foreign_load_heif_class_init(VipsForeignLoadHeifClass * class)960 vips_foreign_load_heif_class_init( VipsForeignLoadHeifClass *class )
961 {
962 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
963 VipsObjectClass *object_class = (VipsObjectClass *) class;
964 VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
965
966 gobject_class->dispose = vips_foreign_load_heif_dispose;
967 gobject_class->set_property = vips_object_set_property;
968 gobject_class->get_property = vips_object_get_property;
969
970 object_class->nickname = "heifload_base";
971 object_class->description = _( "load a HEIF image" );
972 object_class->build = vips_foreign_load_heif_build;
973
974 load_class->get_flags = vips_foreign_load_heif_get_flags;
975 load_class->header = vips_foreign_load_heif_header;
976 load_class->load = vips_foreign_load_heif_load;
977
978 VIPS_ARG_INT( class, "page", 2,
979 _( "Page" ),
980 _( "Load this page from the file" ),
981 VIPS_ARGUMENT_OPTIONAL_INPUT,
982 G_STRUCT_OFFSET( VipsForeignLoadHeif, page ),
983 0, 100000, 0 );
984
985 VIPS_ARG_INT( class, "n", 3,
986 _( "n" ),
987 _( "Load this many pages" ),
988 VIPS_ARGUMENT_OPTIONAL_INPUT,
989 G_STRUCT_OFFSET( VipsForeignLoadHeif, n ),
990 -1, 100000, 1 );
991
992 VIPS_ARG_BOOL( class, "thumbnail", 4,
993 _( "Thumbnail" ),
994 _( "Fetch thumbnail image" ),
995 VIPS_ARGUMENT_OPTIONAL_INPUT,
996 G_STRUCT_OFFSET( VipsForeignLoadHeif, thumbnail ),
997 FALSE );
998
999 VIPS_ARG_BOOL( class, "autorotate", 21,
1000 _( "Autorotate" ),
1001 _( "Rotate image using exif orientation" ),
1002 VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
1003 G_STRUCT_OFFSET( VipsForeignLoadHeif, autorotate ),
1004 FALSE );
1005
1006 }
1007
1008 static gint64
vips_foreign_load_heif_get_position(void * userdata)1009 vips_foreign_load_heif_get_position( void *userdata )
1010 {
1011 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata;
1012
1013 return( vips_source_seek( heif->source, 0L, SEEK_CUR ) );
1014 }
1015
1016 /* libheif read() does not work like unix read().
1017 *
1018 * This method is cannot return EOF. Instead, the separate wait_for_file_size()
1019 * is called beforehand to make sure that there's enough data there.
1020 */
1021 static int
vips_foreign_load_heif_read(void * data,size_t size,void * userdata)1022 vips_foreign_load_heif_read( void *data, size_t size, void *userdata )
1023 {
1024 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata;
1025
1026 while( size > 0 ) {
1027 gint64 bytes_read;
1028
1029 bytes_read = vips_source_read( heif->source, data, size );
1030 if( bytes_read <= 0 )
1031 return( -1 );
1032
1033 size -= bytes_read;
1034 data += bytes_read;
1035 }
1036
1037 return( 0 );
1038 }
1039
1040 static int
vips_foreign_load_heif_seek(gint64 position,void * userdata)1041 vips_foreign_load_heif_seek( gint64 position, void *userdata )
1042 {
1043 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata;
1044
1045 /* Return 0 on success.
1046 */
1047 return( vips_source_seek( heif->source, position, SEEK_SET ) == -1 );
1048 }
1049
1050 /* libheif calls this to mean "I intend to read() to this position, please
1051 * check it is OK".
1052 */
1053 static enum heif_reader_grow_status
vips_foreign_load_heif_wait_for_file_size(gint64 target_size,void * userdata)1054 vips_foreign_load_heif_wait_for_file_size( gint64 target_size, void *userdata )
1055 {
1056 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata;
1057
1058 gint64 old_position;
1059 gint64 result;
1060 enum heif_reader_grow_status status;
1061
1062 /* We seek the VipsSource to the position and check for errors.
1063 */
1064 old_position = vips_source_seek( heif->source, 0L, SEEK_CUR );
1065 result = vips_source_seek( heif->source, target_size, SEEK_SET );
1066 vips_source_seek( heif->source, old_position, SEEK_SET );
1067
1068 if( result < 0 )
1069 /* Unable to seek to this point, so it's beyond EOF.
1070 */
1071 status = heif_reader_grow_status_size_beyond_eof;
1072 else
1073 /* Successfully read to the requested point, but the requested
1074 * point is not necessarily EOF.
1075 */
1076 status = heif_reader_grow_status_size_reached;
1077
1078 return( status );
1079 }
1080
1081 static void
vips_foreign_load_heif_init(VipsForeignLoadHeif * heif)1082 vips_foreign_load_heif_init( VipsForeignLoadHeif *heif )
1083 {
1084 heif->n = 1;
1085
1086 heif->reader = VIPS_ARRAY( NULL, 1, struct heif_reader );
1087
1088 /* The first version to support heif_reader.
1089 */
1090 heif->reader->reader_api_version = 1;
1091 heif->reader->get_position = vips_foreign_load_heif_get_position;
1092 heif->reader->read = vips_foreign_load_heif_read;
1093 heif->reader->seek = vips_foreign_load_heif_seek;
1094 heif->reader->wait_for_file_size =
1095 vips_foreign_load_heif_wait_for_file_size;
1096 }
1097
1098 typedef struct _VipsForeignLoadHeifFile {
1099 VipsForeignLoadHeif parent_object;
1100
1101 /* Filename for load.
1102 */
1103 char *filename;
1104
1105 } VipsForeignLoadHeifFile;
1106
1107 typedef VipsForeignLoadHeifClass VipsForeignLoadHeifFileClass;
1108
1109 G_DEFINE_TYPE( VipsForeignLoadHeifFile, vips_foreign_load_heif_file,
1110 vips_foreign_load_heif_get_type() );
1111
1112 static int
vips_foreign_load_heif_file_build(VipsObject * object)1113 vips_foreign_load_heif_file_build( VipsObject *object )
1114 {
1115 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object;
1116 VipsForeignLoadHeifFile *file = (VipsForeignLoadHeifFile *) object;
1117
1118 if( file->filename )
1119 if( !(heif->source =
1120 vips_source_new_from_file( file->filename )) )
1121 return( -1 );
1122
1123 if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_file_parent_class )->
1124 build( object ) )
1125 return( -1 );
1126
1127 return( 0 );
1128 }
1129
1130 static int
vips_foreign_load_heif_file_is_a(const char * filename)1131 vips_foreign_load_heif_file_is_a( const char *filename )
1132 {
1133 char buf[12];
1134
1135 if( vips__get_bytes( filename, (unsigned char *) buf, 12 ) != 12 )
1136 return( 0 );
1137
1138 return( vips_foreign_load_heif_is_a( buf, 12 ) );
1139 }
1140
1141 static void
vips_foreign_load_heif_file_class_init(VipsForeignLoadHeifFileClass * class)1142 vips_foreign_load_heif_file_class_init( VipsForeignLoadHeifFileClass *class )
1143 {
1144 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
1145 VipsObjectClass *object_class = (VipsObjectClass *) class;
1146 VipsForeignClass *foreign_class = (VipsForeignClass *) class;
1147 VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
1148
1149 gobject_class->set_property = vips_object_set_property;
1150 gobject_class->get_property = vips_object_get_property;
1151
1152 object_class->nickname = "heifload";
1153 object_class->build = vips_foreign_load_heif_file_build;
1154
1155 foreign_class->suffs = vips__heif_suffs;
1156
1157 load_class->is_a = vips_foreign_load_heif_file_is_a;
1158
1159 VIPS_ARG_STRING( class, "filename", 1,
1160 _( "Filename" ),
1161 _( "Filename to load from" ),
1162 VIPS_ARGUMENT_REQUIRED_INPUT,
1163 G_STRUCT_OFFSET( VipsForeignLoadHeifFile, filename ),
1164 NULL );
1165
1166 }
1167
1168 static void
vips_foreign_load_heif_file_init(VipsForeignLoadHeifFile * file)1169 vips_foreign_load_heif_file_init( VipsForeignLoadHeifFile *file )
1170 {
1171 }
1172
1173 typedef struct _VipsForeignLoadHeifBuffer {
1174 VipsForeignLoadHeif parent_object;
1175
1176 /* Load from a buffer.
1177 */
1178 VipsArea *buf;
1179
1180 } VipsForeignLoadHeifBuffer;
1181
1182 typedef VipsForeignLoadHeifClass VipsForeignLoadHeifBufferClass;
1183
1184 G_DEFINE_TYPE( VipsForeignLoadHeifBuffer, vips_foreign_load_heif_buffer,
1185 vips_foreign_load_heif_get_type() );
1186
1187 static int
vips_foreign_load_heif_buffer_build(VipsObject * object)1188 vips_foreign_load_heif_buffer_build( VipsObject *object )
1189 {
1190 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object;
1191 VipsForeignLoadHeifBuffer *buffer =
1192 (VipsForeignLoadHeifBuffer *) object;
1193
1194 if( buffer->buf )
1195 if( !(heif->source = vips_source_new_from_memory(
1196 VIPS_AREA( buffer->buf )->data,
1197 VIPS_AREA( buffer->buf )->length )) )
1198 return( -1 );
1199
1200 if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_file_parent_class )->
1201 build( object ) )
1202 return( -1 );
1203
1204 return( 0 );
1205 }
1206
1207 static gboolean
vips_foreign_load_heif_buffer_is_a(const void * buf,size_t len)1208 vips_foreign_load_heif_buffer_is_a( const void *buf, size_t len )
1209 {
1210 return( vips_foreign_load_heif_is_a( buf, len ) );
1211 }
1212
1213 static void
vips_foreign_load_heif_buffer_class_init(VipsForeignLoadHeifBufferClass * class)1214 vips_foreign_load_heif_buffer_class_init(
1215 VipsForeignLoadHeifBufferClass *class )
1216 {
1217 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
1218 VipsObjectClass *object_class = (VipsObjectClass *) class;
1219 VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
1220
1221 gobject_class->set_property = vips_object_set_property;
1222 gobject_class->get_property = vips_object_get_property;
1223
1224 object_class->nickname = "heifload_buffer";
1225 object_class->build = vips_foreign_load_heif_buffer_build;
1226
1227 load_class->is_a_buffer = vips_foreign_load_heif_buffer_is_a;
1228
1229 VIPS_ARG_BOXED( class, "buffer", 1,
1230 _( "Buffer" ),
1231 _( "Buffer to load from" ),
1232 VIPS_ARGUMENT_REQUIRED_INPUT,
1233 G_STRUCT_OFFSET( VipsForeignLoadHeifBuffer, buf ),
1234 VIPS_TYPE_BLOB );
1235
1236 }
1237
1238 static void
vips_foreign_load_heif_buffer_init(VipsForeignLoadHeifBuffer * buffer)1239 vips_foreign_load_heif_buffer_init( VipsForeignLoadHeifBuffer *buffer )
1240 {
1241 }
1242
1243 typedef struct _VipsForeignLoadHeifSource {
1244 VipsForeignLoadHeif parent_object;
1245
1246 /* Load from a source.
1247 */
1248 VipsSource *source;
1249
1250 } VipsForeignLoadHeifSource;
1251
1252 typedef VipsForeignLoadHeifClass VipsForeignLoadHeifSourceClass;
1253
1254 G_DEFINE_TYPE( VipsForeignLoadHeifSource, vips_foreign_load_heif_source,
1255 vips_foreign_load_heif_get_type() );
1256
1257 static int
vips_foreign_load_heif_source_build(VipsObject * object)1258 vips_foreign_load_heif_source_build( VipsObject *object )
1259 {
1260 VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object;
1261 VipsForeignLoadHeifSource *source =
1262 (VipsForeignLoadHeifSource *) object;
1263
1264 if( source->source ) {
1265 heif->source = source->source;
1266 g_object_ref( heif->source );
1267 }
1268
1269 if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_source_parent_class )->
1270 build( object ) )
1271 return( -1 );
1272
1273 return( 0 );
1274 }
1275
1276 static gboolean
vips_foreign_load_heif_source_is_a_source(VipsSource * source)1277 vips_foreign_load_heif_source_is_a_source( VipsSource *source )
1278 {
1279 const char *p;
1280
1281 return( (p = (const char *) vips_source_sniff( source, 12 )) &&
1282 vips_foreign_load_heif_is_a( p, 12 ) );
1283 }
1284
1285 static void
vips_foreign_load_heif_source_class_init(VipsForeignLoadHeifSourceClass * class)1286 vips_foreign_load_heif_source_class_init(
1287 VipsForeignLoadHeifSourceClass *class )
1288 {
1289 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
1290 VipsObjectClass *object_class = (VipsObjectClass *) class;
1291 VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
1292 VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
1293
1294 gobject_class->set_property = vips_object_set_property;
1295 gobject_class->get_property = vips_object_get_property;
1296
1297 object_class->nickname = "heifload_source";
1298 object_class->build = vips_foreign_load_heif_source_build;
1299
1300 operation_class->flags = VIPS_OPERATION_NOCACHE;
1301
1302 load_class->is_a_source = vips_foreign_load_heif_source_is_a_source;
1303
1304 VIPS_ARG_OBJECT( class, "source", 1,
1305 _( "Source" ),
1306 _( "Source to load from" ),
1307 VIPS_ARGUMENT_REQUIRED_INPUT,
1308 G_STRUCT_OFFSET( VipsForeignLoadHeifSource, source ),
1309 VIPS_TYPE_SOURCE );
1310
1311 }
1312
1313 static void
vips_foreign_load_heif_source_init(VipsForeignLoadHeifSource * source)1314 vips_foreign_load_heif_source_init( VipsForeignLoadHeifSource *source )
1315 {
1316 }
1317
1318 #endif /*HAVE_HEIF_DECODER*/
1319
1320 /* The C API wrappers are defined in foreign.c.
1321 */
1322