From 2b1daff256c80302edbf2ba95190db2b1903e0d3 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sun, 14 Feb 2021 13:55:45 +0100 Subject: [PATCH] Build a dynamically loadable module for libheif By default, a dynamically loadable module is built for libheif (i.e. `--with-heif=module`) when: * libheif is found; * GModule is supported (`gmodule_supported` pkg-config variable). This can be overridden on the command line with: * `--without-heif` - to disable libheif usage; * `--with-heif[=yes]` - to restore the previous behavior; * `--disable-modules` - to disable the build of dynamic modules. --- configure.ac | 26 +- libvips/Makefile.am | 9 + libvips/foreign/Makefile.am | 4 + libvips/foreign/foreign.c | 8 +- libvips/foreign/heif2vips.c | 1267 +++++++++++++++++++++++++++++++++++ libvips/foreign/heifload.c | 1200 +-------------------------------- libvips/foreign/heifsave.c | 659 +----------------- libvips/foreign/vips2heif.c | 714 ++++++++++++++++++++ libvips/module/heif.c | 87 +++ 9 files changed, 2111 insertions(+), 1863 deletions(-) create mode 100644 libvips/foreign/heif2vips.c create mode 100644 libvips/foreign/vips2heif.c create mode 100644 libvips/module/heif.c diff --git a/configure.ac b/configure.ac index f95d9850..50df1381 100644 --- a/configure.ac +++ b/configure.ac @@ -826,7 +826,14 @@ VIPS_LIBS="$VIPS_LIBS $LIBOPENJP2_LIBS" # libheif AC_ARG_WITH([heif], - AS_HELP_STRING([--without-heif], [build without libheif (default: test)])) + AS_HELP_STRING([--without-heif], [build without libheif (default: test)]), + [with_heif=$withval], + [with_heif=$gmodule_with_flag]) + +# libheif as a dynamically loadable module +AS_IF([test x"$with_heif" = x"module"], + [with_heif_module=$gmodule_supported_flag], + [with_heif_module=no]) if test x"$with_heif" != x"no"; then PKG_CHECK_MODULES(HEIF, libheif >= 1.3.0, @@ -845,11 +852,13 @@ if test x"$with_heif" != x"no"; then AC_DEFINE(HAVE_HEIF_ENCODER,1, [define if your libheif has encode support.]) fi - PACKAGES_USED="$PACKAGES_USED libheif" + AS_IF([test x"$with_heif_module" = x"no"], + [PACKAGES_USED="$PACKAGES_USED libheif"]) ], [AC_MSG_WARN([libheif >= 1.3.0 not found; disabling HEIF support]) pkg-config --exists --print-errors "libheif >= 1.3.0" with_heif=no + with_heif_module=no have_h265_decoder= have_h265_encoder= have_avif_decoder= @@ -858,6 +867,12 @@ if test x"$with_heif" != x"no"; then ) fi +AS_IF([test x"$with_heif_module" = x"yes"], + [AC_DEFINE([HEIF_MODULE], [1], [define to build libheif as a dynamically loadable module.])], + [VIPS_CFLAGS="$VIPS_CFLAGS $HEIF_CFLAGS" + VIPS_LIBS="$VIPS_LIBS $HEIF_LIBS"]) +AM_CONDITIONAL(HEIF_MODULE, [test x"$with_heif_module" = x"yes"]) + # color profile support added in 1.3.3 if test x"$with_heif" = x"yes"; then save_LIBS="$LIBS" @@ -891,9 +906,6 @@ if test x"$with_heif" = x"yes"; then CFLAGS="$save_CFLAGS" fi -VIPS_CFLAGS="$VIPS_CFLAGS $HEIF_CFLAGS" -VIPS_LIBS="$VIPS_LIBS $HEIF_LIBS" - # pdfium AC_ARG_WITH([pdfium], AS_HELP_STRING([--without-pdfium], [build without pdfium (default: test)])) @@ -1391,7 +1403,7 @@ PNG load/save with libpng: $with_png, \ 8bpp PNG quantisation: $with_imagequant, \ TIFF load/save with libtiff: $with_tiff, \ image pyramid save: $with_gsf, \ -HEIC/AVIF load/save with libheif: $with_heif, \ +HEIC/AVIF load/save with libheif: $with_heif (dynamic module: $with_heif_module), \ WebP load/save with libwebp: $with_libwebp, \ PDF load with PDFium: $with_pdfium, \ PDF load with poppler-glib: $with_poppler, \ @@ -1501,7 +1513,7 @@ PNG load/save with libpng: $with_png TIFF load/save with libtiff: $with_tiff image pyramid save: $with_gsf (requires libgsf-1 1.14.26 or later) -HEIC/AVIF load/save with libheif: $with_heif +HEIC/AVIF load/save with libheif: $with_heif (dynamic module: $with_heif_module) WebP load/save with libwebp: $with_libwebp (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) PDF load with PDFium: $with_pdfium diff --git a/libvips/Makefile.am b/libvips/Makefile.am index d93252a1..5a422acd 100644 --- a/libvips/Makefile.am +++ b/libvips/Makefile.am @@ -82,6 +82,10 @@ module_LTLIBRARIES = moduledir = @VIPS_LIBDIR@/vips-modules-@VIPS_MAJOR_VERSION@.@VIPS_MINOR_VERSION@ +if HEIF_MODULE +module_LTLIBRARIES += vips-heif.la +endif # HEIF_MODULE + if OPENSLIDE_MODULE module_LTLIBRARIES += vips-openslide.la endif # OPENSLIDE_MODULE @@ -104,6 +108,11 @@ MODULE_LIBADD = \ # dynamically loadable module. The C definitions are always # included in the main library. +vips_heif_la_SOURCES = module/heif.c foreign/heif2vips.c foreign/vips2heif.c +vips_heif_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(HEIF_CFLAGS) +vips_heif_la_LDFLAGS = $(MODULE_LDFLAGS) +vips_heif_la_LIBADD = $(MODULE_LIBADD) $(HEIF_LIBS) + vips_openslide_la_SOURCES = module/openslide.c foreign/openslide2vips.c vips_openslide_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(OPENSLIDE_CFLAGS) vips_openslide_la_LDFLAGS = $(MODULE_LDFLAGS) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 47270a11..1a494760 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -74,6 +74,10 @@ libforeign_la_SOURCES = \ # We still need to include the GObject part of a loader/saver # if it is not built as a dynamically loadable module. +if !HEIF_MODULE +libforeign_la_SOURCES += heif2vips.c vips2heif.c +endif # !HEIF_MODULE + if !OPENSLIDE_MODULE libforeign_la_SOURCES += openslide2vips.c endif # !OPENSLIDE_MODULE diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 7503abfb..29d65c3c 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2360,17 +2360,17 @@ vips_foreign_operation_init( void ) vips_foreign_save_nifti_get_type(); #endif /*HAVE_NIFTI*/ -#ifdef HAVE_HEIF_DECODER +#if defined(HAVE_HEIF_DECODER) && !defined(HEIF_MODULE) vips_foreign_load_heif_file_get_type(); vips_foreign_load_heif_buffer_get_type(); vips_foreign_load_heif_source_get_type(); -#endif /*HAVE_HEIF_DECODER*/ +#endif /*defined(HAVE_HEIF_DECODER) && !defined(HEIF_MODULE)*/ -#ifdef HAVE_HEIF_ENCODER +#if defined(HAVE_HEIF_ENCODER) && !defined(HEIF_MODULE) vips_foreign_save_heif_file_get_type(); vips_foreign_save_heif_buffer_get_type(); vips_foreign_save_heif_target_get_type(); -#endif /*HAVE_HEIF_ENCODER*/ +#endif /*defined(HAVE_HEIF_ENCODER) && !defined(HEIF_MODULE)*/ vips__foreign_load_operation = g_quark_from_static_string( "vips-foreign-load-operation" ); diff --git a/libvips/foreign/heif2vips.c b/libvips/foreign/heif2vips.c new file mode 100644 index 00000000..e9517dfe --- /dev/null +++ b/libvips/foreign/heif2vips.c @@ -0,0 +1,1267 @@ +/* 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 + * - include GObject part from heifload.c + */ + +/* + + 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 + +#ifdef HAVE_HEIF_DECODER + +#include +#include +#include + +#include +#include +#include + +#include "pforeign.h" + +#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; + + /* 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; + + /* 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; + +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(); + 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 ) { + const guint chunk_len = GUINT_FROM_BE( *((guint32 *) buf) ); + + int i; + + /* We've seen real files with 36 here, so 64 should be + * plenty. + */ + if( chunk_len > 64 || + 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; + + /* 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 ); + + 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; + + /* 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, "n-pages", heif->n_top ); + if( vips_object_argument_isset( VIPS_OBJECT( heif ), "n" ) ) + 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 ) ); + + /* FIXME .. we always decode to RGB in generate. We should check for + * all grey images, perhaps. + */ + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL ); + vips_image_init_fields( out, + heif->page_width, heif->page_height * heif->n, bands, + VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, + 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 ) ); + } + } +#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 ); + 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 ) { + 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( "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( " 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*/ + +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 = heif->has_alpha ? + heif_chroma_interleaved_RGBA : + heif_chroma_interleaved_RGB; + + options = heif_decoding_options_alloc(); +#ifdef HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT + /* VIPS_FORMAT_UCHAR is assumed so downsample HDR to 8bpc + */ + options->convert_hdr_to_8bit = TRUE; +#endif /*HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT*/ + 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 ) ); + + 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" ), + _( "Load this page from the file" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadHeif, page ), + 0, 100000, 0 ); + + VIPS_ARG_INT( class, "n", 3, + _( "n" ), + _( "Load this many pages" ), + 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 ); + +} + +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; + 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; + + 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*/ diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index 99a44ae6..8c470314 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -20,6 +20,8 @@ * - 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 */ /* @@ -72,10 +74,10 @@ */ #if defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER) -#include - #include "pforeign.h" +#include + void vips__heif_error( struct heif_error *error ) { @@ -91,1199 +93,7 @@ const char *vips__heif_suffs[] = { NULL }; -#endif /*defined(DECODE) || defined(ENCODE)*/ - -#ifdef HAVE_HEIF_DECODER - -#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; - - /* 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; - - /* 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; - -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(); - 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 ) { - const guint chunk_len = GUINT_FROM_BE( *((guint32 *) buf) ); - - int i; - - /* We've seen real files with 36 here, so 64 should be - * plenty. - */ - if( chunk_len > 64 || - 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; - - /* 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 ); - - 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; - - /* 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, "n-pages", heif->n_top ); - if( vips_object_argument_isset( VIPS_OBJECT( heif ), "n" ) ) - 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 ) ); - - /* FIXME .. we always decode to RGB in generate. We should check for - * all grey images, perhaps. - */ - vips_image_pipelinev( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL ); - vips_image_init_fields( out, - heif->page_width, heif->page_height * heif->n, bands, - VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, - 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 ) ); - } - } -#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 ); - 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 ) { - 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( "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( " 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*/ - -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 = heif->has_alpha ? - heif_chroma_interleaved_RGBA : - heif_chroma_interleaved_RGB; - - options = heif_decoding_options_alloc(); -#ifdef HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT - /* VIPS_FORMAT_UCHAR is assumed so downsample HDR to 8bpc - */ - options->convert_hdr_to_8bit = TRUE; -#endif /*HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT*/ - 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 ) ); - - 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" ), - _( "Load this page from the file" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadHeif, page ), - 0, 100000, 0 ); - - VIPS_ARG_INT( class, "n", 3, - _( "n" ), - _( "Load this many pages" ), - 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 ); - -} - -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; - 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; - - 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*/ +#endif /*defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER)*/ /** * vips_heifload: diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index 20ad3237..700cd890 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -8,6 +8,8 @@ * - save alpha when necessary * 15/3/20 * - revise for new VipsTarget API + * 14/2/21 kleisauke + * - move GObject part to vips2heif.c */ /* @@ -54,663 +56,6 @@ #include #include -#ifdef HAVE_HEIF_ENCODER - -#include - -#include "pforeign.h" - -typedef struct _VipsForeignSaveHeif { - VipsForeignSave parent_object; - - /* Where to write (set by subclasses). - */ - VipsTarget *target; - - /* Coding quality factor (1-100). - */ - int Q; - - /* Lossless compression. - */ - gboolean lossless; - - /* Compression format - */ - VipsForeignHeifCompression compression; - - /* CPU effort (0-8). - */ - int speed; - - /* Chroma subsampling. - */ - VipsForeignSubsample subsample_mode; - - /* The image we save. This is a copy of save->ready since we need to - * be able to update the metadata. - */ - VipsImage *image; - - int page_width; - int page_height; - int n_pages; - - struct heif_context *ctx; - struct heif_encoder *encoder; - - /* The current page we are writing. - */ - struct heif_image_handle *handle; - - /* The current page in memory which we build as we scan down the - * image. - */ - struct heif_image *img; - - /* The libheif memory area we fill with pixels from the libvips - * pipe. - */ - uint8_t *data; - int stride; - -} VipsForeignSaveHeif; - -typedef VipsForeignSaveClass VipsForeignSaveHeifClass; - -G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveHeif, vips_foreign_save_heif, - VIPS_TYPE_FOREIGN_SAVE ); - -static void -vips_foreign_save_heif_dispose( GObject *gobject ) -{ - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) gobject; - - VIPS_UNREF( heif->target ); - VIPS_UNREF( heif->image ); - VIPS_FREEF( heif_image_release, heif->img ); - VIPS_FREEF( heif_image_handle_release, heif->handle ); - VIPS_FREEF( heif_encoder_release, heif->encoder ); - VIPS_FREEF( heif_context_free, heif->ctx ); - - G_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> - dispose( gobject ); -} - -typedef struct heif_error (*libheif_metadata_fn)( struct heif_context *, - const struct heif_image_handle *, - const void *, int ); - -struct _VipsForeignSaveHeifMetadata { - const char *name; - libheif_metadata_fn saver; -} libheif_metadata[] = { - { VIPS_META_EXIF_NAME, heif_context_add_exif_metadata }, - { VIPS_META_XMP_NAME, heif_context_add_XMP_metadata } -}; - -static int -vips_foreign_save_heif_write_metadata( VipsForeignSaveHeif *heif ) -{ - int i; - struct heif_error error; - - /* Rebuild exif from tags, if we'll be saving it. - */ - if( vips_image_get_typeof( heif->image, VIPS_META_EXIF_NAME ) ) - if( vips__exif_update( heif->image ) ) - return( -1 ); - - for( i = 0; i < VIPS_NUMBER( libheif_metadata ); i++ ) - if( vips_image_get_typeof( heif->image, - libheif_metadata[i].name ) ) { - const void *data; - size_t length; - -#ifdef DEBUG - printf( "attaching %s ..\n", - libheif_metadata[i].name ); -#endif /*DEBUG*/ - - if( vips_image_get_blob( heif->image, - libheif_metadata[i].name, &data, &length ) ) - return( -1 ); - - error = libheif_metadata[i].saver( heif->ctx, - heif->handle, data, length ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - } - - return( 0 ); -} - -static int -vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page ) -{ - VipsForeignSave *save = (VipsForeignSave *) heif; - - struct heif_error error; - struct heif_encoding_options *options; - -#ifdef HAVE_HEIF_COLOR_PROFILE - if( !save->strip && - vips_image_get_typeof( heif->image, VIPS_META_ICC_NAME ) ) { - const void *data; - size_t length; - -#ifdef DEBUG - printf( "attaching profile ..\n" ); -#endif /*DEBUG*/ - - if( vips_image_get_blob( heif->image, - VIPS_META_ICC_NAME, &data, &length ) ) - return( -1 ); - - /* FIXME .. also see heif_image_set_nclx_color_profile() - */ - error = heif_image_set_raw_color_profile( heif->img, - "rICC", data, length ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - } -#endif /*HAVE_HEIF_COLOR_PROFILE*/ - - options = heif_encoding_options_alloc(); - if( vips_image_hasalpha( heif->image ) ) - options->save_alpha_channel = 1; - -#ifdef DEBUG - printf( "encoding ..\n" ); -#endif /*DEBUG*/ - error = heif_context_encode_image( heif->ctx, - heif->img, heif->encoder, options, &heif->handle ); - - heif_encoding_options_free( options ); - - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - - if( vips_image_get_typeof( heif->image, "heif-primary" ) ) { - int primary; - - if( vips_image_get_int( heif->image, - "heif-primary", &primary ) ) - return( -1 ); - - if( page == primary ) { - error = heif_context_set_primary_image( heif->ctx, - heif->handle ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - } - } - - if( !save->strip && - vips_foreign_save_heif_write_metadata( heif ) ) - return( -1 ); - - VIPS_FREEF( heif_image_handle_release, heif->handle ); - - return( 0 ); -} - -static int -vips_foreign_save_heif_write_block( VipsRegion *region, VipsRect *area, - void *a ) -{ - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) a; - - int y; - -#ifdef DEBUG - printf( "vips_foreign_save_heif_write_block: y = %d\n", area->top ); -#endif /*DEBUG*/ - - /* Copy a line at a time into our output image, write each time the - * image fills. - */ - for( y = 0; y < area->height; y++ ) { - /* Y in page. - */ - int page = (area->top + y) / heif->page_height; - int line = (area->top + y) % heif->page_height; - - VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + y ); - VipsPel *q = heif->data + line * heif->stride; - - memcpy( q, p, VIPS_IMAGE_SIZEOF_LINE( region->im ) ); - - /* Did we just write the final line? Write as a new page - * into the output. - */ - if( line == heif->page_height - 1 ) - if( vips_foreign_save_heif_write_page( heif, page ) ) - return( -1 ); - } - - return( 0 ); -} - -struct heif_error -vips_foreign_save_heif_write( struct heif_context *ctx, - const void *data, size_t length, void *userdata ) -{ - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) userdata; - - struct heif_error error; - - error.code = 0; - if( vips_target_write( heif->target, data, length ) ) - error.code = -1; - - return( error ); -} - -static int -vips_foreign_save_heif_build( VipsObject *object ) -{ - VipsForeignSave *save = (VipsForeignSave *) object; - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; - - const char *filename; - struct heif_error error; - struct heif_writer writer; - char *chroma; - - if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> - build( object ) ) - return( -1 ); - - /* Make a copy of the image in case we modify the metadata eg. for - * exif_update. - */ - if( vips_copy( save->ready, &heif->image, NULL ) ) - return( -1 ); - - /* Compression defaults to VIPS_FOREIGN_HEIF_COMPRESSION_AV1 for .avif - * suffix. - */ - filename = vips_connection_filename( VIPS_CONNECTION( heif->target ) ); - if( !vips_object_argument_isset( object, "compression" ) && - filename && - vips_iscasepostfix( filename, ".avif" ) ) - heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1; - - error = heif_context_get_encoder_for_format( heif->ctx, - (enum heif_compression_format) heif->compression, - &heif->encoder ); - if( error.code ) { - if( error.code == heif_error_Unsupported_filetype ) - vips_error( "heifsave", - "%s", _( "Unsupported compression" ) ); - else - vips__heif_error( &error ); - - return( -1 ); - } - - error = heif_encoder_set_lossy_quality( heif->encoder, heif->Q ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - - error = heif_encoder_set_lossless( heif->encoder, heif->lossless ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - - error = heif_encoder_set_parameter_integer( heif->encoder, - "speed", heif->speed ); - if( error.code && - error.subcode != heif_suberror_Unsupported_parameter ) { - vips__heif_error( &error ); - return( -1 ); - } - - chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF || - ( heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO && - heif->Q >= 90 ) ? "444" : "420"; - error = heif_encoder_set_parameter_string( heif->encoder, - "chroma", chroma ); - if( error.code && - error.subcode != heif_suberror_Unsupported_parameter ) { - vips__heif_error( &error ); - return( -1 ); - } - - /* TODO .. support extra per-encoder params with - * heif_encoder_list_parameters(). - */ - - heif->page_width = heif->image->Xsize; - heif->page_height = vips_image_get_page_height( heif->image ); - heif->n_pages = heif->image->Ysize / heif->page_height; - - /* Make a heif image the size of a page. We send sink_disc() output - * here and write a frame each time it fills. - */ -#ifdef DEBUG - printf( "vips_foreign_save_heif_build:\n" ); - printf( "\twidth = %d\n", heif->page_width ); - printf( "\theight = %d\n", heif->page_height ); - printf( "\talpha = %d\n", vips_image_hasalpha( heif->image ) ); -#endif /*DEBUG*/ - error = heif_image_create( heif->page_width, heif->page_height, - heif_colorspace_RGB, - vips_image_hasalpha( heif->image ) ? - heif_chroma_interleaved_RGBA : - heif_chroma_interleaved_RGB, - &heif->img ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - - error = heif_image_add_plane( heif->img, heif_channel_interleaved, - heif->page_width, heif->page_height, - vips_image_hasalpha( heif->image ) ? 32 : 24 ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - -#ifdef DEBUG - vips__heif_image_print( heif->img ); -#endif /*DEBUG*/ - - heif->data = heif_image_get_plane( heif->img, - heif_channel_interleaved, &heif->stride ); - - /* Write data. - */ - if( vips_sink_disc( heif->image, - vips_foreign_save_heif_write_block, heif ) ) - return( -1 ); - - /* This has to come right at the end :-( so there's no support for - * incremental writes. - */ - writer.writer_api_version = 1; - writer.write = vips_foreign_save_heif_write; - error = heif_context_write( heif->ctx, &writer, heif ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - - vips_target_finish( heif->target ); - - return( 0 ); -} - -/* Save a bit of typing. - */ -#define UC VIPS_FORMAT_UCHAR - -static int vips_heif_bandfmt[10] = { -/* UC C US S UI I F X D DX */ - UC, UC, UC, UC, UC, UC, UC, UC, UC, UC -}; - -static void -vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *class ) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *object_class = (VipsObjectClass *) class; - VipsForeignClass *foreign_class = (VipsForeignClass *) class; - VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; - - gobject_class->dispose = vips_foreign_save_heif_dispose; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "heifsave_base"; - object_class->description = _( "save image in HEIF format" ); - object_class->build = vips_foreign_save_heif_build; - - foreign_class->suffs = vips__heif_suffs; - - save_class->saveable = VIPS_SAVEABLE_RGBA_ONLY; - save_class->format_table = vips_heif_bandfmt; - - VIPS_ARG_INT( class, "Q", 10, - _( "Q" ), - _( "Q factor" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeif, Q ), - 1, 100, 50 ); - - VIPS_ARG_BOOL( class, "lossless", 13, - _( "Lossless" ), - _( "Enable lossless compression" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeif, lossless ), - FALSE ); - - VIPS_ARG_ENUM( class, "compression", 14, - _( "compression" ), - _( "Compression format" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeif, compression ), - VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, - VIPS_FOREIGN_HEIF_COMPRESSION_HEVC ); - - VIPS_ARG_INT( class, "speed", 15, - _( "speed" ), - _( "CPU effort" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeif, speed ), - 0, 8, 5 ); - - VIPS_ARG_ENUM( class, "subsample_mode", 16, - _( "Subsample mode" ), - _( "Select chroma subsample operation mode" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeif, subsample_mode ), - VIPS_TYPE_FOREIGN_SUBSAMPLE, - VIPS_FOREIGN_SUBSAMPLE_AUTO ); -} - -static void -vips_foreign_save_heif_init( VipsForeignSaveHeif *heif ) -{ - heif->ctx = heif_context_alloc(); - heif->Q = 50; - heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC; - heif->speed = 5; - heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO; -} - -typedef struct _VipsForeignSaveHeifFile { - VipsForeignSaveHeif parent_object; - - /* Filename for save. - */ - char *filename; - -} VipsForeignSaveHeifFile; - -typedef VipsForeignSaveHeifClass VipsForeignSaveHeifFileClass; - -G_DEFINE_TYPE( VipsForeignSaveHeifFile, vips_foreign_save_heif_file, - vips_foreign_save_heif_get_type() ); - -static int -vips_foreign_save_heif_file_build( VipsObject *object ) -{ - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; - VipsForeignSaveHeifFile *file = (VipsForeignSaveHeifFile *) object; - - if( !(heif->target = vips_target_new_to_file( file->filename )) ) - return( -1 ); - - if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_file_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_foreign_save_heif_file_class_init( VipsForeignSaveHeifFileClass *class ) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *object_class = (VipsObjectClass *) class; - - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "heifsave"; - object_class->build = vips_foreign_save_heif_file_build; - - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeifFile, filename ), - NULL ); - -} - -static void -vips_foreign_save_heif_file_init( VipsForeignSaveHeifFile *file ) -{ -} - -typedef struct _VipsForeignSaveHeifBuffer { - VipsForeignSaveHeif parent_object; - - /* Save to a buffer. - */ - VipsArea *buf; - -} VipsForeignSaveHeifBuffer; - -typedef VipsForeignSaveHeifClass VipsForeignSaveHeifBufferClass; - -G_DEFINE_TYPE( VipsForeignSaveHeifBuffer, vips_foreign_save_heif_buffer, - vips_foreign_save_heif_get_type() ); - -static int -vips_foreign_save_heif_buffer_build( VipsObject *object ) -{ - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; - VipsForeignSaveHeifBuffer *buffer = - (VipsForeignSaveHeifBuffer *) object; - - VipsBlob *blob; - - if( !(heif->target = vips_target_new_to_memory()) ) - return( -1 ); - - if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_buffer_parent_class )-> - build( object ) ) - return( -1 ); - - g_object_get( heif->target, "blob", &blob, NULL ); - g_object_set( buffer, "buffer", blob, NULL ); - vips_area_unref( VIPS_AREA( blob ) ); - - return( 0 ); -} - -static void -vips_foreign_save_heif_buffer_class_init( - VipsForeignSaveHeifBufferClass *class ) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *object_class = (VipsObjectClass *) class; - - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "heifsave_buffer"; - object_class->build = vips_foreign_save_heif_buffer_build; - - VIPS_ARG_BOXED( class, "buffer", 1, - _( "Buffer" ), - _( "Buffer to save to" ), - VIPS_ARGUMENT_REQUIRED_OUTPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeifBuffer, buf ), - VIPS_TYPE_BLOB ); - -} - -static void -vips_foreign_save_heif_buffer_init( VipsForeignSaveHeifBuffer *buffer ) -{ -} - -typedef struct _VipsForeignSaveHeifTarget { - VipsForeignSaveHeif parent_object; - - VipsTarget *target; -} VipsForeignSaveHeifTarget; - -typedef VipsForeignSaveHeifClass VipsForeignSaveHeifTargetClass; - -G_DEFINE_TYPE( VipsForeignSaveHeifTarget, vips_foreign_save_heif_target, - vips_foreign_save_heif_get_type() ); - -static int -vips_foreign_save_heif_target_build( VipsObject *object ) -{ - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; - VipsForeignSaveHeifTarget *target = - (VipsForeignSaveHeifTarget *) object; - - if( target->target ) { - heif->target = target->target; - g_object_ref( heif->target ); - } - - if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_target_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_foreign_save_heif_target_class_init( - VipsForeignSaveHeifTargetClass *class ) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *object_class = (VipsObjectClass *) class; - - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "heifsave_target"; - object_class->build = vips_foreign_save_heif_target_build; - - VIPS_ARG_OBJECT( class, "target", 1, - _( "Target" ), - _( "Target to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveHeifTarget, target ), - VIPS_TYPE_TARGET ); - -} - -static void -vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target ) -{ -} - -#endif /*HAVE_HEIF_ENCODER*/ - /** * vips_heifsave: (method) * @in: image to save diff --git a/libvips/foreign/vips2heif.c b/libvips/foreign/vips2heif.c new file mode 100644 index 00000000..bff14c9f --- /dev/null +++ b/libvips/foreign/vips2heif.c @@ -0,0 +1,714 @@ +/* save to heif + * + * 5/7/18 + * - from niftisave.c + * 3/7/19 [lovell] + * - add "compression" option + * 1/9/19 [meyermarcel] + * - save alpha when necessary + * 15/3/20 + * - revise for new VipsTarget API + * 14/2/21 kleisauke + * - include GObject part from heifsave.c + */ + +/* + + 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 DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#ifdef HAVE_HEIF_ENCODER + +#include +#include +#include + +#include +#include + +#include "pforeign.h" + +#include + +typedef struct _VipsForeignSaveHeif { + VipsForeignSave parent_object; + + /* Where to write (set by subclasses). + */ + VipsTarget *target; + + /* Coding quality factor (1-100). + */ + int Q; + + /* Lossless compression. + */ + gboolean lossless; + + /* Compression format + */ + VipsForeignHeifCompression compression; + + /* CPU effort (0-8). + */ + int speed; + + /* Chroma subsampling. + */ + VipsForeignSubsample subsample_mode; + + /* The image we save. This is a copy of save->ready since we need to + * be able to update the metadata. + */ + VipsImage *image; + + int page_width; + int page_height; + int n_pages; + + struct heif_context *ctx; + struct heif_encoder *encoder; + + /* The current page we are writing. + */ + struct heif_image_handle *handle; + + /* The current page in memory which we build as we scan down the + * image. + */ + struct heif_image *img; + + /* The libheif memory area we fill with pixels from the libvips + * pipe. + */ + uint8_t *data; + int stride; + +} VipsForeignSaveHeif; + +typedef VipsForeignSaveClass VipsForeignSaveHeifClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveHeif, vips_foreign_save_heif, + VIPS_TYPE_FOREIGN_SAVE ); + +static void +vips_foreign_save_heif_dispose( GObject *gobject ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) gobject; + + VIPS_UNREF( heif->target ); + VIPS_UNREF( heif->image ); + VIPS_FREEF( heif_image_release, heif->img ); + VIPS_FREEF( heif_image_handle_release, heif->handle ); + VIPS_FREEF( heif_encoder_release, heif->encoder ); + VIPS_FREEF( heif_context_free, heif->ctx ); + + G_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> + dispose( gobject ); +} + +typedef struct heif_error (*libheif_metadata_fn)( struct heif_context *, + const struct heif_image_handle *, + const void *, int ); + +struct _VipsForeignSaveHeifMetadata { + const char *name; + libheif_metadata_fn saver; +} libheif_metadata[] = { + { VIPS_META_EXIF_NAME, heif_context_add_exif_metadata }, + { VIPS_META_XMP_NAME, heif_context_add_XMP_metadata } +}; + +static int +vips_foreign_save_heif_write_metadata( VipsForeignSaveHeif *heif ) +{ + int i; + struct heif_error error; + + /* Rebuild exif from tags, if we'll be saving it. + */ + if( vips_image_get_typeof( heif->image, VIPS_META_EXIF_NAME ) ) + if( vips__exif_update( heif->image ) ) + return( -1 ); + + for( i = 0; i < VIPS_NUMBER( libheif_metadata ); i++ ) + if( vips_image_get_typeof( heif->image, + libheif_metadata[i].name ) ) { + const void *data; + size_t length; + +#ifdef DEBUG + printf( "attaching %s ..\n", + libheif_metadata[i].name ); +#endif /*DEBUG*/ + + if( vips_image_get_blob( heif->image, + libheif_metadata[i].name, &data, &length ) ) + return( -1 ); + + error = libheif_metadata[i].saver( heif->ctx, + heif->handle, data, length ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + } + + return( 0 ); +} + +static int +vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page ) +{ + VipsForeignSave *save = (VipsForeignSave *) heif; + + struct heif_error error; + struct heif_encoding_options *options; + +#ifdef HAVE_HEIF_COLOR_PROFILE + if( !save->strip && + vips_image_get_typeof( heif->image, VIPS_META_ICC_NAME ) ) { + const void *data; + size_t length; + +#ifdef DEBUG + printf( "attaching profile ..\n" ); +#endif /*DEBUG*/ + + if( vips_image_get_blob( heif->image, + VIPS_META_ICC_NAME, &data, &length ) ) + return( -1 ); + + /* FIXME .. also see heif_image_set_nclx_color_profile() + */ + error = heif_image_set_raw_color_profile( heif->img, + "rICC", data, length ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + } +#endif /*HAVE_HEIF_COLOR_PROFILE*/ + + options = heif_encoding_options_alloc(); + if( vips_image_hasalpha( heif->image ) ) + options->save_alpha_channel = 1; + +#ifdef DEBUG + printf( "encoding ..\n" ); +#endif /*DEBUG*/ + error = heif_context_encode_image( heif->ctx, + heif->img, heif->encoder, options, &heif->handle ); + + heif_encoding_options_free( options ); + + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + + if( vips_image_get_typeof( heif->image, "heif-primary" ) ) { + int primary; + + if( vips_image_get_int( heif->image, + "heif-primary", &primary ) ) + return( -1 ); + + if( page == primary ) { + error = heif_context_set_primary_image( heif->ctx, + heif->handle ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + } + } + + if( !save->strip && + vips_foreign_save_heif_write_metadata( heif ) ) + return( -1 ); + + VIPS_FREEF( heif_image_handle_release, heif->handle ); + + return( 0 ); +} + +static int +vips_foreign_save_heif_write_block( VipsRegion *region, VipsRect *area, + void *a ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) a; + + int y; + +#ifdef DEBUG + printf( "vips_foreign_save_heif_write_block: y = %d\n", area->top ); +#endif /*DEBUG*/ + + /* Copy a line at a time into our output image, write each time the + * image fills. + */ + for( y = 0; y < area->height; y++ ) { + /* Y in page. + */ + int page = (area->top + y) / heif->page_height; + int line = (area->top + y) % heif->page_height; + + VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + y ); + VipsPel *q = heif->data + line * heif->stride; + + memcpy( q, p, VIPS_IMAGE_SIZEOF_LINE( region->im ) ); + + /* Did we just write the final line? Write as a new page + * into the output. + */ + if( line == heif->page_height - 1 ) + if( vips_foreign_save_heif_write_page( heif, page ) ) + return( -1 ); + } + + return( 0 ); +} + +struct heif_error +vips_foreign_save_heif_write( struct heif_context *ctx, + const void *data, size_t length, void *userdata ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) userdata; + + struct heif_error error; + + error.code = 0; + if( vips_target_write( heif->target, data, length ) ) + error.code = -1; + + return( error ); +} + +static int +vips_foreign_save_heif_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; + + const char *filename; + struct heif_error error; + struct heif_writer writer; + char *chroma; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> + build( object ) ) + return( -1 ); + + /* Make a copy of the image in case we modify the metadata eg. for + * exif_update. + */ + if( vips_copy( save->ready, &heif->image, NULL ) ) + return( -1 ); + + /* Compression defaults to VIPS_FOREIGN_HEIF_COMPRESSION_AV1 for .avif + * suffix. + */ + filename = vips_connection_filename( VIPS_CONNECTION( heif->target ) ); + if( !vips_object_argument_isset( object, "compression" ) && + filename && + vips_iscasepostfix( filename, ".avif" ) ) + heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1; + + error = heif_context_get_encoder_for_format( heif->ctx, + (enum heif_compression_format) heif->compression, + &heif->encoder ); + if( error.code ) { + if( error.code == heif_error_Unsupported_filetype ) + vips_error( "heifsave", + "%s", _( "Unsupported compression" ) ); + else + vips__heif_error( &error ); + + return( -1 ); + } + + error = heif_encoder_set_lossy_quality( heif->encoder, heif->Q ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + + error = heif_encoder_set_lossless( heif->encoder, heif->lossless ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + + error = heif_encoder_set_parameter_integer( heif->encoder, + "speed", heif->speed ); + if( error.code && + error.subcode != heif_suberror_Unsupported_parameter ) { + vips__heif_error( &error ); + return( -1 ); + } + + chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF || + ( heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO && + heif->Q >= 90 ) ? "444" : "420"; + error = heif_encoder_set_parameter_string( heif->encoder, + "chroma", chroma ); + if( error.code && + error.subcode != heif_suberror_Unsupported_parameter ) { + vips__heif_error( &error ); + return( -1 ); + } + + /* TODO .. support extra per-encoder params with + * heif_encoder_list_parameters(). + */ + + heif->page_width = heif->image->Xsize; + heif->page_height = vips_image_get_page_height( heif->image ); + heif->n_pages = heif->image->Ysize / heif->page_height; + + /* Make a heif image the size of a page. We send sink_disc() output + * here and write a frame each time it fills. + */ +#ifdef DEBUG + printf( "vips_foreign_save_heif_build:\n" ); + printf( "\twidth = %d\n", heif->page_width ); + printf( "\theight = %d\n", heif->page_height ); + printf( "\talpha = %d\n", vips_image_hasalpha( heif->image ) ); +#endif /*DEBUG*/ + error = heif_image_create( heif->page_width, heif->page_height, + heif_colorspace_RGB, + vips_image_hasalpha( heif->image ) ? + heif_chroma_interleaved_RGBA : + heif_chroma_interleaved_RGB, + &heif->img ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + + error = heif_image_add_plane( heif->img, heif_channel_interleaved, + heif->page_width, heif->page_height, + vips_image_hasalpha( heif->image ) ? 32 : 24 ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + +#ifdef DEBUG + vips__heif_image_print( heif->img ); +#endif /*DEBUG*/ + + heif->data = heif_image_get_plane( heif->img, + heif_channel_interleaved, &heif->stride ); + + /* Write data. + */ + if( vips_sink_disc( heif->image, + vips_foreign_save_heif_write_block, heif ) ) + return( -1 ); + + /* This has to come right at the end :-( so there's no support for + * incremental writes. + */ + writer.writer_api_version = 1; + writer.write = vips_foreign_save_heif_write; + error = heif_context_write( heif->ctx, &writer, heif ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + + vips_target_finish( heif->target ); + + return( 0 ); +} + +/* Save a bit of typing. + */ +#define UC VIPS_FORMAT_UCHAR + +static int vips_heif_bandfmt[10] = { +/* UC C US S UI I F X D DX */ + UC, UC, UC, UC, UC, UC, UC, UC, UC, UC +}; + +static void +vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + + gobject_class->dispose = vips_foreign_save_heif_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "heifsave_base"; + object_class->description = _( "save image in HEIF format" ); + object_class->build = vips_foreign_save_heif_build; + + foreign_class->suffs = vips__heif_suffs; + + save_class->saveable = VIPS_SAVEABLE_RGBA_ONLY; + save_class->format_table = vips_heif_bandfmt; + + VIPS_ARG_INT( class, "Q", 10, + _( "Q" ), + _( "Q factor" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeif, Q ), + 1, 100, 50 ); + + VIPS_ARG_BOOL( class, "lossless", 13, + _( "Lossless" ), + _( "Enable lossless compression" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeif, lossless ), + FALSE ); + + VIPS_ARG_ENUM( class, "compression", 14, + _( "compression" ), + _( "Compression format" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeif, compression ), + VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, + VIPS_FOREIGN_HEIF_COMPRESSION_HEVC ); + + VIPS_ARG_INT( class, "speed", 15, + _( "speed" ), + _( "CPU effort" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeif, speed ), + 0, 8, 5 ); + + VIPS_ARG_ENUM( class, "subsample_mode", 16, + _( "Subsample mode" ), + _( "Select chroma subsample operation mode" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeif, subsample_mode ), + VIPS_TYPE_FOREIGN_SUBSAMPLE, + VIPS_FOREIGN_SUBSAMPLE_AUTO ); +} + +static void +vips_foreign_save_heif_init( VipsForeignSaveHeif *heif ) +{ + heif->ctx = heif_context_alloc(); + heif->Q = 50; + heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC; + heif->speed = 5; + heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO; +} + +typedef struct _VipsForeignSaveHeifFile { + VipsForeignSaveHeif parent_object; + + /* Filename for save. + */ + char *filename; + +} VipsForeignSaveHeifFile; + +typedef VipsForeignSaveHeifClass VipsForeignSaveHeifFileClass; + +G_DEFINE_TYPE( VipsForeignSaveHeifFile, vips_foreign_save_heif_file, + vips_foreign_save_heif_get_type() ); + +static int +vips_foreign_save_heif_file_build( VipsObject *object ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; + VipsForeignSaveHeifFile *file = (VipsForeignSaveHeifFile *) object; + + if( !(heif->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_heif_file_class_init( VipsForeignSaveHeifFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "heifsave"; + object_class->build = vips_foreign_save_heif_file_build; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeifFile, filename ), + NULL ); + +} + +static void +vips_foreign_save_heif_file_init( VipsForeignSaveHeifFile *file ) +{ +} + +typedef struct _VipsForeignSaveHeifBuffer { + VipsForeignSaveHeif parent_object; + + /* Save to a buffer. + */ + VipsArea *buf; + +} VipsForeignSaveHeifBuffer; + +typedef VipsForeignSaveHeifClass VipsForeignSaveHeifBufferClass; + +G_DEFINE_TYPE( VipsForeignSaveHeifBuffer, vips_foreign_save_heif_buffer, + vips_foreign_save_heif_get_type() ); + +static int +vips_foreign_save_heif_buffer_build( VipsObject *object ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; + VipsForeignSaveHeifBuffer *buffer = + (VipsForeignSaveHeifBuffer *) object; + + VipsBlob *blob; + + if( !(heif->target = vips_target_new_to_memory()) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_buffer_parent_class )-> + build( object ) ) + return( -1 ); + + g_object_get( heif->target, "blob", &blob, NULL ); + g_object_set( buffer, "buffer", blob, NULL ); + vips_area_unref( VIPS_AREA( blob ) ); + + return( 0 ); +} + +static void +vips_foreign_save_heif_buffer_class_init( + VipsForeignSaveHeifBufferClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "heifsave_buffer"; + object_class->build = vips_foreign_save_heif_buffer_build; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to save to" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeifBuffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_save_heif_buffer_init( VipsForeignSaveHeifBuffer *buffer ) +{ +} + +typedef struct _VipsForeignSaveHeifTarget { + VipsForeignSaveHeif parent_object; + + VipsTarget *target; +} VipsForeignSaveHeifTarget; + +typedef VipsForeignSaveHeifClass VipsForeignSaveHeifTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveHeifTarget, vips_foreign_save_heif_target, + vips_foreign_save_heif_get_type() ); + +static int +vips_foreign_save_heif_target_build( VipsObject *object ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; + VipsForeignSaveHeifTarget *target = + (VipsForeignSaveHeifTarget *) object; + + if( target->target ) { + heif->target = target->target; + g_object_ref( heif->target ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_target_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_heif_target_class_init( + VipsForeignSaveHeifTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "heifsave_target"; + object_class->build = vips_foreign_save_heif_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeifTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target ) +{ +} + +#endif /*HAVE_HEIF_ENCODER*/ diff --git a/libvips/module/heif.c b/libvips/module/heif.c new file mode 100644 index 00000000..461daa22 --- /dev/null +++ b/libvips/module/heif.c @@ -0,0 +1,87 @@ +/* libheif as a dynamically loadable module + * + * 14/2/21 kleisauke + * - initial + */ + +/* + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include +#include +#include + +#if (defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER)) && defined(HEIF_MODULE) + +/* This is called on module load. + */ +G_MODULE_EXPORT const gchar * +g_module_check_init( GModule *module ) +{ +#ifdef DEBUG + printf( "vips_heif: module init\n" ); +#endif /*DEBUG*/ + + extern GType vips_foreign_load_heif_file_get_type( void ); + extern GType vips_foreign_load_heif_buffer_get_type( void ); + extern GType vips_foreign_load_heif_source_get_type( void ); + extern GType vips_foreign_save_heif_file_get_type( void ); + extern GType vips_foreign_save_heif_buffer_get_type( void ); + extern GType vips_foreign_save_heif_target_get_type( void ); + +#ifdef HAVE_HEIF_DECODER + vips_foreign_load_heif_file_get_type(); + vips_foreign_load_heif_buffer_get_type(); + vips_foreign_load_heif_source_get_type(); +#endif /*HAVE_HEIF_DECODER*/ + +#ifdef HAVE_HEIF_ENCODER + vips_foreign_save_heif_file_get_type(); + vips_foreign_save_heif_buffer_get_type(); + vips_foreign_save_heif_target_get_type(); +#endif /*HAVE_HEIF_ENCODER*/ + + /* We can't be unloaded, there would be chaos. + */ + g_module_make_resident( module ); + + return( NULL ); +} + +#endif /*(defined(HAVE_HEIF_DECODER) || defined(HAVE_HEIF_ENCODER)) && defined(HEIF_MODULE)*/