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