/* load heif images with libheif * * 19/1/19 * - from niftiload.c * 24/7/19 [zhoux2016] * - always fetch metadata from the main image (thumbs don't have it) * 24/7/19 * - close early on minimise * - close early on error * 1/9/19 [meyermarcel] * - handle alpha * 30/9/19 * - much faster handling of thumbnail=TRUE and missing thumbnail ... we * were reselecting the image for each scanline * 3/10/19 * - restart after minimise * 15/3/20 * - revise for new VipsSource API * 10/5/20 * - deprecate autorotate -- it's too difficult to support properly * 31/7/20 * - block broken thumbnails, if we can * 14/2/21 kleisauke * - move GObject part to heif2vips.c * 22/12/21 * - add >8 bit support * 23/2/22 lovell * - add @unlimited */ /* This file is part of VIPS. VIPS is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk */ /* #define DEBUG_VERBOSE #define VIPS_DEBUG #define DEBUG */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include #include /* These are shared with the encoder. */ #if defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER) #include "pforeign.h" const char *vips__heic_suffs[] = { ".heic", ".heif", NULL }; const char *vips__avif_suffs[] = { ".avif", NULL }; const char *vips__heif_suffs[] = { ".heic", ".heif", ".avif", NULL }; #endif /*defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER)*/ #ifdef HAVE_HEIF_DECODER #include #define VIPS_TYPE_FOREIGN_LOAD_HEIF (vips_foreign_load_heif_get_type()) #define VIPS_FOREIGN_LOAD_HEIF( obj ) \ (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeif )) #define VIPS_FOREIGN_LOAD_HEIF_CLASS( klass ) \ (G_TYPE_CHECK_CLASS_CAST( (klass), \ VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeifClass)) #define VIPS_IS_FOREIGN_LOAD_HEIF( obj ) \ (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_HEIF )) #define VIPS_IS_FOREIGN_LOAD_HEIF_CLASS( klass ) \ (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_HEIF )) #define VIPS_FOREIGN_LOAD_HEIF_GET_CLASS( obj ) \ (G_TYPE_INSTANCE_GET_CLASS( (obj), \ VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeifClass )) typedef struct _VipsForeignLoadHeif { VipsForeignLoad parent_object; /* Pages to load. */ int page; int n; /* Fetch the thumbnail instead of the image. If there is no thumbnail, * just fetch the image. */ gboolean thumbnail; /* Apply any orientation tags in the header. * * This is deprecated and does nothing. Non-autorotated reads from * libheif are surprisingly hard to support well, since orientation can * be represented in several different ways in HEIC files and devices * vary in how they do this. */ gboolean autorotate; /* remove all denial of service limits. */ gboolean unlimited; /* Context for this image. */ struct heif_context *ctx; /* Number of top-level images in this file. */ int n_top; /* TRUE for RGBA ... otherwise, RGB. */ gboolean has_alpha; /* Size of final output image. */ int width; int height; /* Size of each page. */ int page_width; int page_height; /* Eg. 8 or 12, typically. */ int bits_per_pixel; /* The page number currently in @handle. */ int page_no; /* TRUE if @handle has selected the thumbnail rather than the main * image. */ gboolean thumbnail_set; /* The page number of the primary image. */ int primary_page; /* Array of top-level image IDs. */ heif_item_id *id; /* Handle for the currently selected image. */ struct heif_image_handle *handle; /* Decoded pixel data for the current image. */ struct heif_image *img; /* Valid until img is released. */ int stride; const uint8_t *data; /* Set from subclasses. */ VipsSource *source; /* The reader struct. We use this to attach to our VipsSource. This * has to be alloced rather than in our struct, since it may change * size in libheif API versions. */ struct heif_reader *reader; } VipsForeignLoadHeif; void vips__heif_error( struct heif_error *error ) { if( error->code ) vips_error( "heif", "%s (%d.%d)", error->message, error->code, error->subcode ); } typedef struct _VipsForeignLoadHeifClass { VipsForeignLoadClass parent_class; } VipsForeignLoadHeifClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadHeif, vips_foreign_load_heif, VIPS_TYPE_FOREIGN_LOAD ); static void vips_foreign_load_heif_dispose( GObject *gobject ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) gobject; heif->data = NULL; VIPS_FREEF( heif_image_release, heif->img ); VIPS_FREEF( heif_image_handle_release, heif->handle ); VIPS_FREEF( heif_context_free, heif->ctx ); VIPS_FREE( heif->id ); VIPS_FREE( heif->reader ); VIPS_UNREF( heif->source ); G_OBJECT_CLASS( vips_foreign_load_heif_parent_class )-> dispose( gobject ); } static int vips_foreign_load_heif_build( VipsObject *object ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; #ifdef DEBUG printf( "vips_foreign_load_heif_build:\n" ); #endif /*DEBUG*/ if( heif->source && vips_source_rewind( heif->source ) ) return( -1 ); if( !heif->ctx ) { struct heif_error error; heif->ctx = heif_context_alloc(); #ifdef HAVE_HEIF_SET_MAX_IMAGE_SIZE_LIMIT heif_context_set_maximum_image_size_limit( heif->ctx, heif->unlimited ? USHRT_MAX : 0x4000 ); #endif /* HAVE_HEIF_SET_MAX_IMAGE_SIZE_LIMIT */ error = heif_context_read_from_reader( heif->ctx, heif->reader, heif, NULL ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } } if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_parent_class )-> build( object ) ) return( -1 ); return( 0 ); } static const char *heif_magic[] = { "ftypheic", /* A regular heif image */ "ftypheix", /* Extended range (>8 bit) image */ "ftyphevc", /* Image sequence */ "ftypheim", /* Image sequence */ "ftypheis", /* Scaleable image */ "ftyphevm", /* Multiview sequence */ "ftyphevs", /* Scaleable sequence */ "ftypmif1", /* Nokia alpha_ image */ "ftypmsf1", /* Nokia animation image */ "ftypavif" /* AV1 image format */ }; /* The API has: * * enum heif_filetype_result result = heif_check_filetype( buf, 12 ); * * but it's very conservative and seems to be missing some of the Nokia heif * types. */ static int vips_foreign_load_heif_is_a( const char *buf, int len ) { if( len >= 12 ) { unsigned char *p = (unsigned char *) buf; guint32 chunk_len = VIPS_LSHIFT_INT( p[0], 24 ) | VIPS_LSHIFT_INT( p[1], 16 ) | VIPS_LSHIFT_INT( p[2], 8 ) | VIPS_LSHIFT_INT( p[3], 0 ); int i; /* chunk_len can be pretty big for eg. animated AVIF. */ if( chunk_len > 2048 || chunk_len % 4 != 0 ) return( 0 ); for( i = 0; i < VIPS_NUMBER( heif_magic ); i++ ) if( strncmp( buf + 4, heif_magic[i], 8 ) == 0 ) return( 1 ); } return( 0 ); } static VipsForeignFlags vips_foreign_load_heif_get_flags( VipsForeignLoad *load ) { /* FIXME .. could support random access for grid images. */ return( VIPS_FOREIGN_SEQUENTIAL ); } /* We've selected the page. Try to select the associated thumbnail instead, * if we can. */ static int vips_foreign_load_heif_set_thumbnail( VipsForeignLoadHeif *heif ) { heif_item_id thumb_ids[1]; int n_thumbs; struct heif_image_handle *thumb_handle; struct heif_image *thumb_img; struct heif_error error; double main_aspect; double thumb_aspect; #ifdef DEBUG printf( "vips_foreign_load_heif_set_thumbnail:\n" ); #endif /*DEBUG*/ n_thumbs = heif_image_handle_get_list_of_thumbnail_IDs( heif->handle, thumb_ids, 1 ); if( n_thumbs == 0 ) return( 0 ); error = heif_image_handle_get_thumbnail( heif->handle, thumb_ids[0], &thumb_handle ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } /* Just checking the width and height of the handle isn't * enough -- we have to experimentally decode it and test the * decoded dimensions. */ error = heif_decode_image( thumb_handle, &thumb_img, heif_colorspace_RGB, heif_chroma_interleaved_RGB, NULL ); if( error.code ) { VIPS_FREEF( heif_image_handle_release, thumb_handle ); vips__heif_error( &error ); return( -1 ); } thumb_aspect = (double) heif_image_get_width( thumb_img, heif_channel_interleaved ) / heif_image_get_height( thumb_img, heif_channel_interleaved ); VIPS_FREEF( heif_image_release, thumb_img ); main_aspect = (double) heif_image_handle_get_width( heif->handle ) / heif_image_handle_get_height( heif->handle ); /* The bug we are working around has decoded thumbs as 512x512 * with the main image as 6kx4k, so a 0.1 threshold is more * than tight enough to spot the error. */ if( fabs( main_aspect - thumb_aspect ) > 0.1 ) { VIPS_FREEF( heif_image_handle_release, thumb_handle ); return( 0 ); } VIPS_FREEF( heif_image_handle_release, heif->handle ); heif->handle = thumb_handle; return( 0 ); } /* Select a page. If thumbnail is set, select the thumbnail for that page, if * there is one. */ static int vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif, int page_no, gboolean thumbnail ) { if( !heif->handle || page_no != heif->page_no || thumbnail != heif->thumbnail_set ) { struct heif_error error; #ifdef DEBUG printf( "vips_foreign_load_heif_set_page: %d, thumbnail = %d\n", page_no, thumbnail ); #endif /*DEBUG*/ VIPS_FREEF( heif_image_handle_release, heif->handle ); VIPS_FREEF( heif_image_release, heif->img ); heif->data = NULL; heif->thumbnail_set = FALSE; error = heif_context_get_image_handle( heif->ctx, heif->id[page_no], &heif->handle ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } if( thumbnail ) { if( vips_foreign_load_heif_set_thumbnail( heif ) ) return( -1 ); /* If we were asked to select the thumbnail, say we * did, even if there are no thumbnails and we just * selected the main image. * * If we don't do this, next time around in _generate * we'll try to select the thumbnail again, which will * be horribly slow. */ heif->thumbnail_set = TRUE; } heif->page_no = page_no; } return( 0 ); } static int vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) { VipsForeignLoad *load = (VipsForeignLoad *) heif; int bands; int i; /* Surely, 16 metadata items will be enough for anyone. */ heif_item_id id[16]; int n_metadata; struct heif_error error; VipsForeignHeifCompression compression; VipsInterpretation interpretation; VipsBandFormat format; /* We take the metadata from the non-thumbnail first page. HEIC * thumbnails don't have metadata. */ if( vips_foreign_load_heif_set_page( heif, heif->page, FALSE ) ) return( -1 ); /* Verify dimensions */ if( heif->page_width < 1 || heif->page_height < 1 ) { vips_error( "heifload", "%s", _( "bad dimensions" ) ); return( -1 ); } heif->has_alpha = heif_image_handle_has_alpha_channel( heif->handle ); #ifdef DEBUG printf( "heif_image_handle_has_alpha_channel() = %d\n", heif->has_alpha ); #endif /*DEBUG*/ bands = heif->has_alpha ? 4 : 3; #ifdef DEBUG printf( "heif_image_handle_get_luma_bits_per_pixel() = %d\n", heif_image_handle_get_luma_bits_per_pixel( heif->handle ) ); #endif /*DEBUG*/ /* FIXME .. IPTC as well? */ n_metadata = heif_image_handle_get_list_of_metadata_block_IDs( heif->handle, NULL, id, VIPS_NUMBER( id ) ); for( i = 0; i < n_metadata; i++ ) { size_t length = heif_image_handle_get_metadata_size( heif->handle, id[i] ); const char *type = heif_image_handle_get_metadata_type( heif->handle, id[i] ); unsigned char *data; char name[256]; #ifdef DEBUG printf( "metadata type = %s, length = %zu\n", type, length ); #endif /*DEBUG*/ if( !length ) continue; if( !(data = VIPS_ARRAY( out, length, unsigned char )) ) return( -1 ); error = heif_image_handle_get_metadata( heif->handle, id[i], data ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } /* We need to skip the first four bytes of EXIF, they just * contain the offset. */ if( length > 4 && g_ascii_strcasecmp( type, "exif" ) == 0 ) { data += 4; length -= 4; } /* exif has a special name. * * XMP metadata is just attached with the "mime" type, and * usually start with " 10 && vips_isprefix( "handle ); #ifdef DEBUG { printf( "profile type = " ); switch( profile_type ) { case heif_color_profile_type_not_present: printf( "none" ); break; case heif_color_profile_type_nclx: printf( "nclx" ); break; case heif_color_profile_type_rICC: printf( "rICC" ); break; case heif_color_profile_type_prof: printf( "prof" ); break; default: printf( "unknown" ); break; } printf( "\n" ); } #endif /*DEBUG*/ /* lcms can load standard (prof) and reduced (rICC) profiles */ if( profile_type == heif_color_profile_type_prof || profile_type == heif_color_profile_type_rICC ) { size_t length = heif_image_handle_get_raw_color_profile_size( heif->handle ); unsigned char *data; if( !(data = VIPS_ARRAY( out, length, unsigned char )) ) return( -1 ); error = heif_image_handle_get_raw_color_profile( heif->handle, data ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } #ifdef DEBUG printf( "profile data, length = %zd\n", length ); #endif /*DEBUG*/ vips_image_set_blob( out, VIPS_META_ICC_NAME, (VipsCallbackFn) NULL, data, length ); } else if( profile_type == heif_color_profile_type_nclx ) { g_warning( "heifload: ignoring nclx profile" ); } #endif /*HAVE_HEIF_COLOR_PROFILE*/ vips_image_set_int( out, "heif-primary", heif->primary_page ); vips_image_set_int( out, VIPS_META_N_PAGES, heif->n_top ); /* Only set page-height if we have more than one page, or this could * accidentally turn into an animated image later. */ if( heif->n > 1 ) vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, heif->page_height ); /* Determine compression from HEIF "brand". heif_avif and heif_avis * were added in v1.7. */ compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC; #ifdef HAVE_HEIF_AVIF { const unsigned char *brand_data; if( (brand_data = vips_source_sniff( heif->source, 12 )) ) { enum heif_brand brand; brand = heif_main_brand( brand_data, 12 ); if( brand == heif_avif || brand == heif_avis ) compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1; } } #endif /*HAVE_HEIF_AVIF*/ vips_image_set_string( out, "heif-compression", vips_enum_nick( VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, compression ) ); vips_image_set_int( out, "heif-bitdepth", heif->bits_per_pixel ); if( heif->bits_per_pixel > 8 ) { interpretation = VIPS_INTERPRETATION_RGB16; format = VIPS_FORMAT_USHORT; } else { interpretation = VIPS_INTERPRETATION_sRGB; format = VIPS_FORMAT_UCHAR; } /* FIXME .. we always decode to RGB in generate. We should check for * all grey images, perhaps. */ if( vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ) ) return( -1 ); vips_image_init_fields( out, heif->page_width, heif->page_height * heif->n, bands, format, VIPS_CODING_NONE, interpretation, 1.0, 1.0 ); VIPS_SETSTR( load->out->filename, vips_connection_filename( VIPS_CONNECTION( heif->source ) ) ); return( 0 ); } static int vips_foreign_load_heif_header( VipsForeignLoad *load ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) load; struct heif_error error; heif_item_id primary_id; int i; #ifdef DEBUG printf( "vips_foreign_load_heif_header:\n" ); #endif /*DEBUG*/ heif->n_top = heif_context_get_number_of_top_level_images( heif->ctx ); heif->id = VIPS_ARRAY( NULL, heif->n_top, heif_item_id ); heif_context_get_list_of_top_level_image_IDs( heif->ctx, heif->id, heif->n_top ); /* Note page number of primary image. */ error = heif_context_get_primary_image_ID( heif->ctx, &primary_id ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } for( i = 0; i < heif->n_top; i++ ) if( heif->id[i] == primary_id ) heif->primary_page = i; /* If @n and @page have not been set, @page defaults to the primary * page. */ if( !vips_object_argument_isset( VIPS_OBJECT( load ), "page" ) && !vips_object_argument_isset( VIPS_OBJECT( load ), "n" ) ) heif->page = heif->primary_page; if( heif->n == -1 ) heif->n = heif->n_top - heif->page; if( heif->page < 0 || heif->n <= 0 || heif->page + heif->n > heif->n_top ) { vips_error( class->nickname, "%s", _( "bad page number" ) ); return( -1 ); } #ifdef DEBUG for( i = heif->page; i < heif->page + heif->n; i++ ) { heif_item_id thumb_ids[1]; int n_items; int n_thumbs; int j; if( vips_foreign_load_heif_set_page( heif, i, FALSE ) ) return( -1 ); n_thumbs = heif_image_handle_get_number_of_thumbnails( heif->handle ); n_items = heif_image_handle_get_list_of_thumbnail_IDs( heif->handle, thumb_ids, 1 ); printf( "page = %d\n", i ); printf( "n_thumbs = %d\n", n_thumbs ); printf( "n_items = %d\n", n_items ); for( j = 0; j < n_items; j++ ) { struct heif_image_handle *thumb_handle; error = heif_image_handle_get_thumbnail( heif->handle, thumb_ids[j], &thumb_handle ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } printf( " thumb %d\n", j ); printf( " width = %d\n", heif_image_handle_get_width( thumb_handle ) ); printf( " height = %d\n", heif_image_handle_get_height( thumb_handle ) ); printf( " bits_per_pixel = %d\n", heif_image_handle_get_luma_bits_per_pixel( thumb_handle ) ); } } #endif /*DEBUG*/ /* All pages must be the same size for libvips toilet roll images. */ if( vips_foreign_load_heif_set_page( heif, heif->page, heif->thumbnail ) ) return( -1 ); heif->page_width = heif_image_handle_get_width( heif->handle ); heif->page_height = heif_image_handle_get_height( heif->handle ); heif->bits_per_pixel = heif_image_handle_get_luma_bits_per_pixel( heif->handle ); if( heif->bits_per_pixel < 0 ) { vips_error( class->nickname, "%s", _( "undefined bits per pixel" ) ); return( -1 ); } for( i = heif->page + 1; i < heif->page + heif->n; i++ ) { if( vips_foreign_load_heif_set_page( heif, i, heif->thumbnail ) ) return( -1 ); if( heif_image_handle_get_width( heif->handle ) != heif->page_width || heif_image_handle_get_height( heif->handle ) != heif->page_height || heif_image_handle_get_luma_bits_per_pixel( heif->handle ) != heif->bits_per_pixel ) { vips_error( class->nickname, "%s", _( "not all pages are the same size" ) ); return( -1 ); } } #ifdef DEBUG printf( "page_width = %d\n", heif->page_width ); printf( "page_height = %d\n", heif->page_height ); printf( "bits_per_pixel = %d\n", heif->bits_per_pixel ); printf( "n_top = %d\n", heif->n_top ); for( i = 0; i < heif->n_top; i++ ) { printf( " id[%d] = %d\n", i, heif->id[i] ); if( vips_foreign_load_heif_set_page( heif, i, FALSE ) ) return( -1 ); printf( " width = %d\n", heif_image_handle_get_width( heif->handle ) ); printf( " height = %d\n", heif_image_handle_get_height( heif->handle ) ); printf( " bits_per_pixel = %d\n", heif_image_handle_get_luma_bits_per_pixel( heif->handle ) ); printf( " has_depth = %d\n", heif_image_handle_has_depth_image( heif->handle ) ); printf( " has_alpha = %d\n", heif_image_handle_has_alpha_channel( heif->handle ) ); printf( " n_metadata = %d\n", heif_image_handle_get_number_of_metadata_blocks( heif->handle, NULL ) ); #ifdef HAVE_HEIF_COLOR_PROFILE printf( " colour profile type = 0x%xd\n", heif_image_handle_get_color_profile_type( heif->handle ) ); #endif /*HAVE_HEIF_COLOR_PROFILE*/ } #endif /*DEBUG*/ if( vips_foreign_load_heif_set_header( heif, load->out ) ) return( -1 ); vips_source_minimise( heif->source ); return( 0 ); } #ifdef DEBUG void vips__heif_image_print( struct heif_image *img ) { const static enum heif_channel channel[] = { heif_channel_Y, heif_channel_Cb, heif_channel_Cr, heif_channel_R, heif_channel_G, heif_channel_B, heif_channel_Alpha, heif_channel_interleaved }; const static char *channel_name[] = { "heif_channel_Y", "heif_channel_Cb", "heif_channel_Cr", "heif_channel_R", "heif_channel_G", "heif_channel_B", "heif_channel_Alpha", "heif_channel_interleaved" }; int i; printf( "vips__heif_image_print:\n" ); for( i = 0; i < VIPS_NUMBER( channel ); i++ ) { if( !heif_image_has_channel( img, channel[i] ) ) continue; printf( "\t%s:\n", channel_name[i] ); printf( "\t\twidth = %d\n", heif_image_get_width( img, channel[i] ) ); printf( "\t\theight = %d\n", heif_image_get_height( img, channel[i] ) ); printf( "\t\tbits = %d\n", heif_image_get_bits_per_pixel( img, channel[i] ) ); } } #endif /*DEBUG*/ /* Pick a chroma format. Shared with heifsave. */ int vips__heif_chroma( int bits_per_pixel, gboolean has_alpha ) { if( bits_per_pixel == 8 ) { if( has_alpha ) return( heif_chroma_interleaved_RGBA ); else return( heif_chroma_interleaved_RGB ); } else { if( has_alpha ) return( heif_chroma_interleaved_RRGGBBAA_BE ); else return( heif_chroma_interleaved_RRGGBB_BE ); } } static int vips_foreign_load_heif_generate( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) a; VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( heif ); VipsRect *r = &or->valid; int page = r->top / heif->page_height + heif->page; int line = r->top % heif->page_height; #ifdef DEBUG_VERBOSE printf( "vips_foreign_load_heif_generate: line %d\n", r->top ); #endif /*DEBUG_VERBOSE*/ g_assert( r->height == 1 ); if( vips_foreign_load_heif_set_page( heif, page, heif->thumbnail ) ) return( -1 ); if( !heif->img ) { struct heif_error error; struct heif_decoding_options *options; enum heif_chroma chroma = vips__heif_chroma( heif->bits_per_pixel, heif->has_alpha ); options = heif_decoding_options_alloc(); error = heif_decode_image( heif->handle, &heif->img, heif_colorspace_RGB, chroma, options ); heif_decoding_options_free( options ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } #ifdef DEBUG vips__heif_image_print( heif->img ); #endif /*DEBUG*/ } if( !heif->data ) { int image_width = heif_image_get_width( heif->img, heif_channel_interleaved ); int image_height = heif_image_get_height( heif->img, heif_channel_interleaved ); /* We can sometimes get inconsistency between the dimensions * reported on the handle, and the final image we fetch. Error * out to prevent a segv. */ if( image_width != heif->page_width || image_height != heif->page_height ) { vips_error( class->nickname, "%s", _( "bad image dimensions on decode" ) ); return( -1 ); } if( !(heif->data = heif_image_get_plane_readonly( heif->img, heif_channel_interleaved, &heif->stride )) ) { vips_error( class->nickname, "%s", _( "unable to get image data" ) ); return( -1 ); } } memcpy( VIPS_REGION_ADDR( or, 0, r->top ), heif->data + heif->stride * line, VIPS_IMAGE_SIZEOF_LINE( or->im ) ); /* We may need to swap bytes and shift to fill 16 bits. */ if( heif->bits_per_pixel > 8 ) { int shift = 16 - heif->bits_per_pixel; int ne = VIPS_REGION_N_ELEMENTS( or ); int i; VipsPel *p; p = VIPS_REGION_ADDR( or, 0, r->top ); for( i = 0; i < ne; i++ ) { /* We've asked for big endian, we must write native. */ guint16 v = ((p[0] << 8) | p[1]) << shift; *((guint16 *) p) = v; p += 2; } } return( 0 ); } static void vips_foreign_load_heif_minimise( VipsObject *object, VipsForeignLoadHeif *heif ) { vips_source_minimise( heif->source ); } static int vips_foreign_load_heif_load( VipsForeignLoad *load ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) load; VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 3 ); #ifdef DEBUG printf( "vips_foreign_load_heif_load: loading image\n" ); #endif /*DEBUG*/ t[0] = vips_image_new(); if( vips_foreign_load_heif_set_header( heif, t[0] ) ) return( -1 ); /* Close input immediately at end of read. */ g_signal_connect( t[0], "minimise", G_CALLBACK( vips_foreign_load_heif_minimise ), heif ); if( vips_image_generate( t[0], NULL, vips_foreign_load_heif_generate, NULL, heif, NULL ) || vips_sequential( t[0], &t[1], NULL ) || vips_image_write( t[1], load->real ) ) return( -1 ); if( vips_source_decode( heif->source ) ) return( -1 ); return( 0 ); } static void vips_foreign_load_heif_class_init( VipsForeignLoadHeifClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; gobject_class->dispose = vips_foreign_load_heif_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "heifload_base"; object_class->description = _( "load a HEIF image" ); object_class->build = vips_foreign_load_heif_build; load_class->get_flags = vips_foreign_load_heif_get_flags; load_class->header = vips_foreign_load_heif_header; load_class->load = vips_foreign_load_heif_load; VIPS_ARG_INT( class, "page", 2, _( "Page" ), _( "First page to load" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadHeif, page ), 0, 100000, 0 ); VIPS_ARG_INT( class, "n", 3, _( "n" ), _( "Number of pages to load, -1 for all" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadHeif, n ), -1, 100000, 1 ); VIPS_ARG_BOOL( class, "thumbnail", 4, _( "Thumbnail" ), _( "Fetch thumbnail image" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadHeif, thumbnail ), FALSE ); VIPS_ARG_BOOL( class, "autorotate", 21, _( "Autorotate" ), _( "Rotate image using exif orientation" ), VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignLoadHeif, autorotate ), FALSE ); VIPS_ARG_BOOL( class, "unlimited", 22, _( "Unlimited" ), _( "Remove all denial of service limits" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadHeif, unlimited ), FALSE ); } static gint64 vips_foreign_load_heif_get_position( void *userdata ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; return( vips_source_seek( heif->source, 0L, SEEK_CUR ) ); } /* libheif read() does not work like unix read(). * * This method is cannot return EOF. Instead, the separate wait_for_file_size() * is called beforehand to make sure that there's enough data there. */ static int vips_foreign_load_heif_read( void *data, size_t size, void *userdata ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; while( size > 0 ) { gint64 bytes_read; bytes_read = vips_source_read( heif->source, data, size ); if( bytes_read <= 0 ) return( -1 ); size -= bytes_read; data += bytes_read; } return( 0 ); } static int vips_foreign_load_heif_seek( gint64 position, void *userdata ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; /* Return 0 on success. */ return( vips_source_seek( heif->source, position, SEEK_SET ) == -1 ); } /* libheif calls this to mean "I intend to read() to this position, please * check it is OK". */ static enum heif_reader_grow_status vips_foreign_load_heif_wait_for_file_size( gint64 target_size, void *userdata ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; gint64 old_position; gint64 result; enum heif_reader_grow_status status; /* We seek the VipsSource to the position and check for errors. */ old_position = vips_source_seek( heif->source, 0L, SEEK_CUR ); result = vips_source_seek( heif->source, target_size, SEEK_SET ); vips_source_seek( heif->source, old_position, SEEK_SET ); if( result < 0 ) /* Unable to seek to this point, so it's beyond EOF. */ status = heif_reader_grow_status_size_beyond_eof; else /* Successfully read to the requested point, but the requested * point is not necessarily EOF. */ status = heif_reader_grow_status_size_reached; return( status ); } static void vips_foreign_load_heif_init( VipsForeignLoadHeif *heif ) { heif->n = 1; heif->reader = VIPS_ARRAY( NULL, 1, struct heif_reader ); /* The first version to support heif_reader. */ heif->reader->reader_api_version = 1; heif->reader->get_position = vips_foreign_load_heif_get_position; heif->reader->read = vips_foreign_load_heif_read; heif->reader->seek = vips_foreign_load_heif_seek; heif->reader->wait_for_file_size = vips_foreign_load_heif_wait_for_file_size; } typedef struct _VipsForeignLoadHeifFile { VipsForeignLoadHeif parent_object; /* Filename for load. */ char *filename; } VipsForeignLoadHeifFile; typedef VipsForeignLoadHeifClass VipsForeignLoadHeifFileClass; G_DEFINE_TYPE( VipsForeignLoadHeifFile, vips_foreign_load_heif_file, vips_foreign_load_heif_get_type() ); static int vips_foreign_load_heif_file_build( VipsObject *object ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; VipsForeignLoadHeifFile *file = (VipsForeignLoadHeifFile *) object; if( file->filename ) if( !(heif->source = vips_source_new_from_file( file->filename )) ) return( -1 ); if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_file_parent_class )-> build( object ) ) return( -1 ); return( 0 ); } static int vips_foreign_load_heif_file_is_a( const char *filename ) { char buf[12]; if( vips__get_bytes( filename, (unsigned char *) buf, 12 ) != 12 ) return( 0 ); return( vips_foreign_load_heif_is_a( buf, 12 ) ); } static void vips_foreign_load_heif_file_class_init( VipsForeignLoadHeifFileClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "heifload"; object_class->build = vips_foreign_load_heif_file_build; foreign_class->suffs = vips__heif_suffs; load_class->is_a = vips_foreign_load_heif_file_is_a; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsForeignLoadHeifFile, filename ), NULL ); } static void vips_foreign_load_heif_file_init( VipsForeignLoadHeifFile *file ) { } typedef struct _VipsForeignLoadHeifBuffer { VipsForeignLoadHeif parent_object; /* Load from a buffer. */ VipsArea *buf; } VipsForeignLoadHeifBuffer; typedef VipsForeignLoadHeifClass VipsForeignLoadHeifBufferClass; G_DEFINE_TYPE( VipsForeignLoadHeifBuffer, vips_foreign_load_heif_buffer, vips_foreign_load_heif_get_type() ); static int vips_foreign_load_heif_buffer_build( VipsObject *object ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; VipsForeignLoadHeifBuffer *buffer = (VipsForeignLoadHeifBuffer *) object; if( buffer->buf ) if( !(heif->source = vips_source_new_from_memory( VIPS_AREA( buffer->buf )->data, VIPS_AREA( buffer->buf )->length )) ) return( -1 ); if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_file_parent_class )-> build( object ) ) return( -1 ); return( 0 ); } static gboolean vips_foreign_load_heif_buffer_is_a( const void *buf, size_t len ) { return( vips_foreign_load_heif_is_a( buf, len ) ); } static void vips_foreign_load_heif_buffer_class_init( VipsForeignLoadHeifBufferClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "heifload_buffer"; object_class->build = vips_foreign_load_heif_buffer_build; load_class->is_a_buffer = vips_foreign_load_heif_buffer_is_a; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsForeignLoadHeifBuffer, buf ), VIPS_TYPE_BLOB ); } static void vips_foreign_load_heif_buffer_init( VipsForeignLoadHeifBuffer *buffer ) { } typedef struct _VipsForeignLoadHeifSource { VipsForeignLoadHeif parent_object; /* Load from a source. */ VipsSource *source; } VipsForeignLoadHeifSource; typedef VipsForeignLoadHeifClass VipsForeignLoadHeifSourceClass; G_DEFINE_TYPE( VipsForeignLoadHeifSource, vips_foreign_load_heif_source, vips_foreign_load_heif_get_type() ); static int vips_foreign_load_heif_source_build( VipsObject *object ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; VipsForeignLoadHeifSource *source = (VipsForeignLoadHeifSource *) object; if( source->source ) { heif->source = source->source; g_object_ref( heif->source ); } if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_source_parent_class )-> build( object ) ) return( -1 ); return( 0 ); } static gboolean vips_foreign_load_heif_source_is_a_source( VipsSource *source ) { const char *p; return( (p = (const char *) vips_source_sniff( source, 12 )) && vips_foreign_load_heif_is_a( p, 12 ) ); } static void vips_foreign_load_heif_source_class_init( VipsForeignLoadHeifSourceClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "heifload_source"; object_class->build = vips_foreign_load_heif_source_build; operation_class->flags |= VIPS_OPERATION_NOCACHE; load_class->is_a_source = vips_foreign_load_heif_source_is_a_source; VIPS_ARG_OBJECT( class, "source", 1, _( "Source" ), _( "Source to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsForeignLoadHeifSource, source ), VIPS_TYPE_SOURCE ); } static void vips_foreign_load_heif_source_init( VipsForeignLoadHeifSource *source ) { } #endif /*HAVE_HEIF_DECODER*/ /* The C API wrappers are defined in foreign.c. */