From a63d37fc9b46247f4da2d04ae8657b5ee0420597 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 13 Feb 2021 14:15:27 +0100 Subject: [PATCH 1/6] Initial support for dynamic loadable modules with GModule --- configure.ac | 30 ++++++++++++++++++++++++++++-- libvips/Makefile.am | 30 ++++++++++++++++++++++++++++++ libvips/foreign/Makefile.am | 3 +++ libvips/iofuncs/init.c | 11 ++++++++--- libvips/module/.gitkeep | 0 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 libvips/module/.gitkeep diff --git a/configure.ac b/configure.ac index 3c99ab52..7d74f24e 100644 --- a/configure.ac +++ b/configure.ac @@ -399,8 +399,8 @@ AC_CHECK_LIB(m,hypot,[AC_DEFINE(HAVE_HYPOT,1,[have hypot() in libm.])]) AC_CHECK_LIB(m,atan2,[AC_DEFINE(HAVE_ATAN2,1,[have atan2() in libm.])]) # have to have these parts of glib ... we need glib 2.15 for gio -PKG_CHECK_MODULES(REQUIRED, glib-2.0 >= 2.40 gmodule-2.0 gobject-2.0 gio-2.0) -PACKAGES_USED="$PACKAGES_USED glib-2.0 gmodule-2.0 gobject-2.0 gio-2.0" +PKG_CHECK_MODULES(REQUIRED, glib-2.0 >= 2.40 gmodule-no-export-2.0 gobject-2.0 gio-2.0) +PACKAGES_USED="$PACKAGES_USED glib-2.0 gmodule-no-export-2.0 gobject-2.0 gio-2.0" # from 2.62 we have datetime PKG_CHECK_MODULES(DATE_TIME_FORMAT_ISO8601, glib-2.0 >= 2.62, @@ -437,6 +437,30 @@ PKG_CHECK_MODULES(HAVE_CHECKED_MUL, glib-2.0 >= 2.48, ] ) +AC_MSG_CHECKING([whether to build dynamic modules]) + +AC_ARG_ENABLE([modules], + AS_HELP_STRING([--disable-modules], [disable dynamic modules (default: enabled)])) + +gmodule_supported_bool=false +gmodule_supported_flag=no +gmodule_with_flag=yes + +if test x"$enable_modules" = x"no"; then + AC_MSG_RESULT(no) +else + AC_MSG_RESULT(yes) + AC_MSG_CHECKING(whether dynamic modules work) + gmodule_supported_bool=`$PKG_CONFIG gmodule-no-export-2.0 --variable gmodule_supported` + if $gmodule_supported_bool; then + gmodule_supported_flag=yes + gmodule_with_flag='module' + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi +fi + # check for gtk-doc GTK_DOC_CHECK([1.14],[--flavour no-tmpl]) @@ -1334,6 +1358,7 @@ VIPS_LIBS="$VIPS_LIBS $REQUIRED_LIBS -lm" VIPS_CONFIG="\ enable debug: $enable_debug, \ enable deprecated library components: $enable_deprecated, \ +enable modules: $gmodule_supported_flag, \ enable docs with gtkdoc: $enable_gtk_doc, \ gobject introspection: $found_introspection, \ RAD load/save: $with_radiance, \ @@ -1434,6 +1459,7 @@ AC_MSG_RESULT([dnl ## Build options enable debug: $enable_debug enable deprecated library components: $enable_deprecated +enable modules: $gmodule_supported_flag enable docs with gtkdoc: $enable_gtk_doc gobject introspection: $found_introspection RAD load/save: $with_radiance diff --git a/libvips/Makefile.am b/libvips/Makefile.am index 212dc491..fa113418 100644 --- a/libvips/Makefile.am +++ b/libvips/Makefile.am @@ -61,6 +61,7 @@ libvips_la_LDFLAGS = \ -version-info @LIBRARY_CURRENT@:@LIBRARY_REVISION@:@LIBRARY_AGE@ EXTRA_DIST = \ + module \ $(OPTIONAL_DIST_DIR) CLEANFILES = @@ -72,6 +73,35 @@ install-exec-hook: cp soname.h $(DESTDIR)$(pkgincludedir) && \ rm soname.h +# Modules + +module_LTLIBRARIES = + +# All modules within the $VIPSHOME/lib/vips-modules-MAJOR.MINOR +# directory are automatically loaded on vips_init. + +moduledir = @VIPS_LIBDIR@/vips-modules-@VIPS_MAJOR_VERSION@.@VIPS_MINOR_VERSION@ + +MODULE_CPPFLAGS = \ + -I${top_srcdir}/libvips/include \ + $(REQUIRED_CFLAGS) + +MODULE_LDFLAGS = \ + -no-undefined \ + -shared \ + -module \ + -avoid-version + +MODULE_LIBADD = \ + libvips.la \ + $(REQUIRED_LIBS) + +# Note that only the GObject part should be included in a +# dynamically loadable module. The C definitions are always +# included in the main library. + +# Introspection + -include $(INTROSPECTION_MAKEFILE) INTROSPECTION_GIRS = INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index afe30463..bab9fb7b 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -72,6 +72,9 @@ libforeign_la_SOURCES = \ webpload.c \ webpsave.c +# We still need to include the GObject part of a loader/saver +# if it is not built as a dynamically loadable module. + EXTRA_DIST = libnsgif AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index b14564db..1503fd61 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -510,13 +510,18 @@ vips_init( const char *argv0 ) vips_mosaicing_operation_init(); vips_g_input_stream_get_type(); - /* Load any vips8 plugins from the vips libdir. Keep going, even if - * some plugins fail to load. + /* Load any vips8 modules from the vips libdir. Keep going, even if + * some modules fail to load. + */ + (void) vips_load_plugins( "%s/vips-modules-%d.%d", + libdir, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION ); + +#if ENABLE_DEPRECATED + /* Load any vips8 plugins from the vips libdir. */ (void) vips_load_plugins( "%s/vips-plugins-%d.%d", libdir, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION ); -#if ENABLE_DEPRECATED /* Load up any vips7 plugins in the vips libdir. We don't error on * failure, it's too annoying to have VIPS refuse to start because of * a broken plugin. diff --git a/libvips/module/.gitkeep b/libvips/module/.gitkeep new file mode 100644 index 00000000..e69de29b From 5c54d137013c139bee6a80cd8899c1867720752a Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 11 Feb 2021 20:45:57 +0100 Subject: [PATCH 2/6] Support `G_MODULE_SUFFIX` as suffix for modules In addition to the `.plg` suffix, which is still present for backwards compatibility reasons. --- libvips/iofuncs/init.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 1503fd61..be4dced3 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -227,8 +227,8 @@ vips_get_argv0( void ) * Returns: 0 on success, -1 otherwise */ -/* Load all plugins in a directory ... look for '.plg' suffix. Error if we had - * any probs. +/* Load all plugins in a directory ... look for '.' or + * '.plg' (deprecated) suffix. Error if we had any probs. */ static int vips_load_plugins( const char *fmt, ... ) @@ -259,7 +259,11 @@ vips_load_plugins( const char *fmt, ... ) result = 0; while( (name = g_dir_read_name( dir )) ) - if( vips_ispostfix( name, ".plg" ) ) { + if( vips_ispostfix( name, "." G_MODULE_SUFFIX ) +#if ENABLE_DEPRECATED + || vips_ispostfix( name, ".plg" ) +#endif + ) { char path[VIPS_PATH_MAX]; GModule *module; From 6fd38df95ac0d76e18dbacc255c429834c3f969d Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 27 Mar 2021 18:42:53 +0100 Subject: [PATCH 3/6] Build a dynamically loadable module for OpenSlide By default, a dynamically loadable module is built for OpenSlide (i.e. `--with-openslide=module`) when: * OpenSlide is found; * GModule is supported (`gmodule_supported` pkg-config variable). This can be overridden on the command line with: * `--without-openslide` - to disable OpenSlide usage; * `--with-openslide[=yes]` - to restore the previous behavior; * `--disable-modules` - to disable the build of dynamic modules. --- configure.ac | 28 ++- libvips/Makefile.am | 9 + libvips/foreign/Makefile.am | 5 +- libvips/foreign/foreign.c | 4 +- libvips/foreign/openslide2vips.c | 379 ++++++++++++++++++++++++++++++- libvips/foreign/openslideload.c | 377 +----------------------------- libvips/foreign/pforeign.h | 9 - libvips/module/.gitkeep | 0 libvips/module/openslide.c | 74 ++++++ 9 files changed, 486 insertions(+), 399 deletions(-) delete mode 100644 libvips/module/.gitkeep create mode 100644 libvips/module/openslide.c diff --git a/configure.ac b/configure.ac index 7d74f24e..f95d9850 100644 --- a/configure.ac +++ b/configure.ac @@ -984,32 +984,44 @@ VIPS_LIBS="$VIPS_LIBS $ZLIB_LIBS" # OpenSlide AC_ARG_WITH([openslide], AS_HELP_STRING([--without-openslide], - [build without OpenSlide (default: test)]) -) + [build without OpenSlide (default: test)]), + [with_openslide=$withval], + [with_openslide=$gmodule_with_flag]) + +# OpenSlide as a dynamically loadable module +AS_IF([test x"$with_openslide" = x"module"], + [with_openslide_module=$gmodule_supported_flag], + [with_openslide_module=no]) if test x"$with_openslide" != x"no"; then PKG_CHECK_MODULES(OPENSLIDE, [openslide >= 3.4.0], [AC_DEFINE(HAVE_OPENSLIDE_3_4,1,[define if you have OpenSlide >= 3.4.0 installed.]) AC_DEFINE(HAVE_OPENSLIDE,1,[define if you have OpenSlide >= 3.3.0 installed.]) with_openslide=yes - PACKAGES_USED="$PACKAGES_USED openslide" + AS_IF([test x"$with_openslide_module" = x"no"], + [PACKAGES_USED="$PACKAGES_USED openslide"]) ], [AC_MSG_NOTICE([OpenSlide >= 3.4.0 not found; checking for >= 3.3.0]) PKG_CHECK_MODULES(OPENSLIDE, [openslide >= 3.3.0], [AC_DEFINE(HAVE_OPENSLIDE,1,[define if you have OpenSlide >= 3.3.0 installed.]) with_openslide=yes - PACKAGES_USED="$PACKAGES_USED openslide" + AS_IF([test x"$with_openslide_module" = x"no"], + [PACKAGES_USED="$PACKAGES_USED openslide"]) ], [AC_MSG_WARN([OpenSlide >= 3.3.0 not found; disabling virtual slide support]) with_openslide=no + with_openslide_module=no ] ) ] ) fi -VIPS_CFLAGS="$VIPS_CFLAGS $OPENSLIDE_CFLAGS" -VIPS_LIBS="$VIPS_LIBS $OPENSLIDE_LIBS" +AS_IF([test x"$with_openslide_module" = x"yes"], + [AC_DEFINE([OPENSLIDE_MODULE], [1], [define to build OpenSlide as a dynamically loadable module.])], + [VIPS_CFLAGS="$VIPS_CFLAGS $OPENSLIDE_CFLAGS" + VIPS_LIBS="$VIPS_LIBS $OPENSLIDE_LIBS"]) +AM_CONDITIONAL(OPENSLIDE_MODULE, [test x"$with_openslide_module" = x"yes"]) # matio AC_ARG_WITH([matio], @@ -1385,7 +1397,7 @@ PDF load with PDFium: $with_pdfium, \ PDF load with poppler-glib: $with_poppler, \ SVG load with librsvg-2.0: $with_rsvg, \ EXR load with OpenEXR: $with_OpenEXR, \ -slide load with OpenSlide: $with_openslide, \ +slide load with OpenSlide: $with_openslide (dynamic module: $with_openslide_module), \ Matlab load with matio: $with_matio, \ NIfTI load/save with niftiio: $with_nifti, \ FITS load/save with cfitsio: $with_cfitsio, \ @@ -1500,7 +1512,7 @@ SVG load with librsvg-2.0: $with_rsvg EXR load with OpenEXR: $with_OpenEXR JPEG2000 load/save with libopenjp2: $with_libopenjp2 (requires libopenjp2 2.2 or later) -slide load with OpenSlide: $with_openslide +slide load with OpenSlide: $with_openslide (dynamic module: $with_openslide_module) (requires openslide-3.3.0 or later) Matlab load with matio: $with_matio NIfTI load/save with niftiio: $with_nifti diff --git a/libvips/Makefile.am b/libvips/Makefile.am index fa113418..d93252a1 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 OPENSLIDE_MODULE +module_LTLIBRARIES += vips-openslide.la +endif # OPENSLIDE_MODULE + MODULE_CPPFLAGS = \ -I${top_srcdir}/libvips/include \ $(REQUIRED_CFLAGS) @@ -100,6 +104,11 @@ MODULE_LIBADD = \ # dynamically loadable module. The C definitions are always # included in the main library. +vips_openslide_la_SOURCES = module/openslide.c foreign/openslide2vips.c +vips_openslide_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(OPENSLIDE_CFLAGS) +vips_openslide_la_LDFLAGS = $(MODULE_LDFLAGS) +vips_openslide_la_LIBADD = $(MODULE_LIBADD) $(OPENSLIDE_LIBS) + # Introspection -include $(INTROSPECTION_MAKEFILE) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index bab9fb7b..47270a11 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -40,7 +40,6 @@ libforeign_la_SOURCES = \ nsgifload.c \ openexr2vips.c \ openexrload.c \ - openslide2vips.c \ openslideload.c \ pdfiumload.c \ pdfload.c \ @@ -75,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 !OPENSLIDE_MODULE +libforeign_la_SOURCES += openslide2vips.c +endif # !OPENSLIDE_MODULE + EXTRA_DIST = libnsgif AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 9cb0e876..7503abfb 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2322,10 +2322,10 @@ vips_foreign_operation_init( void ) vips_foreign_save_tiff_buffer_get_type(); #endif /*HAVE_TIFF*/ -#ifdef HAVE_OPENSLIDE +#if defined(HAVE_OPENSLIDE) && !defined(OPENSLIDE_MODULE) vips_foreign_load_openslide_file_get_type(); vips_foreign_load_openslide_source_get_type(); -#endif /*HAVE_OPENSLIDE*/ +#endif /*defined(HAVE_OPENSLIDE) && !defined(OPENSLIDE_MODULE)*/ #ifdef ENABLE_MAGICKLOAD #ifdef HAVE_MAGICK6 diff --git a/libvips/foreign/openslide2vips.c b/libvips/foreign/openslide2vips.c index 631220dd..1ec5c2e5 100644 --- a/libvips/foreign/openslide2vips.c +++ b/libvips/foreign/openslide2vips.c @@ -53,6 +53,8 @@ * - option to attach associated images as metadata * 22/6/20 adamu * - set libvips xres/yres from openslide mpp-x/mpp-y + * 13/2/21 kleisauke + * - include GObject part from openslideload.c */ /* @@ -126,7 +128,7 @@ typedef struct { int tile_height; } ReadSlide; -int +static int vips__openslide_isslide( const char *filename ) { #ifdef HAVE_OPENSLIDE_3_4 @@ -543,7 +545,7 @@ readslide_parse( ReadSlide *rslide, VipsImage *image ) return( 0 ); } -int +static int vips__openslide_read_header( const char *filename, VipsImage *out, int level, gboolean autocrop, char *associated, gboolean attach_associated ) @@ -614,7 +616,7 @@ vips__openslide_generate( VipsRegion *out, return( 0 ); } -int +static int vips__openslide_read( const char *filename, VipsImage *out, int level, gboolean autocrop, gboolean attach_associated ) { @@ -658,7 +660,7 @@ vips__openslide_read( const char *filename, VipsImage *out, return( 0 ); } -int +static int vips__openslide_read_associated( const char *filename, VipsImage *out, const char *associated ) { @@ -699,4 +701,373 @@ vips__openslide_read_associated( const char *filename, VipsImage *out, return( 0 ); } +typedef struct _VipsForeignLoadOpenslide { + VipsForeignLoad parent_object; + + /* Source to load from (set by subclasses). + */ + VipsSource *source; + + /* Filename from source. + */ + const char *filename; + + /* Load this level. + */ + int level; + + /* Crop to image bounds. + */ + gboolean autocrop; + + /* Load just this associated image. + */ + char *associated; + + /* Attach all associated images as metadata items. + */ + gboolean attach_associated; + +} VipsForeignLoadOpenslide; + +typedef VipsForeignLoadClass VipsForeignLoadOpenslideClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadOpenslide, vips_foreign_load_openslide, + VIPS_TYPE_FOREIGN_LOAD ); + +static void +vips_foreign_load_openslide_dispose( GObject *gobject ) +{ + VipsForeignLoadOpenslide *openslide = + (VipsForeignLoadOpenslide *) gobject; + + VIPS_UNREF( openslide->source ); + + G_OBJECT_CLASS( vips_foreign_load_openslide_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_openslide_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsForeignLoadOpenslide *openslide = + (VipsForeignLoadOpenslide *) object; + + /* We can only open source which have an associated filename, since + * the openslide library works in terms of filenames. + */ + if( openslide->source ) { + openslide->filename = + vips_connection_filename( VIPS_CONNECTION( + openslide->source ) ); + if( !openslide->filename ) { + vips_error( class->nickname, "%s", + _( "no filename available" ) ); + return( -1 ); + } + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_openslide_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static VipsForeignFlags +vips_foreign_load_openslide_get_flags_source( VipsSource *source ) +{ + /* We can't tell from just the source, we need to know what part of + * the file the user wants. But it'll usually be partial. + */ + return( VIPS_FOREIGN_PARTIAL ); +} + +static VipsForeignFlags +vips_foreign_load_openslide_get_flags( VipsForeignLoad *load ) +{ + VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load; + VipsForeignFlags flags; + + flags = 0; + if( !openslide->associated ) + flags |= VIPS_FOREIGN_PARTIAL; + + return( flags ); +} + +static VipsForeignFlags +vips_foreign_load_openslide_get_flags_filename( const char *filename ) +{ + VipsSource *source; + VipsForeignFlags flags; + + if( !(source = vips_source_new_from_file( filename )) ) + return( 0 ); + flags = vips_foreign_load_openslide_get_flags_source( source ); + VIPS_UNREF( source ); + + return( flags ); +} + +static int +vips_foreign_load_openslide_header( VipsForeignLoad *load ) +{ + VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load; + + if( vips__openslide_read_header( openslide->filename, load->out, + openslide->level, openslide->autocrop, + openslide->associated, openslide->attach_associated ) ) + return( -1 ); + + VIPS_SETSTR( load->out->filename, openslide->filename ); + + return( 0 ); +} + +static int +vips_foreign_load_openslide_load( VipsForeignLoad *load ) +{ + VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load; + + if( !openslide->associated ) { + if( vips__openslide_read( openslide->filename, load->real, + openslide->level, openslide->autocrop, + openslide->attach_associated ) ) + return( -1 ); + } + else { + if( vips__openslide_read_associated( openslide->filename, + load->real, openslide->associated ) ) + return( -1 ); + } + + return( 0 ); +} + +static void +vips_foreign_load_openslide_class_init( VipsForeignLoadOpenslideClass *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->dispose = vips_foreign_load_openslide_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "openslideload_base"; + object_class->description = _( "load OpenSlide base class" ); + object_class->build = vips_foreign_load_openslide_build; + + /* We need to be ahead of the tiff sniffer since many OpenSlide + * formats are tiff derivatives. If we see a tiff which would be + * better handled by the vips tiff loader we are careful to say no. + * + * We need to be ahead of JPEG, since MRXS images are also + * JPEGs. + */ + foreign_class->priority = 100; + + load_class->get_flags_filename = + vips_foreign_load_openslide_get_flags_filename; + load_class->get_flags = vips_foreign_load_openslide_get_flags; + load_class->header = vips_foreign_load_openslide_header; + load_class->load = vips_foreign_load_openslide_load; + + VIPS_ARG_INT( class, "level", 20, + _( "Level" ), + _( "Load this level from the file" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadOpenslide, level ), + 0, 100000, 0 ); + + VIPS_ARG_BOOL( class, "autocrop", 21, + _( "Autocrop" ), + _( "Crop to image bounds" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadOpenslide, autocrop ), + FALSE ); + + VIPS_ARG_STRING( class, "associated", 22, + _( "Associated" ), + _( "Load this associated image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadOpenslide, associated ), + NULL ); + + VIPS_ARG_BOOL( class, "attach-associated", 13, + _( "Attach associated" ), + _( "Attach all associated images" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadOpenslide, attach_associated ), + FALSE ); + +} + +static void +vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) +{ +} + +typedef struct _VipsForeignLoadOpenslideFile { + VipsForeignLoadOpenslide parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadOpenslideFile; + +typedef VipsForeignLoadOpenslideClass VipsForeignLoadOpenslideFileClass; + +G_DEFINE_TYPE( VipsForeignLoadOpenslideFile, vips_foreign_load_openslide_file, + vips_foreign_load_openslide_get_type() ); + +static int +vips_foreign_load_openslide_file_build( VipsObject *object ) +{ + VipsForeignLoadOpenslide *openslide = + (VipsForeignLoadOpenslide *) object; + VipsForeignLoadOpenslideFile *file = + (VipsForeignLoadOpenslideFile *) object; + + if( file->filename && + !(openslide->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_openslide_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static const char *vips_foreign_openslide_suffs[] = { + ".svs", /* Aperio */ + ".vms", ".vmu", ".ndpi", /* Hamamatsu */ + ".scn", /* Leica */ + ".mrxs", /* MIRAX */ + ".svslide", /* Sakura */ + ".tif", /* Trestle */ + ".bif", /* Ventana */ + NULL +}; + +static void +vips_foreign_load_openslide_file_class_init( + VipsForeignLoadOpenslideFileClass *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 = "openslideload"; + object_class->description = _( "load file with OpenSlide" ); + object_class->build = vips_foreign_load_openslide_file_build; + + foreign_class->suffs = vips_foreign_openslide_suffs; + + load_class->is_a = vips__openslide_isslide; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadOpenslideFile, filename ), + NULL ); + +} + +static void +vips_foreign_load_openslide_file_init( VipsForeignLoadOpenslideFile *openslide ) +{ +} + +typedef struct _VipsForeignLoadOpenslideSource { + VipsForeignLoadOpenslide parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadOpenslideSource; + +typedef VipsForeignLoadOpenslideClass VipsForeignLoadOpenslideSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadOpenslideSource, + vips_foreign_load_openslide_source, + vips_foreign_load_openslide_get_type() ); + +static int +vips_foreign_load_openslide_source_build( VipsObject *object ) +{ + VipsForeignLoadOpenslide *openslide = + (VipsForeignLoadOpenslide *) object; + VipsForeignLoadOpenslideSource *source = + (VipsForeignLoadOpenslideSource *) object; + + if( source->source ) { + openslide->source = source->source; + g_object_ref( openslide->source ); + } + + if( VIPS_OBJECT_CLASS( + vips_foreign_load_openslide_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_openslide_source_is_a_source( VipsSource *source ) +{ + const char *filename; + + return( (filename = + vips_connection_filename( VIPS_CONNECTION( source ) )) && + vips__openslide_isslide( filename ) ); +} + +static void +vips_foreign_load_openslide_source_class_init( + VipsForeignLoadOpenslideSourceClass *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 = "openslideload_source"; + object_class->description = _( "load source with OpenSlide" ); + object_class->build = vips_foreign_load_openslide_source_build; + + load_class->is_a_source = + vips_foreign_load_openslide_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadOpenslideSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_openslide_source_init( + VipsForeignLoadOpenslideSource *openslide ) +{ +} + #endif /*HAVE_OPENSLIDE*/ diff --git a/libvips/foreign/openslideload.c b/libvips/foreign/openslideload.c index 64b78248..9cc5479d 100644 --- a/libvips/foreign/openslideload.c +++ b/libvips/foreign/openslideload.c @@ -11,6 +11,8 @@ * - drop glib log handler (unneeded with >= 3.3.0) * 27/1/18 * - option to attach associated images as metadata + * 13/2/21 kleisauke + * - move GObject part to openslide2vips.c */ /* @@ -57,381 +59,6 @@ #include #include -#include "pforeign.h" - -#ifdef HAVE_OPENSLIDE - -typedef struct _VipsForeignLoadOpenslide { - VipsForeignLoad parent_object; - - /* Source to load from (set by subclasses). - */ - VipsSource *source; - - /* Filename from source. - */ - const char *filename; - - /* Load this level. - */ - int level; - - /* Crop to image bounds. - */ - gboolean autocrop; - - /* Load just this associated image. - */ - char *associated; - - /* Attach all associated images as metadata items. - */ - gboolean attach_associated; - -} VipsForeignLoadOpenslide; - -typedef VipsForeignLoadClass VipsForeignLoadOpenslideClass; - -G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadOpenslide, vips_foreign_load_openslide, - VIPS_TYPE_FOREIGN_LOAD ); - -static void -vips_foreign_load_openslide_dispose( GObject *gobject ) -{ - VipsForeignLoadOpenslide *openslide = - (VipsForeignLoadOpenslide *) gobject; - - VIPS_UNREF( openslide->source ); - - G_OBJECT_CLASS( vips_foreign_load_openslide_parent_class )-> - dispose( gobject ); -} - -static int -vips_foreign_load_openslide_build( VipsObject *object ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); - VipsForeignLoadOpenslide *openslide = - (VipsForeignLoadOpenslide *) object; - - /* We can only open source which have an associated filename, since - * the openslide library works in terms of filenames. - */ - if( openslide->source ) { - openslide->filename = - vips_connection_filename( VIPS_CONNECTION( - openslide->source ) ); - if( !openslide->filename ) { - vips_error( class->nickname, "%s", - _( "no filename available" ) ); - return( -1 ); - } - } - - if( VIPS_OBJECT_CLASS( vips_foreign_load_openslide_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static VipsForeignFlags -vips_foreign_load_openslide_get_flags_source( VipsSource *source ) -{ - /* We can't tell from just the source, we need to know what part of - * the file the user wants. But it'll usually be partial. - */ - return( VIPS_FOREIGN_PARTIAL ); -} - -static VipsForeignFlags -vips_foreign_load_openslide_get_flags( VipsForeignLoad *load ) -{ - VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load; - VipsForeignFlags flags; - - flags = 0; - if( !openslide->associated ) - flags |= VIPS_FOREIGN_PARTIAL; - - return( flags ); -} - -static VipsForeignFlags -vips_foreign_load_openslide_get_flags_filename( const char *filename ) -{ - VipsSource *source; - VipsForeignFlags flags; - - if( !(source = vips_source_new_from_file( filename )) ) - return( 0 ); - flags = vips_foreign_load_openslide_get_flags_source( source ); - VIPS_UNREF( source ); - - return( flags ); -} - -static int -vips_foreign_load_openslide_header( VipsForeignLoad *load ) -{ - VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load; - - if( vips__openslide_read_header( openslide->filename, load->out, - openslide->level, openslide->autocrop, - openslide->associated, openslide->attach_associated ) ) - return( -1 ); - - VIPS_SETSTR( load->out->filename, openslide->filename ); - - return( 0 ); -} - -static int -vips_foreign_load_openslide_load( VipsForeignLoad *load ) -{ - VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load; - - if( !openslide->associated ) { - if( vips__openslide_read( openslide->filename, load->real, - openslide->level, openslide->autocrop, - openslide->attach_associated ) ) - return( -1 ); - } - else { - if( vips__openslide_read_associated( openslide->filename, - load->real, openslide->associated ) ) - return( -1 ); - } - - return( 0 ); -} - -static void -vips_foreign_load_openslide_class_init( VipsForeignLoadOpenslideClass *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->dispose = vips_foreign_load_openslide_dispose; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "openslideload_base"; - object_class->description = _( "load OpenSlide base class" ); - object_class->build = vips_foreign_load_openslide_build; - - /* We need to be ahead of the tiff sniffer since many OpenSlide - * formats are tiff derivatives. If we see a tiff which would be - * better handled by the vips tiff loader we are careful to say no. - * - * We need to be ahead of JPEG, since MRXS images are also - * JPEGs. - */ - foreign_class->priority = 100; - - load_class->get_flags_filename = - vips_foreign_load_openslide_get_flags_filename; - load_class->get_flags = vips_foreign_load_openslide_get_flags; - load_class->header = vips_foreign_load_openslide_header; - load_class->load = vips_foreign_load_openslide_load; - - VIPS_ARG_INT( class, "level", 20, - _( "Level" ), - _( "Load this level from the file" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadOpenslide, level ), - 0, 100000, 0 ); - - VIPS_ARG_BOOL( class, "autocrop", 21, - _( "Autocrop" ), - _( "Crop to image bounds" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadOpenslide, autocrop ), - FALSE ); - - VIPS_ARG_STRING( class, "associated", 22, - _( "Associated" ), - _( "Load this associated image" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadOpenslide, associated ), - NULL ); - - VIPS_ARG_BOOL( class, "attach-associated", 13, - _( "Attach associated" ), - _( "Attach all associated images" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadOpenslide, attach_associated ), - FALSE ); - -} - -static void -vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide ) -{ -} - -typedef struct _VipsForeignLoadOpenslideFile { - VipsForeignLoadOpenslide parent_object; - - /* Filename for load. - */ - char *filename; - -} VipsForeignLoadOpenslideFile; - -typedef VipsForeignLoadOpenslideClass VipsForeignLoadOpenslideFileClass; - -G_DEFINE_TYPE( VipsForeignLoadOpenslideFile, vips_foreign_load_openslide_file, - vips_foreign_load_openslide_get_type() ); - -static int -vips_foreign_load_openslide_file_build( VipsObject *object ) -{ - VipsForeignLoadOpenslide *openslide = - (VipsForeignLoadOpenslide *) object; - VipsForeignLoadOpenslideFile *file = - (VipsForeignLoadOpenslideFile *) object; - - if( file->filename && - !(openslide->source = - vips_source_new_from_file( file->filename )) ) - return( -1 ); - - if( VIPS_OBJECT_CLASS( vips_foreign_load_openslide_file_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static const char *vips_foreign_openslide_suffs[] = { - ".svs", /* Aperio */ - ".vms", ".vmu", ".ndpi", /* Hamamatsu */ - ".scn", /* Leica */ - ".mrxs", /* MIRAX */ - ".svslide", /* Sakura */ - ".tif", /* Trestle */ - ".bif", /* Ventana */ - NULL -}; - -static void -vips_foreign_load_openslide_file_class_init( - VipsForeignLoadOpenslideFileClass *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 = "openslideload"; - object_class->description = _( "load file with OpenSlide" ); - object_class->build = vips_foreign_load_openslide_file_build; - - foreign_class->suffs = vips_foreign_openslide_suffs; - - load_class->is_a = vips__openslide_isslide; - - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadOpenslideFile, filename ), - NULL ); - -} - -static void -vips_foreign_load_openslide_file_init( VipsForeignLoadOpenslideFile *openslide ) -{ -} - -typedef struct _VipsForeignLoadOpenslideSource { - VipsForeignLoadOpenslide parent_object; - - /* Load from a source. - */ - VipsSource *source; - -} VipsForeignLoadOpenslideSource; - -typedef VipsForeignLoadOpenslideClass VipsForeignLoadOpenslideSourceClass; - -G_DEFINE_TYPE( VipsForeignLoadOpenslideSource, - vips_foreign_load_openslide_source, - vips_foreign_load_openslide_get_type() ); - -static int -vips_foreign_load_openslide_source_build( VipsObject *object ) -{ - VipsForeignLoadOpenslide *openslide = - (VipsForeignLoadOpenslide *) object; - VipsForeignLoadOpenslideSource *source = - (VipsForeignLoadOpenslideSource *) object; - - if( source->source ) { - openslide->source = source->source; - g_object_ref( openslide->source ); - } - - if( VIPS_OBJECT_CLASS( - vips_foreign_load_openslide_source_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static gboolean -vips_foreign_load_openslide_source_is_a_source( VipsSource *source ) -{ - const char *filename; - - return( (filename = - vips_connection_filename( VIPS_CONNECTION( source ) )) && - vips__openslide_isslide( filename ) ); -} - -static void -vips_foreign_load_openslide_source_class_init( - VipsForeignLoadOpenslideSourceClass *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 = "openslideload_source"; - object_class->description = _( "load source with OpenSlide" ); - object_class->build = vips_foreign_load_openslide_source_build; - - load_class->is_a_source = - vips_foreign_load_openslide_source_is_a_source; - - VIPS_ARG_OBJECT( class, "source", 1, - _( "Source" ), - _( "Source to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadOpenslideSource, source ), - VIPS_TYPE_SOURCE ); - -} - -static void -vips_foreign_load_openslide_source_init( - VipsForeignLoadOpenslideSource *openslide ) -{ -} - -#endif /*HAVE_OPENSLIDE*/ - /** * vips_openslideload: * @filename: file to load diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 433bf1fe..de2b38c2 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -213,15 +213,6 @@ int vips__webp_write_target( VipsImage *image, VipsTarget *target, gboolean min_size, int kmin, int kmax, gboolean strip, const char *profile ); -int vips__openslide_isslide( const char *filename ); -int vips__openslide_read_header( const char *filename, VipsImage *out, - int level, gboolean autocrop, - char *associated, gboolean attach_associated ); -int vips__openslide_read( const char *filename, VipsImage *out, - int level, gboolean autocrop, gboolean attach_associated ); -int vips__openslide_read_associated( const char *filename, VipsImage *out, - const char *associated ); - gboolean vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len ); gboolean vips_foreign_load_pdf_is_a( const char *filename ); diff --git a/libvips/module/.gitkeep b/libvips/module/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/libvips/module/openslide.c b/libvips/module/openslide.c new file mode 100644 index 00000000..2b0f345e --- /dev/null +++ b/libvips/module/openslide.c @@ -0,0 +1,74 @@ +/* openslide as a dynamically loadable module + * + * 11/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_OPENSLIDE) && defined(OPENSLIDE_MODULE) + +/* This is called on module load. + */ +G_MODULE_EXPORT const gchar * +g_module_check_init( GModule *module ) +{ +#ifdef DEBUG + printf( "vips_openslide: module init\n" ); +#endif /*DEBUG*/ + + extern GType vips_foreign_load_openslide_file_get_type( void ); + extern GType vips_foreign_load_openslide_source_get_type( void ); + + vips_foreign_load_openslide_file_get_type(); + vips_foreign_load_openslide_source_get_type(); + + /* We can't be unloaded, there would be chaos. + */ + g_module_make_resident( module ); + + return( NULL ); +} + +#endif /*defined(HAVE_OPENSLIDE) && defined(OPENSLIDE_MODULE)*/ From 2b1daff256c80302edbf2ba95190db2b1903e0d3 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sun, 14 Feb 2021 13:55:45 +0100 Subject: [PATCH 4/6] 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)*/ From 0c0b68b4b9b3fc0b8d1bdfbd4c920e2406f06bca Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 21 Apr 2021 16:39:02 +0200 Subject: [PATCH 5/6] Build a dynamically loadable module for poppler By default, a dynamically loadable module is built for poppler (i.e. `--with-poppler=module`) when: * poppler is found; * GModule is supported (`gmodule_supported` pkg-config variable). This can be overridden on the command line with: * `--without-poppler` - to disable poppler usage; * `--with-poppler[=yes]` - to restore the previous behavior; * `--disable-modules` - to disable the build of dynamic modules. --- configure.ac | 24 +- libvips/Makefile.am | 9 + libvips/foreign/Makefile.am | 4 + libvips/foreign/foreign.c | 4 +- libvips/foreign/pdfload.c | 743 +----------------------------- libvips/foreign/poppler2vips.c | 811 +++++++++++++++++++++++++++++++++ libvips/module/poppler.c | 76 +++ 7 files changed, 922 insertions(+), 749 deletions(-) create mode 100644 libvips/foreign/poppler2vips.c create mode 100644 libvips/module/poppler.c diff --git a/configure.ac b/configure.ac index 50df1381..4791d551 100644 --- a/configure.ac +++ b/configure.ac @@ -931,21 +931,33 @@ VIPS_LIBS="$VIPS_LIBS $PDFIUM_LIBS" # poppler AC_ARG_WITH([poppler], - AS_HELP_STRING([--without-poppler], [build without poppler (default: test)])) + AS_HELP_STRING([--without-poppler], [build without poppler (default: test)]), + [with_poppler=$withval], + [with_poppler=$gmodule_with_flag]) + +# poppler as a dynamically loadable module +AS_IF([test x"$with_poppler" = x"module"], + [with_poppler_module=$gmodule_supported_flag], + [with_poppler_module=no]) if test x"$with_poppler" != x"no"; then PKG_CHECK_MODULES(POPPLER, [poppler-glib >= 0.16.0 cairo >= 1.2], [ AC_DEFINE(HAVE_POPPLER,1,[define if you have poppler-glib >= 0.16.0 and cairo >= 1.2 installed.]) with_poppler=yes - PACKAGES_USED="$PACKAGES_USED poppler-glib cairo" + AS_IF([test x"$with_poppler_module" = x"no"], + [PACKAGES_USED="$PACKAGES_USED poppler-glib cairo"]) ], [ AC_MSG_WARN([poppler-glib >= 0.16.0 or cairo >= 1.2 not found; disabling PDF load via poppler]) with_poppler=no + with_poppler_module=no ]) fi -VIPS_CFLAGS="$VIPS_CFLAGS $POPPLER_CFLAGS" -VIPS_LIBS="$VIPS_LIBS $POPPLER_LIBS" +AS_IF([test x"$with_poppler_module" = x"yes"], + [AC_DEFINE([POPPLER_MODULE], [1], [define to build poppler as a dynamically loadable module.])], + [VIPS_CFLAGS="$VIPS_CFLAGS $POPPLER_CFLAGS" + VIPS_LIBS="$VIPS_LIBS $POPPLER_LIBS"]) +AM_CONDITIONAL(POPPLER_MODULE, [test x"$with_poppler_module" = x"yes"]) # librsvg AC_ARG_WITH([rsvg], @@ -1406,7 +1418,7 @@ image pyramid save: $with_gsf, \ 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, \ +PDF load with poppler-glib: $with_poppler (dynamic module: $with_poppler_module), \ SVG load with librsvg-2.0: $with_rsvg, \ EXR load with OpenEXR: $with_OpenEXR, \ slide load with OpenSlide: $with_openslide (dynamic module: $with_openslide_module), \ @@ -1517,7 +1529,7 @@ HEIC/AVIF load/save with libheif: $with_heif (dynamic module: $with_heif_m WebP load/save with libwebp: $with_libwebp (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) PDF load with PDFium: $with_pdfium -PDF load with poppler-glib: $with_poppler +PDF load with poppler-glib: $with_poppler (dynamic module: $with_poppler_module) (requires poppler-glib 0.16.0 or later) SVG load with librsvg-2.0: $with_rsvg (requires librsvg-2.0 2.34.0 or later) diff --git a/libvips/Makefile.am b/libvips/Makefile.am index 5a422acd..2f312912 100644 --- a/libvips/Makefile.am +++ b/libvips/Makefile.am @@ -86,6 +86,10 @@ if HEIF_MODULE module_LTLIBRARIES += vips-heif.la endif # HEIF_MODULE +if POPPLER_MODULE +module_LTLIBRARIES += vips-poppler.la +endif # POPPLER_MODULE + if OPENSLIDE_MODULE module_LTLIBRARIES += vips-openslide.la endif # OPENSLIDE_MODULE @@ -113,6 +117,11 @@ vips_heif_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(HEIF_CFLAGS) vips_heif_la_LDFLAGS = $(MODULE_LDFLAGS) vips_heif_la_LIBADD = $(MODULE_LIBADD) $(HEIF_LIBS) +vips_poppler_la_SOURCES = module/poppler.c foreign/poppler2vips.c +vips_poppler_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(POPPLER_CFLAGS) +vips_poppler_la_LDFLAGS = $(MODULE_LDFLAGS) +vips_poppler_la_LIBADD = $(MODULE_LIBADD) $(POPPLER_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 1a494760..537db99c 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -78,6 +78,10 @@ if !HEIF_MODULE libforeign_la_SOURCES += heif2vips.c vips2heif.c endif # !HEIF_MODULE +if !POPPLER_MODULE +libforeign_la_SOURCES += poppler2vips.c +endif # !POPPLER_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 29d65c3c..4f396bbd 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2238,11 +2238,11 @@ vips_foreign_operation_init( void ) vips_foreign_save_rad_target_get_type(); #endif /*HAVE_RADIANCE*/ -#ifdef HAVE_POPPLER +#if defined(HAVE_POPPLER) && !defined(POPPLER_MODULE) vips_foreign_load_pdf_file_get_type(); vips_foreign_load_pdf_buffer_get_type(); vips_foreign_load_pdf_source_get_type(); -#endif /*HAVE_POPPLER*/ +#endif /*defined(HAVE_POPPLER) && !defined(POPPLER_MODULE)*/ #ifdef HAVE_PDFIUM vips_foreign_load_pdf_file_get_type(); diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index 0a497510..a0a03383 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -18,6 +18,8 @@ * - move on top of VipsSource * 21/9/20 * - allow dpi and scale to both be set [le0daniel] + * 21/4/21 kleisauke + * - move GObject part to poppler2vips.c */ /* @@ -67,747 +69,6 @@ #include "pforeign.h" -#if defined(HAVE_POPPLER) - -#include -#include - -#define VIPS_TYPE_FOREIGN_LOAD_PDF (vips_foreign_load_pdf_get_type()) -#define VIPS_FOREIGN_LOAD_PDF( obj ) \ - (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ - VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdf )) -#define VIPS_FOREIGN_LOAD_PDF_CLASS( klass ) \ - (G_TYPE_CHECK_CLASS_CAST( (klass), \ - VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdfClass)) -#define VIPS_IS_FOREIGN_LOAD_PDF( obj ) \ - (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_PDF )) -#define VIPS_IS_FOREIGN_LOAD_PDF_CLASS( klass ) \ - (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_PDF )) -#define VIPS_FOREIGN_LOAD_PDF_GET_CLASS( obj ) \ - (G_TYPE_INSTANCE_GET_CLASS( (obj), \ - VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdfClass )) - -typedef struct _VipsForeignLoadPdf { - VipsForeignLoad parent_object; - - /* The VipsSource we load from, and the GInputStream we wrap around - * it. Set from subclasses. - */ - VipsSource *source; - GInputStream *stream; - - /* Load this page. - */ - int page_no; - - /* Load this many pages. - */ - int n; - - /* Render at this DPI. - */ - double dpi; - - /* Scale by this factor. - */ - double scale; - - /* The total scale factor we render with. - */ - double total_scale; - - /* Background colour. - */ - VipsArrayDouble *background; - - /* Poppler is not thread-safe, so we run inside a single-threaded - * cache. On the plus side, this means we only need one @page pointer, - * even though we change this during _generate(). - */ - PopplerDocument *doc; - PopplerPage *page; - int current_page; - - /* Doc has this many pages. - */ - int n_pages; - - /* We need to read out the size of each page we will render, and lay - * them out in the final image. - */ - VipsRect image; - VipsRect *pages; - - /* The [double] background converted to the image format. - */ - VipsPel *ink; - -} VipsForeignLoadPdf; - -typedef struct _VipsForeignLoadPdfClass { - VipsForeignLoadClass parent_class; - -} VipsForeignLoadPdfClass; - -G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPdf, vips_foreign_load_pdf, - VIPS_TYPE_FOREIGN_LOAD ); - -static void -vips_foreign_load_pdf_dispose( GObject *gobject ) -{ - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( gobject ); - - VIPS_UNREF( pdf->page ); - VIPS_UNREF( pdf->doc ); - VIPS_UNREF( pdf->source ); - VIPS_UNREF( pdf->stream ); - - G_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> - dispose( gobject ); -} - -static int -vips_foreign_load_pdf_build( VipsObject *object ) -{ - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); - - GError *error = NULL; - - if( vips_source_rewind( pdf->source ) ) - return( -1 ); - - pdf->total_scale = pdf->scale * pdf->dpi / 72.0; - - pdf->stream = vips_g_input_stream_new_from_source( pdf->source ); - if( !(pdf->doc = poppler_document_new_from_stream( pdf->stream, - vips_source_length( pdf->source ), NULL, NULL, &error )) ) { - vips_g_error( &error ); - return( -1 ); - } - - if( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); -} - -static VipsForeignFlags -vips_foreign_load_pdf_get_flags_filename( const char *filename ) -{ - /* We can render any part of the page on demand. - */ - return( VIPS_FOREIGN_PARTIAL ); -} - -static VipsForeignFlags -vips_foreign_load_pdf_get_flags( VipsForeignLoad *load ) -{ - return( VIPS_FOREIGN_PARTIAL ); -} - -static int -vips_foreign_load_pdf_get_page( VipsForeignLoadPdf *pdf, int page_no ) -{ - if( pdf->current_page != page_no || - !pdf->page ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( pdf ); - - VIPS_UNREF( pdf->page ); - pdf->current_page = -1; - -#ifdef DEBUG - printf( "vips_foreign_load_pdf_get_page: %d\n", page_no ); -#endif /*DEBUG*/ - - if( !(pdf->page = poppler_document_get_page( pdf->doc, page_no )) ) { - vips_error( class->nickname, - _( "unable to load page %d" ), page_no ); - return( -1 ); - } - pdf->current_page = page_no; - } - - return( 0 ); -} - -/* String-based metadata fields we extract. - */ -typedef struct _VipsForeignLoadPdfMetadata { - char *(*pdf_fetch)( PopplerDocument *doc ); - char *field; -} VipsForeignLoadPdfMetadata; - -static VipsForeignLoadPdfMetadata vips_foreign_load_pdf_metadata[] = { - { poppler_document_get_title, "pdf-title" }, - { poppler_document_get_author, "pdf-author" }, - { poppler_document_get_subject, "pdf-subject" }, - { poppler_document_get_keywords, "pdf-keywords" }, - { poppler_document_get_creator, "pdf-creator" }, - { poppler_document_get_producer, "pdf-producer" }, - { poppler_document_get_metadata, "pdf-metadata" }, -}; -static int n_metadata = VIPS_NUMBER( vips_foreign_load_pdf_metadata ); - -static int -vips_foreign_load_pdf_set_image( VipsForeignLoadPdf *pdf, VipsImage *out ) -{ - int i; - double res; - -#ifdef DEBUG - printf( "vips_foreign_load_pdf_set_image: %p\n", pdf ); -#endif /*DEBUG*/ - - /* We render to a linecache, so fat strips work well. - */ - vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); - - /* Extract and attach metadata. Set the old name too for compat. - */ - vips_image_set_int( out, "pdf-n_pages", pdf->n_pages ); - vips_image_set_int( out, VIPS_META_N_PAGES, pdf->n_pages ); - - for( i = 0; i < n_metadata; i++ ) { - VipsForeignLoadPdfMetadata *metadata = - &vips_foreign_load_pdf_metadata[i]; - - char *str; - - if( (str = metadata->pdf_fetch( pdf->doc )) ) { - vips_image_set_string( out, metadata->field, str ); - g_free( str ); - } - } - - /* We need pixels/mm for vips. - */ - res = pdf->dpi / 25.4; - - vips_image_init_fields( out, - pdf->image.width, pdf->image.height, - 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, res, res ); - - return( 0 ); -} - -static int -vips_foreign_load_pdf_header( VipsForeignLoad *load ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( load ); - - int top; - int i; - -#ifdef DEBUG - printf( "vips_foreign_load_pdf_header: %p\n", pdf ); -#endif /*DEBUG*/ - - pdf->n_pages = poppler_document_get_n_pages( pdf->doc ); - - /* @n == -1 means until the end of the doc. - */ - if( pdf->n == -1 ) - pdf->n = pdf->n_pages - pdf->page_no; - - if( pdf->page_no + pdf->n > pdf->n_pages || - pdf->page_no < 0 || - pdf->n <= 0 ) { - vips_error( class->nickname, "%s", _( "pages out of range" ) ); - return( -1 ); - } - - /* Lay out the pages in our output image. - */ - if( !(pdf->pages = VIPS_ARRAY( pdf, pdf->n, VipsRect )) ) - return( -1 ); - - top = 0; - pdf->image.left = 0; - pdf->image.top = 0; - pdf->image.width = 0; - pdf->image.height = 0; - for( i = 0; i < pdf->n; i++ ) { - double width; - double height; - - if( vips_foreign_load_pdf_get_page( pdf, pdf->page_no + i ) ) - return( -1 ); - poppler_page_get_size( pdf->page, &width, &height ); - pdf->pages[i].left = 0; - pdf->pages[i].top = top; - /* We do round to nearest, in the same way that vips_resize() - * does round to nearest. Without this, things like - * shrink-on-load will break. - */ - pdf->pages[i].width = VIPS_RINT( width * pdf->total_scale ); - pdf->pages[i].height = VIPS_RINT( height * pdf->total_scale ); - - if( pdf->pages[i].width > pdf->image.width ) - pdf->image.width = pdf->pages[i].width; - pdf->image.height += pdf->pages[i].height; - - top += pdf->pages[i].height; - } - - /* If all pages are the same height, we can tag this as a toilet roll - * image. - */ - for( i = 1; i < pdf->n; i++ ) - if( pdf->pages[i].height != pdf->pages[0].height ) - break; - if( vips_object_argument_isset( VIPS_OBJECT( pdf ), "n" ) ) - vips_image_set_int( load->out, - VIPS_META_PAGE_HEIGHT, pdf->pages[0].height ); - - vips_foreign_load_pdf_set_image( pdf, load->out ); - - /* Convert the background to the image format. - * - * FIXME ... we probably should convert this to pre-multiplied BGRA - * to match the Cairo convention. See vips__premultiplied_bgra2rgba(). - */ - if( !(pdf->ink = vips__vector_to_ink( class->nickname, - load->out, - VIPS_AREA( pdf->background )->data, NULL, - VIPS_AREA( pdf->background )->n )) ) - return( -1 ); - - vips_source_minimise( pdf->source ); - - return( 0 ); -} - -static void -vips_foreign_load_pdf_minimise( VipsImage *image, VipsForeignLoadPdf *pdf ) -{ - vips_source_minimise( pdf->source ); -} - -static int -vips_foreign_load_pdf_generate( VipsRegion *or, - void *seq, void *a, void *b, gboolean *stop ) -{ - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( a ); - VipsRect *r = &or->valid; - - int top; - int i; - int y; - - /* - printf( "vips_foreign_load_pdf_generate: " - "left = %d, top = %d, width = %d, height = %d\n", - r->left, r->top, r->width, r->height ); - */ - - /* Poppler won't always paint the background. - */ - vips_region_paint_pel( or, r, pdf->ink ); - - /* Search through the pages we are drawing for the first containing - * this rect. This could be quicker, perhaps a binary search, but who - * cares. - */ - for( i = 0; i < pdf->n; i++ ) - if( VIPS_RECT_BOTTOM( &pdf->pages[i] ) > r->top ) - break; - - top = r->top; - while( top < VIPS_RECT_BOTTOM( r ) ) { - VipsRect rect; - cairo_surface_t *surface; - cairo_t *cr; - - vips_rect_intersectrect( r, &pdf->pages[i], &rect ); - - surface = cairo_image_surface_create_for_data( - VIPS_REGION_ADDR( or, rect.left, rect.top ), - CAIRO_FORMAT_ARGB32, - rect.width, rect.height, - VIPS_REGION_LSKIP( or ) ); - cr = cairo_create( surface ); - cairo_surface_destroy( surface ); - - cairo_scale( cr, pdf->total_scale, pdf->total_scale ); - cairo_translate( cr, - (pdf->pages[i].left - rect.left) / pdf->total_scale, - (pdf->pages[i].top - rect.top) / pdf->total_scale ); - - /* poppler is single-threaded, but we don't need to lock since - * we're running inside a non-threaded tilecache. - */ - if( vips_foreign_load_pdf_get_page( pdf, pdf->page_no + i ) ) - return( -1 ); - poppler_page_render( pdf->page, cr ); - - cairo_destroy( cr ); - - top += rect.height; - i += 1; - } - - /* Cairo makes pre-multipled BRGA, we must byteswap and unpremultiply. - */ - for( y = 0; y < r->height; y++ ) - vips__premultiplied_bgra2rgba( - (guint32 *) VIPS_REGION_ADDR( or, r->left, r->top + y ), - r->width ); - - return( 0 ); -} - -static int -vips_foreign_load_pdf_load( VipsForeignLoad *load ) -{ - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( load ); - VipsImage **t = (VipsImage **) - vips_object_local_array( (VipsObject *) load, 2 ); - -#ifdef DEBUG - printf( "vips_foreign_load_pdf_load: %p\n", pdf ); -#endif /*DEBUG*/ - - /* Read to this image, then cache to out, see below. - */ - t[0] = vips_image_new(); - - /* Close input immediately at end of read. - */ - g_signal_connect( t[0], "minimise", - G_CALLBACK( vips_foreign_load_pdf_minimise ), pdf ); - - /* Very large strips to limit render calls per page. - */ - vips_foreign_load_pdf_set_image( pdf, t[0] ); - if( vips_image_generate( t[0], - NULL, vips_foreign_load_pdf_generate, NULL, pdf, NULL ) || - vips_sequential( t[0], &t[1], - "tile_height", VIPS_MIN( 5000, pdf->pages[0].height ), - NULL ) || - vips_image_write( t[1], load->real ) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_foreign_load_pdf_class_init( VipsForeignLoadPdfClass *class ) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *object_class = (VipsObjectClass *) class; - VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; - - gobject_class->dispose = vips_foreign_load_pdf_dispose; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "pdfload_base"; - object_class->description = _( "load PDF with libpoppler" ); - object_class->build = vips_foreign_load_pdf_build; - - load_class->get_flags_filename = - vips_foreign_load_pdf_get_flags_filename; - load_class->get_flags = vips_foreign_load_pdf_get_flags; - load_class->header = vips_foreign_load_pdf_header; - load_class->load = vips_foreign_load_pdf_load; - - VIPS_ARG_INT( class, "page", 20, - _( "Page" ), - _( "Load this page from the file" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdf, page_no ), - 0, 100000, 0 ); - - VIPS_ARG_INT( class, "n", 21, - _( "n" ), - _( "Load this many pages" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdf, n ), - -1, 100000, 1 ); - - VIPS_ARG_DOUBLE( class, "dpi", 22, - _( "DPI" ), - _( "Render at this DPI" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdf, dpi ), - 0.001, 100000.0, 72.0 ); - - VIPS_ARG_DOUBLE( class, "scale", 23, - _( "Scale" ), - _( "Scale output by this factor" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdf, scale ), - 0.001, 100000.0, 1.0 ); - - VIPS_ARG_BOXED( class, "background", 24, - _( "Background" ), - _( "Background value" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdf, background ), - VIPS_TYPE_ARRAY_DOUBLE ); - -} - -static void -vips_foreign_load_pdf_init( VipsForeignLoadPdf *pdf ) -{ - pdf->dpi = 72.0; - pdf->scale = 1.0; - pdf->n = 1; - pdf->current_page = -1; - pdf->background = vips_array_double_newv( 1, 255.0 ); -} - -typedef struct _VipsForeignLoadPdfFile { - VipsForeignLoadPdf parent_object; - - /* Filename for load. - */ - char *filename; - - char *uri; - -} VipsForeignLoadPdfFile; - -typedef VipsForeignLoadPdfClass VipsForeignLoadPdfFileClass; - -G_DEFINE_TYPE( VipsForeignLoadPdfFile, vips_foreign_load_pdf_file, - vips_foreign_load_pdf_get_type() ); - -static void -vips_foreign_load_pdf_file_dispose( GObject *gobject ) -{ - VipsForeignLoadPdfFile *file = - (VipsForeignLoadPdfFile *) gobject; - - VIPS_FREE( file->uri ); - - G_OBJECT_CLASS( vips_foreign_load_pdf_file_parent_class )-> - dispose( gobject ); -} - -static int -vips_foreign_load_pdf_file_header( VipsForeignLoad *load ) -{ - VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load; - - VIPS_SETSTR( load->out->filename, file->filename ); - - return( VIPS_FOREIGN_LOAD_CLASS( - vips_foreign_load_pdf_file_parent_class )->header( load ) ); -} - -static const char *vips_foreign_pdf_suffs[] = { - ".pdf", - NULL -}; - -static int -vips_foreign_load_pdf_file_build( VipsObject *object ) -{ - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); - VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) pdf; - -#ifdef DEBUG - printf( "vips_foreign_load_pdf_file_build: %s\n", file->filename ); -#endif /*DEBUG*/ - - if( file->filename ) { - char *path; - GError *error = NULL; - - /* We need an absolute path for a URI. - */ - path = vips_realpath( file->filename ); - if( !(file->uri = g_filename_to_uri( path, NULL, &error )) ) { - g_free( path ); - vips_g_error( &error ); - return( -1 ); - } - g_free( path ); - - if( !(pdf->source = - vips_source_new_from_file( file->filename )) ) - return( -1 ); - - } - - return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_file_parent_class )-> - build( object ) ); -} - -static void -vips_foreign_load_pdf_file_class_init( - VipsForeignLoadPdfFileClass *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->dispose = vips_foreign_load_pdf_file_dispose; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "pdfload"; - object_class->description = _( "load PDF from file" ); - object_class->build = vips_foreign_load_pdf_file_build; - - foreign_class->suffs = vips_foreign_pdf_suffs; - - load_class->is_a = vips_foreign_load_pdf_is_a; - load_class->header = vips_foreign_load_pdf_file_header; - - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdfFile, filename ), - NULL ); - -} - -static void -vips_foreign_load_pdf_file_init( VipsForeignLoadPdfFile *file ) -{ -} - -typedef struct _VipsForeignLoadPdfBuffer { - VipsForeignLoadPdf parent_object; - - /* Load from a buffer. - */ - VipsArea *buf; - -} VipsForeignLoadPdfBuffer; - -typedef VipsForeignLoadPdfClass VipsForeignLoadPdfBufferClass; - -G_DEFINE_TYPE( VipsForeignLoadPdfBuffer, vips_foreign_load_pdf_buffer, - vips_foreign_load_pdf_get_type() ); - -static int -vips_foreign_load_pdf_buffer_build( VipsObject *object ) -{ - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); - VipsForeignLoadPdfBuffer *buffer = (VipsForeignLoadPdfBuffer *) pdf; - - if( buffer->buf && - !(pdf->source = vips_source_new_from_memory( - VIPS_AREA( buffer->buf )->data, - VIPS_AREA( buffer->buf )->length )) ) - return( -1 ); - - return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_buffer_parent_class )-> - build( object ) ); -} - -static void -vips_foreign_load_pdf_buffer_class_init( - VipsForeignLoadPdfBufferClass *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 = "pdfload_buffer"; - object_class->description = _( "load PDF from buffer" ); - object_class->build = vips_foreign_load_pdf_buffer_build; - - load_class->is_a_buffer = vips_foreign_load_pdf_is_a_buffer; - - VIPS_ARG_BOXED( class, "buffer", 1, - _( "Buffer" ), - _( "Buffer to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdfBuffer, buf ), - VIPS_TYPE_BLOB ); - -} - -static void -vips_foreign_load_pdf_buffer_init( VipsForeignLoadPdfBuffer *buffer ) -{ -} - -typedef struct _VipsForeignLoadPdfSource { - VipsForeignLoadPdf parent_object; - - VipsSource *source; - -} VipsForeignLoadPdfSource; - -typedef VipsForeignLoadPdfClass VipsForeignLoadPdfSourceClass; - -G_DEFINE_TYPE( VipsForeignLoadPdfSource, vips_foreign_load_pdf_source, - vips_foreign_load_pdf_get_type() ); - -static int -vips_foreign_load_pdf_source_build( VipsObject *object ) -{ - VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); - VipsForeignLoadPdfSource *source = (VipsForeignLoadPdfSource *) pdf; - - if( source->source ) { - pdf->source = source->source; - g_object_ref( pdf->source ); - } - - return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_source_parent_class )-> - build( object ) ); -} - -static gboolean -vips_foreign_load_pdf_source_is_a_source( VipsSource *source ) -{ - const unsigned char *p; - - return( (p = vips_source_sniff( source, 4 )) && - p[0] == '%' && - p[1] == 'P' && - p[2] == 'D' && - p[3] == 'F' ); -} - -static void -vips_foreign_load_pdf_source_class_init( - VipsForeignLoadPdfSourceClass *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 = "pdfload_source"; - object_class->description = _( "load PDF from source" ); - object_class->build = vips_foreign_load_pdf_source_build; - - load_class->is_a_source = vips_foreign_load_pdf_source_is_a_source; - - VIPS_ARG_OBJECT( class, "source", 1, - _( "Source" ), - _( "Source to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPdfSource, source ), - VIPS_TYPE_SOURCE ); - -} - -static void -vips_foreign_load_pdf_source_init( VipsForeignLoadPdfSource *source ) -{ -} - -#endif /*defined(HAVE_POPPLER)*/ - /* Also used by the pdfium loader. */ gboolean diff --git a/libvips/foreign/poppler2vips.c b/libvips/foreign/poppler2vips.c new file mode 100644 index 00000000..f2f31bd9 --- /dev/null +++ b/libvips/foreign/poppler2vips.c @@ -0,0 +1,811 @@ +/* load PDF with libpoppler + * + * 7/2/16 + * - from openslideload.c + * 12/5/16 + * - add @n ... number of pages to load + * 23/11/16 + * - set page-height, if we can + * 28/6/17 + * - use a much larger strip size, thanks bubba + * 8/6/18 + * - add background param + * 16/8/18 [kleisauke] + * - shut down the input file as soon as we can + * 19/9/19 + * - reopen the input if we minimised too early + * 11/3/20 + * - move on top of VipsSource + * 21/9/20 + * - allow dpi and scale to both be set [le0daniel] + * 21/4/21 kleisauke + * - include GObject part from pdfload.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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "pforeign.h" + +#ifdef HAVE_POPPLER + +#include +#include + +#define VIPS_TYPE_FOREIGN_LOAD_PDF (vips_foreign_load_pdf_get_type()) +#define VIPS_FOREIGN_LOAD_PDF( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdf )) +#define VIPS_FOREIGN_LOAD_PDF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdfClass)) +#define VIPS_IS_FOREIGN_LOAD_PDF( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_PDF )) +#define VIPS_IS_FOREIGN_LOAD_PDF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_PDF )) +#define VIPS_FOREIGN_LOAD_PDF_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdfClass )) + +typedef struct _VipsForeignLoadPdf { + VipsForeignLoad parent_object; + + /* The VipsSource we load from, and the GInputStream we wrap around + * it. Set from subclasses. + */ + VipsSource *source; + GInputStream *stream; + + /* Load this page. + */ + int page_no; + + /* Load this many pages. + */ + int n; + + /* Render at this DPI. + */ + double dpi; + + /* Scale by this factor. + */ + double scale; + + /* The total scale factor we render with. + */ + double total_scale; + + /* Background colour. + */ + VipsArrayDouble *background; + + /* Poppler is not thread-safe, so we run inside a single-threaded + * cache. On the plus side, this means we only need one @page pointer, + * even though we change this during _generate(). + */ + PopplerDocument *doc; + PopplerPage *page; + int current_page; + + /* Doc has this many pages. + */ + int n_pages; + + /* We need to read out the size of each page we will render, and lay + * them out in the final image. + */ + VipsRect image; + VipsRect *pages; + + /* The [double] background converted to the image format. + */ + VipsPel *ink; + +} VipsForeignLoadPdf; + +typedef struct _VipsForeignLoadPdfClass { + VipsForeignLoadClass parent_class; + +} VipsForeignLoadPdfClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPdf, vips_foreign_load_pdf, + VIPS_TYPE_FOREIGN_LOAD ); + +static void +vips_foreign_load_pdf_dispose( GObject *gobject ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( gobject ); + + VIPS_UNREF( pdf->page ); + VIPS_UNREF( pdf->doc ); + VIPS_UNREF( pdf->source ); + VIPS_UNREF( pdf->stream ); + + G_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_pdf_build( VipsObject *object ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + + GError *error = NULL; + + if( vips_source_rewind( pdf->source ) ) + return( -1 ); + + pdf->total_scale = pdf->scale * pdf->dpi / 72.0; + + pdf->stream = vips_g_input_stream_new_from_source( pdf->source ); + if( !(pdf->doc = poppler_document_new_from_stream( pdf->stream, + vips_source_length( pdf->source ), NULL, NULL, &error )) ) { + vips_g_error( &error ); + return( -1 ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static VipsForeignFlags +vips_foreign_load_pdf_get_flags_filename( const char *filename ) +{ + /* We can render any part of the page on demand. + */ + return( VIPS_FOREIGN_PARTIAL ); +} + +static VipsForeignFlags +vips_foreign_load_pdf_get_flags( VipsForeignLoad *load ) +{ + return( VIPS_FOREIGN_PARTIAL ); +} + +static int +vips_foreign_load_pdf_get_page( VipsForeignLoadPdf *pdf, int page_no ) +{ + if( pdf->current_page != page_no || + !pdf->page ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( pdf ); + + VIPS_UNREF( pdf->page ); + pdf->current_page = -1; + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_get_page: %d\n", page_no ); +#endif /*DEBUG*/ + + if( !(pdf->page = poppler_document_get_page( pdf->doc, page_no )) ) { + vips_error( class->nickname, + _( "unable to load page %d" ), page_no ); + return( -1 ); + } + pdf->current_page = page_no; + } + + return( 0 ); +} + +/* String-based metadata fields we extract. + */ +typedef struct _VipsForeignLoadPdfMetadata { + char *(*pdf_fetch)( PopplerDocument *doc ); + char *field; +} VipsForeignLoadPdfMetadata; + +static VipsForeignLoadPdfMetadata vips_foreign_load_pdf_metadata[] = { + { poppler_document_get_title, "pdf-title" }, + { poppler_document_get_author, "pdf-author" }, + { poppler_document_get_subject, "pdf-subject" }, + { poppler_document_get_keywords, "pdf-keywords" }, + { poppler_document_get_creator, "pdf-creator" }, + { poppler_document_get_producer, "pdf-producer" }, + { poppler_document_get_metadata, "pdf-metadata" }, +}; +static int n_metadata = VIPS_NUMBER( vips_foreign_load_pdf_metadata ); + +static int +vips_foreign_load_pdf_set_image( VipsForeignLoadPdf *pdf, VipsImage *out ) +{ + int i; + double res; + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_set_image: %p\n", pdf ); +#endif /*DEBUG*/ + + /* We render to a linecache, so fat strips work well. + */ + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); + + /* Extract and attach metadata. Set the old name too for compat. + */ + vips_image_set_int( out, "pdf-n_pages", pdf->n_pages ); + vips_image_set_int( out, VIPS_META_N_PAGES, pdf->n_pages ); + + for( i = 0; i < n_metadata; i++ ) { + VipsForeignLoadPdfMetadata *metadata = + &vips_foreign_load_pdf_metadata[i]; + + char *str; + + if( (str = metadata->pdf_fetch( pdf->doc )) ) { + vips_image_set_string( out, metadata->field, str ); + g_free( str ); + } + } + + /* We need pixels/mm for vips. + */ + res = pdf->dpi / 25.4; + + vips_image_init_fields( out, + pdf->image.width, pdf->image.height, + 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, res, res ); + + return( 0 ); +} + +static int +vips_foreign_load_pdf_header( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( load ); + + int top; + int i; + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_header: %p\n", pdf ); +#endif /*DEBUG*/ + + pdf->n_pages = poppler_document_get_n_pages( pdf->doc ); + + /* @n == -1 means until the end of the doc. + */ + if( pdf->n == -1 ) + pdf->n = pdf->n_pages - pdf->page_no; + + if( pdf->page_no + pdf->n > pdf->n_pages || + pdf->page_no < 0 || + pdf->n <= 0 ) { + vips_error( class->nickname, "%s", _( "pages out of range" ) ); + return( -1 ); + } + + /* Lay out the pages in our output image. + */ + if( !(pdf->pages = VIPS_ARRAY( pdf, pdf->n, VipsRect )) ) + return( -1 ); + + top = 0; + pdf->image.left = 0; + pdf->image.top = 0; + pdf->image.width = 0; + pdf->image.height = 0; + for( i = 0; i < pdf->n; i++ ) { + double width; + double height; + + if( vips_foreign_load_pdf_get_page( pdf, pdf->page_no + i ) ) + return( -1 ); + poppler_page_get_size( pdf->page, &width, &height ); + pdf->pages[i].left = 0; + pdf->pages[i].top = top; + /* We do round to nearest, in the same way that vips_resize() + * does round to nearest. Without this, things like + * shrink-on-load will break. + */ + pdf->pages[i].width = VIPS_RINT( width * pdf->total_scale ); + pdf->pages[i].height = VIPS_RINT( height * pdf->total_scale ); + + if( pdf->pages[i].width > pdf->image.width ) + pdf->image.width = pdf->pages[i].width; + pdf->image.height += pdf->pages[i].height; + + top += pdf->pages[i].height; + } + + /* If all pages are the same height, we can tag this as a toilet roll + * image. + */ + for( i = 1; i < pdf->n; i++ ) + if( pdf->pages[i].height != pdf->pages[0].height ) + break; + if( vips_object_argument_isset( VIPS_OBJECT( pdf ), "n" ) ) + vips_image_set_int( load->out, + VIPS_META_PAGE_HEIGHT, pdf->pages[0].height ); + + vips_foreign_load_pdf_set_image( pdf, load->out ); + + /* Convert the background to the image format. + * + * FIXME ... we probably should convert this to pre-multiplied BGRA + * to match the Cairo convention. See vips__premultiplied_bgra2rgba(). + */ + if( !(pdf->ink = vips__vector_to_ink( class->nickname, + load->out, + VIPS_AREA( pdf->background )->data, NULL, + VIPS_AREA( pdf->background )->n )) ) + return( -1 ); + + vips_source_minimise( pdf->source ); + + return( 0 ); +} + +static void +vips_foreign_load_pdf_minimise( VipsImage *image, VipsForeignLoadPdf *pdf ) +{ + vips_source_minimise( pdf->source ); +} + +static int +vips_foreign_load_pdf_generate( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( a ); + VipsRect *r = &or->valid; + + int top; + int i; + int y; + + /* + printf( "vips_foreign_load_pdf_generate: " + "left = %d, top = %d, width = %d, height = %d\n", + r->left, r->top, r->width, r->height ); + */ + + /* Poppler won't always paint the background. + */ + vips_region_paint_pel( or, r, pdf->ink ); + + /* Search through the pages we are drawing for the first containing + * this rect. This could be quicker, perhaps a binary search, but who + * cares. + */ + for( i = 0; i < pdf->n; i++ ) + if( VIPS_RECT_BOTTOM( &pdf->pages[i] ) > r->top ) + break; + + top = r->top; + while( top < VIPS_RECT_BOTTOM( r ) ) { + VipsRect rect; + cairo_surface_t *surface; + cairo_t *cr; + + vips_rect_intersectrect( r, &pdf->pages[i], &rect ); + + surface = cairo_image_surface_create_for_data( + VIPS_REGION_ADDR( or, rect.left, rect.top ), + CAIRO_FORMAT_ARGB32, + rect.width, rect.height, + VIPS_REGION_LSKIP( or ) ); + cr = cairo_create( surface ); + cairo_surface_destroy( surface ); + + cairo_scale( cr, pdf->total_scale, pdf->total_scale ); + cairo_translate( cr, + (pdf->pages[i].left - rect.left) / pdf->total_scale, + (pdf->pages[i].top - rect.top) / pdf->total_scale ); + + /* poppler is single-threaded, but we don't need to lock since + * we're running inside a non-threaded tilecache. + */ + if( vips_foreign_load_pdf_get_page( pdf, pdf->page_no + i ) ) + return( -1 ); + poppler_page_render( pdf->page, cr ); + + cairo_destroy( cr ); + + top += rect.height; + i += 1; + } + + /* Cairo makes pre-multipled BRGA, we must byteswap and unpremultiply. + */ + for( y = 0; y < r->height; y++ ) + vips__premultiplied_bgra2rgba( + (guint32 *) VIPS_REGION_ADDR( or, r->left, r->top + y ), + r->width ); + + return( 0 ); +} + +static int +vips_foreign_load_pdf_load( VipsForeignLoad *load ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( load ); + VipsImage **t = (VipsImage **) + vips_object_local_array( (VipsObject *) load, 2 ); + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_load: %p\n", pdf ); +#endif /*DEBUG*/ + + /* Read to this image, then cache to out, see below. + */ + t[0] = vips_image_new(); + + /* Close input immediately at end of read. + */ + g_signal_connect( t[0], "minimise", + G_CALLBACK( vips_foreign_load_pdf_minimise ), pdf ); + + /* Very large strips to limit render calls per page. + */ + vips_foreign_load_pdf_set_image( pdf, t[0] ); + if( vips_image_generate( t[0], + NULL, vips_foreign_load_pdf_generate, NULL, pdf, NULL ) || + vips_sequential( t[0], &t[1], + "tile_height", VIPS_MIN( 5000, pdf->pages[0].height ), + NULL ) || + vips_image_write( t[1], load->real ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_pdf_class_init( VipsForeignLoadPdfClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_pdf_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "pdfload_base"; + object_class->description = _( "load PDF with libpoppler" ); + object_class->build = vips_foreign_load_pdf_build; + + load_class->get_flags_filename = + vips_foreign_load_pdf_get_flags_filename; + load_class->get_flags = vips_foreign_load_pdf_get_flags; + load_class->header = vips_foreign_load_pdf_header; + load_class->load = vips_foreign_load_pdf_load; + + VIPS_ARG_INT( class, "page", 20, + _( "Page" ), + _( "Load this page from the file" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdf, page_no ), + 0, 100000, 0 ); + + VIPS_ARG_INT( class, "n", 21, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdf, n ), + -1, 100000, 1 ); + + VIPS_ARG_DOUBLE( class, "dpi", 22, + _( "DPI" ), + _( "Render at this DPI" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdf, dpi ), + 0.001, 100000.0, 72.0 ); + + VIPS_ARG_DOUBLE( class, "scale", 23, + _( "Scale" ), + _( "Scale output by this factor" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdf, scale ), + 0.001, 100000.0, 1.0 ); + + VIPS_ARG_BOXED( class, "background", 24, + _( "Background" ), + _( "Background value" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdf, background ), + VIPS_TYPE_ARRAY_DOUBLE ); + +} + +static void +vips_foreign_load_pdf_init( VipsForeignLoadPdf *pdf ) +{ + pdf->dpi = 72.0; + pdf->scale = 1.0; + pdf->n = 1; + pdf->current_page = -1; + pdf->background = vips_array_double_newv( 1, 255.0 ); +} + +typedef struct _VipsForeignLoadPdfFile { + VipsForeignLoadPdf parent_object; + + /* Filename for load. + */ + char *filename; + + char *uri; + +} VipsForeignLoadPdfFile; + +typedef VipsForeignLoadPdfClass VipsForeignLoadPdfFileClass; + +G_DEFINE_TYPE( VipsForeignLoadPdfFile, vips_foreign_load_pdf_file, + vips_foreign_load_pdf_get_type() ); + +static void +vips_foreign_load_pdf_file_dispose( GObject *gobject ) +{ + VipsForeignLoadPdfFile *file = + (VipsForeignLoadPdfFile *) gobject; + + VIPS_FREE( file->uri ); + + G_OBJECT_CLASS( vips_foreign_load_pdf_file_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_pdf_file_header( VipsForeignLoad *load ) +{ + VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load; + + VIPS_SETSTR( load->out->filename, file->filename ); + + return( VIPS_FOREIGN_LOAD_CLASS( + vips_foreign_load_pdf_file_parent_class )->header( load ) ); +} + +static const char *vips_foreign_pdf_suffs[] = { + ".pdf", + NULL +}; + +static int +vips_foreign_load_pdf_file_build( VipsObject *object ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) pdf; + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_file_build: %s\n", file->filename ); +#endif /*DEBUG*/ + + if( file->filename ) { + char *path; + GError *error = NULL; + + /* We need an absolute path for a URI. + */ + path = vips_realpath( file->filename ); + if( !(file->uri = g_filename_to_uri( path, NULL, &error )) ) { + g_free( path ); + vips_g_error( &error ); + return( -1 ); + } + g_free( path ); + + if( !(pdf->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + } + + return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_file_parent_class )-> + build( object ) ); +} + +static void +vips_foreign_load_pdf_file_class_init( + VipsForeignLoadPdfFileClass *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->dispose = vips_foreign_load_pdf_file_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "pdfload"; + object_class->description = _( "load PDF from file" ); + object_class->build = vips_foreign_load_pdf_file_build; + + foreign_class->suffs = vips_foreign_pdf_suffs; + + load_class->is_a = vips_foreign_load_pdf_is_a; + load_class->header = vips_foreign_load_pdf_file_header; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdfFile, filename ), + NULL ); + +} + +static void +vips_foreign_load_pdf_file_init( VipsForeignLoadPdfFile *file ) +{ +} + +typedef struct _VipsForeignLoadPdfBuffer { + VipsForeignLoadPdf parent_object; + + /* Load from a buffer. + */ + VipsArea *buf; + +} VipsForeignLoadPdfBuffer; + +typedef VipsForeignLoadPdfClass VipsForeignLoadPdfBufferClass; + +G_DEFINE_TYPE( VipsForeignLoadPdfBuffer, vips_foreign_load_pdf_buffer, + vips_foreign_load_pdf_get_type() ); + +static int +vips_foreign_load_pdf_buffer_build( VipsObject *object ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + VipsForeignLoadPdfBuffer *buffer = (VipsForeignLoadPdfBuffer *) pdf; + + if( buffer->buf && + !(pdf->source = vips_source_new_from_memory( + VIPS_AREA( buffer->buf )->data, + VIPS_AREA( buffer->buf )->length )) ) + return( -1 ); + + return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_buffer_parent_class )-> + build( object ) ); +} + +static void +vips_foreign_load_pdf_buffer_class_init( + VipsForeignLoadPdfBufferClass *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 = "pdfload_buffer"; + object_class->description = _( "load PDF from buffer" ); + object_class->build = vips_foreign_load_pdf_buffer_build; + + load_class->is_a_buffer = vips_foreign_load_pdf_is_a_buffer; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdfBuffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_pdf_buffer_init( VipsForeignLoadPdfBuffer *buffer ) +{ +} + +typedef struct _VipsForeignLoadPdfSource { + VipsForeignLoadPdf parent_object; + + VipsSource *source; + +} VipsForeignLoadPdfSource; + +typedef VipsForeignLoadPdfClass VipsForeignLoadPdfSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadPdfSource, vips_foreign_load_pdf_source, + vips_foreign_load_pdf_get_type() ); + +static int +vips_foreign_load_pdf_source_build( VipsObject *object ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + VipsForeignLoadPdfSource *source = (VipsForeignLoadPdfSource *) pdf; + + if( source->source ) { + pdf->source = source->source; + g_object_ref( pdf->source ); + } + + return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_source_parent_class )-> + build( object ) ); +} + +static gboolean +vips_foreign_load_pdf_source_is_a_source( VipsSource *source ) +{ + const unsigned char *p; + + return( (p = vips_source_sniff( source, 4 )) && + p[0] == '%' && + p[1] == 'P' && + p[2] == 'D' && + p[3] == 'F' ); +} + +static void +vips_foreign_load_pdf_source_class_init( + VipsForeignLoadPdfSourceClass *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 = "pdfload_source"; + object_class->description = _( "load PDF from source" ); + object_class->build = vips_foreign_load_pdf_source_build; + + load_class->is_a_source = vips_foreign_load_pdf_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdfSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_pdf_source_init( VipsForeignLoadPdfSource *source ) +{ +} + +#endif /*defined(HAVE_POPPLER)*/ diff --git a/libvips/module/poppler.c b/libvips/module/poppler.c new file mode 100644 index 00000000..65b018b5 --- /dev/null +++ b/libvips/module/poppler.c @@ -0,0 +1,76 @@ +/* poppler as a dynamically loadable module + * + * 21/4/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_POPPLER) && defined(POPPLER_MODULE) + +/* This is called on module load. + */ +G_MODULE_EXPORT const gchar * +g_module_check_init( GModule *module ) +{ +#ifdef DEBUG + printf( "vips_poppler: module init\n" ); +#endif /*DEBUG*/ + + extern GType vips_foreign_load_pdf_file_get_type( void ); + extern GType vips_foreign_load_pdf_buffer_get_type( void ); + extern GType vips_foreign_load_pdf_source_get_type( void ); + + vips_foreign_load_pdf_file_get_type(); + vips_foreign_load_pdf_buffer_get_type(); + vips_foreign_load_pdf_source_get_type(); + + /* We can't be unloaded, there would be chaos. + */ + g_module_make_resident( module ); + + return( NULL ); +} + +#endif /*defined(HAVE_POPPLER) && defined(POPPLER_MODULE)*/ From 34c0f31643e797bf0b1af0e0029a73b86ccab0f1 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 21 Apr 2021 17:27:05 +0200 Subject: [PATCH 6/6] Build a dynamically loadable module for *magick By default, a dynamically loadable module is built for *magick (i.e. `--with-magick=module`) when: * ImageMagick or GraphicsMagick is found; * GModule is supported (`gmodule_supported` pkg-config variable). This can be overridden on the command line with: * `--without-magick` - to disable *magick usage; * `--with-magick[=yes]` - to restore the previous behavior; * `--disable-modules` - to disable the build of dynamic modules. --- configure.ac | 29 +- libvips/Makefile.am | 16 + libvips/foreign/Makefile.am | 14 +- libvips/foreign/foreign.c | 8 +- libvips/foreign/magick6load.c | 324 +++++++++++++++++ libvips/foreign/magickload.c | 265 +------------- libvips/foreign/magicksave.c | 580 +------------------------------ libvips/foreign/vips2magick.c | 633 ++++++++++++++++++++++++++++++++++ libvips/module/magick.c | 92 +++++ 9 files changed, 1105 insertions(+), 856 deletions(-) create mode 100644 libvips/foreign/magick6load.c create mode 100644 libvips/foreign/vips2magick.c create mode 100644 libvips/module/magick.c diff --git a/configure.ac b/configure.ac index 4791d551..36696e24 100644 --- a/configure.ac +++ b/configure.ac @@ -535,7 +535,14 @@ VIPS_LIBS="$VIPS_LIBS $FFTW_LIBS" # ImageMagick AC_ARG_WITH([magick], - AS_HELP_STRING([--without-magick], [build without libMagic (default: test)])) + AS_HELP_STRING([--without-magick], [build without libMagic (default: test)]), + [with_magick=$withval], + [with_magick=$gmodule_with_flag]) + +# libMagic as a dynamically loadable module +AS_IF([test x"$with_magick" = x"module"], + [with_magick_module=$gmodule_supported_flag], + [with_magick_module=no]) AC_ARG_WITH([magickpackage], AS_HELP_STRING([--with-magickpackage], @@ -552,6 +559,7 @@ if test x"$with_magickpackage" = x""; then ], [AC_MSG_WARN([neither MagickCore nor ImageMagick found; disabling Magick support]) with_magick=no + with_magick_module=no ] ) ] @@ -568,23 +576,27 @@ if test x"$with_magick" != x"no"; then with_magick=yes magick7=yes magick_version=magick7 - PACKAGES_USED="$PACKAGES_USED $with_magickpackage" + AS_IF([test x"$with_magick_module" = x"no"], + [PACKAGES_USED="$PACKAGES_USED $with_magickpackage"]) ], [PKG_CHECK_MODULES(MAGICK, $with_magickpackage, [AC_DEFINE(HAVE_MAGICK6,1,[define if you have libMagick6 installed.]) with_magick=yes magick6=yes magick_version=magick6 - PACKAGES_USED="$PACKAGES_USED $with_magickpackage" + AS_IF([test x"$with_magick_module" = x"no"], + [PACKAGES_USED="$PACKAGES_USED $with_magickpackage"]) ], [AC_MSG_WARN([$with_magickpackage not found; disabling Magick support]) with_magick=no + with_magick_module=no ] ) ] ) else with_magick=no + with_magick_module=no magick6=no magick_version=none with_magickpackage=none @@ -692,8 +704,11 @@ else enable_magicksave=no fi -VIPS_CFLAGS="$VIPS_CFLAGS $MAGICK_CFLAGS" -VIPS_LIBS="$VIPS_LIBS $MAGICK_LIBS" +AS_IF([test x"$with_magick_module" = x"yes"], + [AC_DEFINE([MAGICK_MODULE], [1], [define to build libMagic as a dynamically loadable module.])], + [VIPS_CFLAGS="$VIPS_CFLAGS $MAGICK_CFLAGS" + VIPS_LIBS="$VIPS_LIBS $MAGICK_LIBS"]) +AM_CONDITIONAL(MAGICK_MODULE, [test x"$with_magick_module" = x"yes"]) # orc AC_ARG_WITH([orc], @@ -1425,7 +1440,7 @@ slide load with OpenSlide: $with_openslide (dynamic module: $with_openslide_modu Matlab load with matio: $with_matio, \ NIfTI load/save with niftiio: $with_nifti, \ FITS load/save with cfitsio: $with_cfitsio, \ -Magick package: $with_magickpackage, \ +Magick package: $with_magickpackage (dynamic module: $with_magick_module), \ Magick API version: $magick_version, \ load with libMagickCore: $enable_magickload, \ save with libMagickCore: $enable_magicksave" @@ -1541,7 +1556,7 @@ slide load with OpenSlide: $with_openslide (dynamic module: $with_o Matlab load with matio: $with_matio NIfTI load/save with niftiio: $with_nifti FITS load/save with cfitsio: $with_cfitsio -Magick package: $with_magickpackage +Magick package: $with_magickpackage (dynamic module: $with_magick_module) Magick API version: $magick_version load with libMagickCore: $enable_magickload save with libMagickCore: $enable_magicksave diff --git a/libvips/Makefile.am b/libvips/Makefile.am index 2f312912..a6ebb41d 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 MAGICK_MODULE +module_LTLIBRARIES += vips-magick.la +endif # MAGICK_MODULE + if HEIF_MODULE module_LTLIBRARIES += vips-heif.la endif # HEIF_MODULE @@ -112,6 +116,18 @@ MODULE_LIBADD = \ # dynamically loadable module. The C definitions are always # included in the main library. +vips_magick_la_SOURCES = \ + module/magick.c \ + foreign/magick.c \ + foreign/magick.h \ + foreign/magick2vips.c \ + foreign/magick6load.c \ + foreign/magick7load.c \ + foreign/vips2magick.c +vips_magick_la_CPPFLAGS = $(MODULE_CPPFLAGS) $(MAGICK_CFLAGS) +vips_magick_la_LDFLAGS = $(MODULE_LDFLAGS) +vips_magick_la_LIBADD = $(MODULE_LIBADD) $(MAGICK_LIBS) + 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) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index 537db99c..84581272 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -25,10 +25,6 @@ libforeign_la_SOURCES = \ jpeg.h \ jpegload.c \ jpegsave.c \ - magick2vips.c \ - magick7load.c \ - magick.c \ - magick.h \ magickload.c \ magicksave.c \ matlab.c \ @@ -74,6 +70,16 @@ 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 !MAGICK_MODULE +libforeign_la_SOURCES += \ + magick.c \ + magick.h \ + magick2vips.c \ + magick6load.c \ + magick7load.c \ + vips2magick.c +endif # !MAGICK_MODULE + if !HEIF_MODULE libforeign_la_SOURCES += heif2vips.c vips2heif.c endif # !HEIF_MODULE diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 4f396bbd..2e0d7c39 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2327,7 +2327,7 @@ vips_foreign_operation_init( void ) vips_foreign_load_openslide_source_get_type(); #endif /*defined(HAVE_OPENSLIDE) && !defined(OPENSLIDE_MODULE)*/ -#ifdef ENABLE_MAGICKLOAD +#if defined(ENABLE_MAGICKLOAD) && !defined(MAGICK_MODULE) #ifdef HAVE_MAGICK6 vips_foreign_load_magick_file_get_type(); vips_foreign_load_magick_buffer_get_type(); @@ -2337,12 +2337,12 @@ vips_foreign_operation_init( void ) vips_foreign_load_magick7_file_get_type(); vips_foreign_load_magick7_buffer_get_type(); #endif /*HAVE_MAGICK7*/ -#endif /*ENABLE_MAGICKLOAD*/ +#endif /*defined(ENABLE_MAGICKLOAD) && !defined(MAGICK_MODULE)*/ -#ifdef ENABLE_MAGICKSAVE +#if defined(ENABLE_MAGICKSAVE) && !defined(MAGICK_MODULE) vips_foreign_save_magick_file_get_type(); vips_foreign_save_magick_buffer_get_type(); -#endif /*ENABLE_MAGICKSAVE*/ +#endif /*defined(ENABLE_MAGICKSAVE) && !defined(MAGICK_MODULE)*/ #ifdef HAVE_CFITSIO vips_foreign_load_fits_file_get_type(); diff --git a/libvips/foreign/magick6load.c b/libvips/foreign/magick6load.c new file mode 100644 index 00000000..13dab096 --- /dev/null +++ b/libvips/foreign/magick6load.c @@ -0,0 +1,324 @@ +/* load with libMagick + * + * 5/12/11 + * - from openslideload.c + * 17/1/12 + * - remove header-only loads + * 11/6/13 + * - add @all_frames option, off by default + * 14/2/16 + * - add @page option, 0 by default + * 25/11/16 + * - add @n, deprecate @all_frames (just sets n = -1) + * 8/9/17 + * - don't cache magickload + * 21/4/21 kleisauke + * - include GObject part from magickload.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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#ifdef ENABLE_MAGICKLOAD + +#ifdef HAVE_MAGICK6 + +#include "pforeign.h" +#include "magick.h" + +typedef struct _VipsForeignLoadMagick { + VipsForeignLoad parent_object; + + /* Deprecated. Just sets n = -1. + */ + gboolean all_frames; + + char *density; /* Load at this resolution */ + int page; /* Load this page (frame) */ + int n; /* Load this many pages */ + +} VipsForeignLoadMagick; + +typedef VipsForeignLoadClass VipsForeignLoadMagickClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadMagick, vips_foreign_load_magick, + VIPS_TYPE_FOREIGN_LOAD ); + +static VipsForeignFlags +vips_foreign_load_magick_get_flags_filename( const char *filename ) +{ + return( VIPS_FOREIGN_PARTIAL ); +} + +static VipsForeignFlags +vips_foreign_load_magick_get_flags( VipsForeignLoad *load ) +{ + return( VIPS_FOREIGN_PARTIAL ); +} + +static void +vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( 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 = "magickload_base"; + object_class->description = _( "load with ImageMagick" ); + + /* Don't cache magickload: it can gobble up memory and disc. + */ + operation_class->flags = VIPS_OPERATION_NOCACHE; + + /* We need to be well to the back of the queue since vips's + * dedicated loaders are usually preferable. + */ + foreign_class->priority = -100; + + load_class->get_flags_filename = + vips_foreign_load_magick_get_flags_filename; + load_class->get_flags = vips_foreign_load_magick_get_flags; + + VIPS_ARG_BOOL( class, "all_frames", 20, + _( "all_frames" ), + _( "Read all frames from an image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignLoadMagick, all_frames ), + FALSE ); + + VIPS_ARG_STRING( class, "density", 21, + _( "Density" ), + _( "Canvas resolution for rendering vector formats like SVG" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagick, density ), + NULL ); + + VIPS_ARG_INT( class, "page", 22, + _( "Page" ), + _( "Load this page from the file" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagick, page ), + 0, 100000, 0 ); + + VIPS_ARG_INT( class, "n", 23, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagick, n ), + -1, 100000, 1 ); +} + +static void +vips_foreign_load_magick_init( VipsForeignLoadMagick *magick ) +{ + magick->n = 1; +} + +typedef struct _VipsForeignLoadMagickFile { + VipsForeignLoadMagick parent_object; + + char *filename; + +} VipsForeignLoadMagickFile; + +typedef VipsForeignLoadMagickClass VipsForeignLoadMagickFileClass; + +G_DEFINE_TYPE( VipsForeignLoadMagickFile, vips_foreign_load_magick_file, + vips_foreign_load_magick_get_type() ); + +static gboolean +ismagick( const char *filename ) +{ + /* Fetch up to the first 100 bytes. Hopefully that'll be enough. + */ + unsigned char buf[100]; + int len; + + return( (len = vips__get_bytes( filename, buf, 100 )) > 10 && + magick_ismagick( buf, len ) ); +} + +/* Unfortunately, libMagick does not support header-only reads very well. See + * + * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017 + * + * Test especially with BMP, GIF, TGA. So we are forced to read the entire + * image in the @header() method. + */ +static int +vips_foreign_load_magick_file_header( VipsForeignLoad *load ) +{ + VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load; + VipsForeignLoadMagickFile *magick_file = + (VipsForeignLoadMagickFile *) load; + + if( magick->all_frames ) + magick->n = -1; + + if( vips__magick_read( magick_file->filename, + load->out, magick->density, + magick->page, magick->n ) ) + return( -1 ); + + VIPS_SETSTR( load->out->filename, magick_file->filename ); + + return( 0 ); +} + +static void +vips_foreign_load_magick_file_class_init( + VipsForeignLoadMagickFileClass *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 = "magickload"; + object_class->description = _( "load file with ImageMagick" ); + + load_class->is_a = ismagick; + load_class->header = vips_foreign_load_magick_file_header; + load_class->load = NULL; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagickFile, filename ), + NULL ); + +} + +static void +vips_foreign_load_magick_file_init( VipsForeignLoadMagickFile *magick_file ) +{ +} + +typedef struct _VipsForeignLoadMagickBuffer { + VipsForeignLoadMagick parent_object; + + VipsArea *buf; + +} VipsForeignLoadMagickBuffer; + +typedef VipsForeignLoadMagickClass VipsForeignLoadMagickBufferClass; + +G_DEFINE_TYPE( VipsForeignLoadMagickBuffer, vips_foreign_load_magick_buffer, + vips_foreign_load_magick_get_type() ); + +static gboolean +vips_foreign_load_magick_buffer_is_a_buffer( const void *buf, size_t len ) +{ + return( len > 10 && magick_ismagick( (const unsigned char *) buf, len ) ); +} + +/* Unfortunately, libMagick does not support header-only reads very well. See + * + * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017 + * + * Test especially with BMP, GIF, TGA. So we are forced to read the entire + * image in the @header() method. + */ +static int +vips_foreign_load_magick_buffer_header( VipsForeignLoad *load ) +{ + VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load; + VipsForeignLoadMagickBuffer *magick_buffer = + (VipsForeignLoadMagickBuffer *) load; + + if( magick->all_frames ) + magick->n = -1; + + if( vips__magick_read_buffer( + magick_buffer->buf->data, magick_buffer->buf->length, + load->out, magick->density, magick->page, + magick->n ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_magick_buffer_class_init( + VipsForeignLoadMagickBufferClass *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 = "magickload_buffer"; + object_class->description = _( "load buffer with ImageMagick" ); + + load_class->is_a_buffer = vips_foreign_load_magick_buffer_is_a_buffer; + load_class->header = vips_foreign_load_magick_buffer_header; + load_class->load = NULL; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagickBuffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer ) +{ +} + +#endif /*HAVE_MAGICK6*/ + +#endif /*ENABLE_MAGICKLOAD*/ diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c index e94d2226..d6fdb209 100644 --- a/libvips/foreign/magickload.c +++ b/libvips/foreign/magickload.c @@ -12,6 +12,8 @@ * - add @n, deprecate @all_frames (just sets n = -1) * 8/9/17 * - don't cache magickload + * 21/4/21 kleisauke + * - move GObject part to magick6load.c */ /* @@ -58,269 +60,6 @@ #include #include -#ifdef ENABLE_MAGICKLOAD - -#ifdef HAVE_MAGICK6 - -#include "pforeign.h" -#include "magick.h" - -typedef struct _VipsForeignLoadMagick { - VipsForeignLoad parent_object; - - /* Deprecated. Just sets n = -1. - */ - gboolean all_frames; - - char *density; /* Load at this resolution */ - int page; /* Load this page (frame) */ - int n; /* Load this many pages */ - -} VipsForeignLoadMagick; - -typedef VipsForeignLoadClass VipsForeignLoadMagickClass; - -G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadMagick, vips_foreign_load_magick, - VIPS_TYPE_FOREIGN_LOAD ); - -static VipsForeignFlags -vips_foreign_load_magick_get_flags_filename( const char *filename ) -{ - return( VIPS_FOREIGN_PARTIAL ); -} - -static VipsForeignFlags -vips_foreign_load_magick_get_flags( VipsForeignLoad *load ) -{ - return( VIPS_FOREIGN_PARTIAL ); -} - -static void -vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class ) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *object_class = (VipsObjectClass *) class; - VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( 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 = "magickload_base"; - object_class->description = _( "load with ImageMagick" ); - - /* Don't cache magickload: it can gobble up memory and disc. - */ - operation_class->flags = VIPS_OPERATION_NOCACHE; - - /* We need to be well to the back of the queue since vips's - * dedicated loaders are usually preferable. - */ - foreign_class->priority = -100; - - load_class->get_flags_filename = - vips_foreign_load_magick_get_flags_filename; - load_class->get_flags = vips_foreign_load_magick_get_flags; - - VIPS_ARG_BOOL( class, "all_frames", 20, - _( "all_frames" ), - _( "Read all frames from an image" ), - VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, - G_STRUCT_OFFSET( VipsForeignLoadMagick, all_frames ), - FALSE ); - - VIPS_ARG_STRING( class, "density", 21, - _( "Density" ), - _( "Canvas resolution for rendering vector formats like SVG" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadMagick, density ), - NULL ); - - VIPS_ARG_INT( class, "page", 22, - _( "Page" ), - _( "Load this page from the file" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadMagick, page ), - 0, 100000, 0 ); - - VIPS_ARG_INT( class, "n", 23, - _( "n" ), - _( "Load this many pages" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadMagick, n ), - -1, 100000, 1 ); -} - -static void -vips_foreign_load_magick_init( VipsForeignLoadMagick *magick ) -{ - magick->n = 1; -} - -typedef struct _VipsForeignLoadMagickFile { - VipsForeignLoadMagick parent_object; - - char *filename; - -} VipsForeignLoadMagickFile; - -typedef VipsForeignLoadMagickClass VipsForeignLoadMagickFileClass; - -G_DEFINE_TYPE( VipsForeignLoadMagickFile, vips_foreign_load_magick_file, - vips_foreign_load_magick_get_type() ); - -static gboolean -ismagick( const char *filename ) -{ - /* Fetch up to the first 100 bytes. Hopefully that'll be enough. - */ - unsigned char buf[100]; - int len; - - return( (len = vips__get_bytes( filename, buf, 100 )) > 10 && - magick_ismagick( buf, len ) ); -} - -/* Unfortunately, libMagick does not support header-only reads very well. See - * - * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017 - * - * Test especially with BMP, GIF, TGA. So we are forced to read the entire - * image in the @header() method. - */ -static int -vips_foreign_load_magick_file_header( VipsForeignLoad *load ) -{ - VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load; - VipsForeignLoadMagickFile *magick_file = - (VipsForeignLoadMagickFile *) load; - - if( magick->all_frames ) - magick->n = -1; - - if( vips__magick_read( magick_file->filename, - load->out, magick->density, - magick->page, magick->n ) ) - return( -1 ); - - VIPS_SETSTR( load->out->filename, magick_file->filename ); - - return( 0 ); -} - -static void -vips_foreign_load_magick_file_class_init( - VipsForeignLoadMagickFileClass *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 = "magickload"; - object_class->description = _( "load file with ImageMagick" ); - - load_class->is_a = ismagick; - load_class->header = vips_foreign_load_magick_file_header; - load_class->load = NULL; - - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadMagickFile, filename ), - NULL ); - -} - -static void -vips_foreign_load_magick_file_init( VipsForeignLoadMagickFile *magick_file ) -{ -} - -typedef struct _VipsForeignLoadMagickBuffer { - VipsForeignLoadMagick parent_object; - - VipsArea *buf; - -} VipsForeignLoadMagickBuffer; - -typedef VipsForeignLoadMagickClass VipsForeignLoadMagickBufferClass; - -G_DEFINE_TYPE( VipsForeignLoadMagickBuffer, vips_foreign_load_magick_buffer, - vips_foreign_load_magick_get_type() ); - -static gboolean -vips_foreign_load_magick_buffer_is_a_buffer( const void *buf, size_t len ) -{ - return( len > 10 && magick_ismagick( (const unsigned char *) buf, len ) ); -} - -/* Unfortunately, libMagick does not support header-only reads very well. See - * - * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017 - * - * Test especially with BMP, GIF, TGA. So we are forced to read the entire - * image in the @header() method. - */ -static int -vips_foreign_load_magick_buffer_header( VipsForeignLoad *load ) -{ - VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load; - VipsForeignLoadMagickBuffer *magick_buffer = - (VipsForeignLoadMagickBuffer *) load; - - if( magick->all_frames ) - magick->n = -1; - - if( vips__magick_read_buffer( - magick_buffer->buf->data, magick_buffer->buf->length, - load->out, magick->density, magick->page, - magick->n ) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_foreign_load_magick_buffer_class_init( - VipsForeignLoadMagickBufferClass *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 = "magickload_buffer"; - object_class->description = _( "load buffer with ImageMagick" ); - - load_class->is_a_buffer = vips_foreign_load_magick_buffer_is_a_buffer; - load_class->header = vips_foreign_load_magick_buffer_header; - load_class->load = NULL; - - VIPS_ARG_BOXED( class, "buffer", 1, - _( "Buffer" ), - _( "Buffer to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadMagickBuffer, buf ), - VIPS_TYPE_BLOB ); - -} - -static void -vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer ) -{ -} - -#endif /*HAVE_MAGICK6*/ - -#endif /*ENABLE_MAGICKLOAD*/ - /** * vips_magickload: * @filename: file to load diff --git a/libvips/foreign/magicksave.c b/libvips/foreign/magicksave.c index bc1d3987..1fc379a3 100644 --- a/libvips/foreign/magicksave.c +++ b/libvips/foreign/magicksave.c @@ -12,6 +12,8 @@ * - support array of delays * 5/8/19 DarthSim * - support GIF optimization + * 21/4/21 kleisauke + * - move GObject part to vips2magick.c */ /* @@ -52,584 +54,6 @@ #include -#ifdef ENABLE_MAGICKSAVE - -#include "pforeign.h" -#include "magick.h" - -typedef struct _VipsForeignSaveMagick { - VipsForeignSave parent_object; - - /* Parameters. - */ - char *filename; /* NULL during buffer output */ - char *format; - int quality; - gboolean optimize_gif_frames; - gboolean optimize_gif_transparency; - - ImageInfo *image_info; - ExceptionInfo *exception; - char *map; - StorageType storage_type; - Image *images; - Image *current_image; - - int page_height; - GValue delay_gvalue; - int *delays; - int delays_length; - - /* The position of current_image in the output. - */ - VipsRect position; - -} VipsForeignSaveMagick; - -typedef VipsForeignSaveClass VipsForeignSaveMagickClass; - -G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveMagick, vips_foreign_save_magick, - VIPS_TYPE_FOREIGN_SAVE ); - -static void -vips_foreign_save_magick_dispose( GObject *gobject ) -{ - VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) gobject; - -#ifdef DEBUG - printf( "vips_foreign_save_magick_dispose: %p\n", gobject ); -#endif /*DEBUG*/ - - VIPS_FREE( magick->map ); - VIPS_FREEF( DestroyImageList, magick->images ); - VIPS_FREEF( DestroyImageInfo, magick->image_info ); - VIPS_FREEF( magick_destroy_exception, magick->exception ); - g_value_unset( &magick->delay_gvalue ); - - G_OBJECT_CLASS( vips_foreign_save_magick_parent_class )-> - dispose( gobject ); -} - -/* Move current_image on to the next image we will write. - */ -static int -vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( magick ); - VipsForeignSave *save = (VipsForeignSave *) magick; - VipsImage *im = save->ready; - - Image *image; - int number; - const char *str; - int page_index; - - g_assert( !magick->current_image ); - - if( magick->images == NULL ) { - if( !(image = magick_acquire_image( magick->image_info, - magick->exception )) ) - return( -1 ); - - magick->images = image; - magick->position.top = 0; - magick->position.left = 0; - magick->position.width = im->Xsize; - magick->position.height = magick->page_height; - } - else { - image = GetLastImageInList( magick->images ); - magick_acquire_next_image( magick->image_info, image, - magick->exception ); - if( GetNextImageInList( image ) == NULL ) - return( -1 ); - - image = SyncNextImageInList( image ); - magick->position.top += magick->page_height; - } - - if( !magick_set_image_size( image, - im->Xsize, magick->page_height, magick->exception ) ) { - magick_vips_error( class->nickname, magick->exception ); - return( -1 ); - } - - /* Delay must be converted from milliseconds into centiseconds - * as GIF image requires centiseconds. - */ - if( magick->delays ) { - page_index = magick->position.top / magick->page_height; - if( page_index < magick->delays_length ) - image->delay = - VIPS_RINT( magick->delays[page_index] / 10.0 ); - } - - /* ImageMagick uses iterations like this (at least in gif save): - * 0 - set 0 loops (infinite) - * 1 - don't write the netscape extension block - * 2 - loop once - * 3 - loop twice etc. - */ - if( vips_image_get_typeof( im, "loop" ) && - !vips_image_get_int( im, "loop", &number ) ) { - image->iterations = (size_t) number; - } - else { - /* DEPRECATED "gif-loop" - * - * We have the simple gif meaning, so we must add one unless - * it's zero. - */ - if( vips_image_get_typeof( im, "gif-loop" ) && - !vips_image_get_int( im, "gif-loop", &number ) ) - image->iterations = (size_t) (number ? number + 1 : 0); - } - - if( vips_image_get_typeof( im, "gif-comment" ) && - !vips_image_get_string( im, "gif-comment", &str ) ) - magick_set_property( image, "comment", str, magick->exception ); - - /* libvips keeps animations as a set of independent frames, so we want - * to clear to the background between each one. - */ - image->dispose = BackgroundDispose; - - if( !save->strip && - magick_set_magick_profile( image, im, magick->exception ) ) { - magick_vips_error( class->nickname, magick->exception ); - return( -1 ); - } - - magick->current_image = image; - - return( 0 ); -} - -/* We've written all the pixels to current_image ... finish it off ready to - * move on. - */ -static void -vips_foreign_save_magick_end_image( VipsForeignSaveMagick *magick ) -{ - if( magick->current_image ) { - magick_inherit_exception( magick->exception, - magick->current_image ); - magick->current_image = NULL; - } -} - -/* Another block of pixels have arrived from libvips. - */ -static int -vips_foreign_save_magick_write_block( VipsRegion *region, VipsRect *area, - void *a ) -{ - VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) a; - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( magick ); - - VipsRect pixels; - - pixels = region->valid; - do { - VipsRect hit; - void *p; - - if( !magick->current_image && - vips_foreign_save_magick_next_image( magick ) ) - return( -1 ); - - vips_rect_intersectrect( &pixels, &magick->position, &hit ); - p = VIPS_REGION_ADDR( region, hit.left, hit.top ); - if( !magick_import_pixels( magick->current_image, - hit.left, hit.top - magick->position.top, - hit.width, hit.height, - magick->map, magick->storage_type, - p, - magick->exception ) ) { - magick_vips_error( class->nickname, - magick->exception ); - return( -1 ); - } - - /* Have we filled the page. - */ - if( VIPS_RECT_BOTTOM( &hit ) == - VIPS_RECT_BOTTOM( &magick->position ) ) - vips_foreign_save_magick_end_image( magick ); - - pixels.top += hit.height; - pixels.height -= hit.height; - } while( pixels.height > 0 ); - - return( 0 ); -} - -static int -vips_foreign_save_magick_build( VipsObject *object ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); - VipsForeignSave *save = (VipsForeignSave *) object; - VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) object; - - VipsImage *im; - -#ifdef DEBUG - printf( "vips_foreign_save_magick_build: %p\n", object ); -#endif /*DEBUG*/ - - if( VIPS_OBJECT_CLASS( vips_foreign_save_magick_parent_class )-> - build( object ) ) - return( -1 ); - - magick_genesis(); - - /* The image to save. - */ - im = save->ready; - - magick->exception = magick_acquire_exception(); - magick->image_info = CloneImageInfo( NULL ); - - switch( im->BandFmt ) { - case VIPS_FORMAT_UCHAR: - magick->storage_type = CharPixel; - break; - - case VIPS_FORMAT_USHORT: - magick->storage_type = ShortPixel; - break; - - case VIPS_FORMAT_UINT: - magick->storage_type = LongPixel; - break; - - case VIPS_FORMAT_FLOAT: - magick->storage_type = FloatPixel; - break; - - case VIPS_FORMAT_DOUBLE: - magick->storage_type = DoublePixel; - break; - - default: - vips_error( class->nickname, - "%s", _( "unsupported image format" ) ); - return( -1 ); - } - - switch( im->Bands ) { - case 1: - magick->map = g_strdup( "I" ); - break; - - case 2: - magick->map = g_strdup( "IA" ); - break; - - case 3: - magick->map = g_strdup( "RGB" ); - break; - - case 4: - if( im->Type == VIPS_INTERPRETATION_CMYK ) - magick->map = g_strdup( "CMYK" ); - else - magick->map = g_strdup( "RGBA" ); - break; - - case 5: - magick->map = g_strdup( "CMYKA" ); - break; - - default: - vips_error( class->nickname, - "%s", _( "unsupported number of image bands" ) ); - return( -1 ); - } - - if( magick->format ) { - vips_strncpy( magick->image_info->magick, - magick->format, MaxPathExtent ); - if( magick->filename ) - (void) vips_snprintf( magick->image_info->filename, - MaxPathExtent, "%s:%s", - magick->format, magick->filename ); - } - else if( magick->filename ) { - vips_strncpy( magick->image_info->filename, - magick->filename, MaxPathExtent ); - } - - if( magick->quality > 0 ) - magick->image_info->quality = magick->quality; - - magick->page_height = vips_image_get_page_height( im ); - - /* Get as a gvalue so we can keep a ref to the delay array while we - * need it. - */ - if( vips_image_get_typeof( im, "delay" ) ) { - g_value_unset( &magick->delay_gvalue ); - if( vips_image_get( im, "delay", &magick->delay_gvalue ) ) - return( -1 ); - magick->delays = vips_value_get_array_int( - &magick->delay_gvalue, &magick->delays_length ); - } - - if( vips_sink_disc( im, - vips_foreign_save_magick_write_block, magick ) ) - return( -1 ); - - if( magick->optimize_gif_frames ) { - if( !magick_optimize_image_layers( &magick->images, - magick->exception ) ) { - magick_inherit_exception( magick->exception, - magick->images ); - magick_vips_error( class->nickname, magick->exception ); - - return( -1 ); - } - } - - if( magick->optimize_gif_transparency ) { - if( !magick_optimize_image_transparency( magick->images, - magick->exception ) ) { - magick_inherit_exception( magick->exception, - magick->images ); - magick_vips_error( class->nickname, magick->exception ); - - return( -1 ); - } - } - - return( 0 ); -} - -/* We could call into libMagick and discover what save formats it supports, but - * that would mean starting up libMagick on libvips init, and that would add a - * lot of time. - * - * Instead, just list the commonly-used formats that all libMagicks support and - * that libvips does not. - */ -static const char *vips__save_magick_suffs[] = { ".gif", ".bmp", NULL }; - -/* Save a bit of typing. - */ -#define UC VIPS_FORMAT_UCHAR -#define US VIPS_FORMAT_USHORT -#define UI VIPS_FORMAT_UINT -#define F VIPS_FORMAT_FLOAT -#define D VIPS_FORMAT_DOUBLE - -static int bandfmt_magick[10] = { -/* UC C US S UI I F X D DX */ - UC, UC, US, US, UI, UI, F, F, D, D -}; - -static void -vips_foreign_save_magick_class_init( VipsForeignSaveMagickClass *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_magick_dispose; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - object_class->nickname = "magicksave_base"; - object_class->description = _( "save with ImageMagick" ); - object_class->build = vips_foreign_save_magick_build; - - /* We need to be well to the back of the queue since vips's - * dedicated savers are usually preferable. - */ - foreign_class->priority = -100; - foreign_class->suffs = vips__save_magick_suffs; - - save_class->saveable = VIPS_SAVEABLE_ANY; - save_class->format_table = bandfmt_magick; - - VIPS_ARG_STRING( class, "format", 2, - _( "Format" ), - _( "Format to save in" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveMagick, format ), - NULL ); - - VIPS_ARG_INT( class, "quality", 3, - _( "Quality" ), - _( "Quality to use" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveMagick, quality ), - 0, 100, 0 ); - - VIPS_ARG_BOOL( class, "optimize_gif_frames", 4, - _( "Optimize_gif_frames" ), - _( "Apply GIF frames optimization" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveMagick, optimize_gif_frames ), - FALSE ); - - VIPS_ARG_BOOL( class, "optimize_gif_transparency", 5, - _( "Optimize_gif_transparency" ), - _( "Apply GIF transparency optimization" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveMagick, - optimize_gif_transparency ), - FALSE ); -} - -static void -vips_foreign_save_magick_init( VipsForeignSaveMagick *magick ) -{ - /* Init to an int just to have something there. It is swapped for an - * int array later. - */ - g_value_init( &magick->delay_gvalue, G_TYPE_INT ); -} - -typedef struct _VipsForeignSaveMagickFile { - VipsForeignSaveMagick parent_object; - - char *filename; - -} VipsForeignSaveMagickFile; - -typedef VipsForeignSaveMagickClass VipsForeignSaveMagickFileClass; - -G_DEFINE_TYPE( VipsForeignSaveMagickFile, vips_foreign_save_magick_file, - vips_foreign_save_magick_get_type() ); - -static int -vips_foreign_save_magick_file_build( VipsObject *object ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); - VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) object; - VipsForeignSaveMagickFile *file = (VipsForeignSaveMagickFile *) object; - - magick->filename = file->filename; - - if( VIPS_OBJECT_CLASS( vips_foreign_save_magick_file_parent_class )-> - build( object ) ) - return( -1 ); - - if( !WriteImages( magick->image_info, magick->images, - magick->image_info->filename, magick->exception ) ) { - magick_inherit_exception( magick->exception, magick->images ); - magick_vips_error( class->nickname, magick->exception ); - - return( -1 ); - } - - return( 0 ); -} - -static void -vips_foreign_save_magick_file_class_init( - VipsForeignSaveMagickFileClass *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 = "magicksave"; - object_class->description = _( "save file with ImageMagick" ); - object_class->build = vips_foreign_save_magick_file_build; - - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveMagickFile, filename ), - NULL ); - -} - -static void -vips_foreign_save_magick_file_init( VipsForeignSaveMagickFile *file ) -{ -} - -typedef struct _VipsForeignSaveMagickBuffer { - VipsForeignSaveMagick parent_object; - - /* Save to a buffer. - */ - VipsArea *buf; - -} VipsForeignSaveMagickBuffer; - -typedef VipsForeignSaveMagickClass VipsForeignSaveMagickBufferClass; - -G_DEFINE_TYPE( VipsForeignSaveMagickBuffer, vips_foreign_save_magick_buffer, - vips_foreign_save_magick_get_type() ); - -static int -vips_foreign_save_magick_buffer_build( VipsObject *object ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); - VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) object; - VipsForeignSaveMagickBuffer *buffer = - (VipsForeignSaveMagickBuffer *) object; - - void *obuf; - size_t olen; - VipsBlob *blob; - - if( VIPS_OBJECT_CLASS( vips_foreign_save_magick_buffer_parent_class )-> - build( object ) ) - return( -1 ); - - if( !(obuf = magick_images_to_blob( magick->image_info, magick->images, - &olen, magick->exception )) ) { - magick_inherit_exception( magick->exception, magick->images ); - magick_vips_error( class->nickname, magick->exception ); - - return( -1 ); - } - - blob = vips_blob_new( (VipsCallbackFn) vips_area_free_cb, obuf, olen ); - g_object_set( buffer, "buffer", blob, NULL ); - vips_area_unref( VIPS_AREA( blob ) ); - - return( 0 ); -} - -static void -vips_foreign_save_magick_buffer_class_init( - VipsForeignSaveMagickBufferClass *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 = "magicksave_buffer"; - object_class->description = _( "save image to magick buffer" ); - object_class->build = vips_foreign_save_magick_buffer_build; - - VIPS_ARG_BOXED( class, "buffer", 1, - _( "Buffer" ), - _( "Buffer to save to" ), - VIPS_ARGUMENT_REQUIRED_OUTPUT, - G_STRUCT_OFFSET( VipsForeignSaveMagickBuffer, buf ), - VIPS_TYPE_BLOB ); - -} - -static void -vips_foreign_save_magick_buffer_init( VipsForeignSaveMagickBuffer *buffer ) -{ -} - -#endif /*ENABLE_MAGICKSAVE*/ - /** * vips_magicksave: (method) * @in: image to save diff --git a/libvips/foreign/vips2magick.c b/libvips/foreign/vips2magick.c new file mode 100644 index 00000000..21eedc82 --- /dev/null +++ b/libvips/foreign/vips2magick.c @@ -0,0 +1,633 @@ +/* save with libMagick + * + * 22/12/17 dlemstra + * 6/2/19 DarthSim + * - fix GraphicsMagick support + * 17/2/19 + * - support ICC, XMP, EXIF, IPTC metadata + * - write with a single call to vips_sink_disc() + * 29/6/19 + * - support "strip" option + * 6/7/19 [deftomat] + * - support array of delays + * 5/8/19 DarthSim + * - support GIF optimization + * 21/4/21 kleisauke + * - include GObject part from magicksave.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 + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include + +#ifdef ENABLE_MAGICKSAVE + +#include "pforeign.h" +#include "magick.h" + +typedef struct _VipsForeignSaveMagick { + VipsForeignSave parent_object; + + /* Parameters. + */ + char *filename; /* NULL during buffer output */ + char *format; + int quality; + gboolean optimize_gif_frames; + gboolean optimize_gif_transparency; + + ImageInfo *image_info; + ExceptionInfo *exception; + char *map; + StorageType storage_type; + Image *images; + Image *current_image; + + int page_height; + GValue delay_gvalue; + int *delays; + int delays_length; + + /* The position of current_image in the output. + */ + VipsRect position; + +} VipsForeignSaveMagick; + +typedef VipsForeignSaveClass VipsForeignSaveMagickClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveMagick, vips_foreign_save_magick, + VIPS_TYPE_FOREIGN_SAVE ); + +static void +vips_foreign_save_magick_dispose( GObject *gobject ) +{ + VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) gobject; + +#ifdef DEBUG + printf( "vips_foreign_save_magick_dispose: %p\n", gobject ); +#endif /*DEBUG*/ + + VIPS_FREE( magick->map ); + VIPS_FREEF( DestroyImageList, magick->images ); + VIPS_FREEF( DestroyImageInfo, magick->image_info ); + VIPS_FREEF( magick_destroy_exception, magick->exception ); + g_value_unset( &magick->delay_gvalue ); + + G_OBJECT_CLASS( vips_foreign_save_magick_parent_class )-> + dispose( gobject ); +} + +/* Move current_image on to the next image we will write. + */ +static int +vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( magick ); + VipsForeignSave *save = (VipsForeignSave *) magick; + VipsImage *im = save->ready; + + Image *image; + int number; + const char *str; + int page_index; + + g_assert( !magick->current_image ); + + if( magick->images == NULL ) { + if( !(image = magick_acquire_image( magick->image_info, + magick->exception )) ) + return( -1 ); + + magick->images = image; + magick->position.top = 0; + magick->position.left = 0; + magick->position.width = im->Xsize; + magick->position.height = magick->page_height; + } + else { + image = GetLastImageInList( magick->images ); + magick_acquire_next_image( magick->image_info, image, + magick->exception ); + if( GetNextImageInList( image ) == NULL ) + return( -1 ); + + image = SyncNextImageInList( image ); + magick->position.top += magick->page_height; + } + + if( !magick_set_image_size( image, + im->Xsize, magick->page_height, magick->exception ) ) { + magick_vips_error( class->nickname, magick->exception ); + return( -1 ); + } + + /* Delay must be converted from milliseconds into centiseconds + * as GIF image requires centiseconds. + */ + if( magick->delays ) { + page_index = magick->position.top / magick->page_height; + if( page_index < magick->delays_length ) + image->delay = + VIPS_RINT( magick->delays[page_index] / 10.0 ); + } + + /* ImageMagick uses iterations like this (at least in gif save): + * 0 - set 0 loops (infinite) + * 1 - don't write the netscape extension block + * 2 - loop once + * 3 - loop twice etc. + */ + if( vips_image_get_typeof( im, "loop" ) && + !vips_image_get_int( im, "loop", &number ) ) { + image->iterations = (size_t) number; + } + else { + /* DEPRECATED "gif-loop" + * + * We have the simple gif meaning, so we must add one unless + * it's zero. + */ + if( vips_image_get_typeof( im, "gif-loop" ) && + !vips_image_get_int( im, "gif-loop", &number ) ) + image->iterations = (size_t) (number ? number + 1 : 0); + } + + if( vips_image_get_typeof( im, "gif-comment" ) && + !vips_image_get_string( im, "gif-comment", &str ) ) + magick_set_property( image, "comment", str, magick->exception ); + + /* libvips keeps animations as a set of independent frames, so we want + * to clear to the background between each one. + */ + image->dispose = BackgroundDispose; + + if( !save->strip && + magick_set_magick_profile( image, im, magick->exception ) ) { + magick_vips_error( class->nickname, magick->exception ); + return( -1 ); + } + + magick->current_image = image; + + return( 0 ); +} + +/* We've written all the pixels to current_image ... finish it off ready to + * move on. + */ +static void +vips_foreign_save_magick_end_image( VipsForeignSaveMagick *magick ) +{ + if( magick->current_image ) { + magick_inherit_exception( magick->exception, + magick->current_image ); + magick->current_image = NULL; + } +} + +/* Another block of pixels have arrived from libvips. + */ +static int +vips_foreign_save_magick_write_block( VipsRegion *region, VipsRect *area, + void *a ) +{ + VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) a; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( magick ); + + VipsRect pixels; + + pixels = region->valid; + do { + VipsRect hit; + void *p; + + if( !magick->current_image && + vips_foreign_save_magick_next_image( magick ) ) + return( -1 ); + + vips_rect_intersectrect( &pixels, &magick->position, &hit ); + p = VIPS_REGION_ADDR( region, hit.left, hit.top ); + if( !magick_import_pixels( magick->current_image, + hit.left, hit.top - magick->position.top, + hit.width, hit.height, + magick->map, magick->storage_type, + p, + magick->exception ) ) { + magick_vips_error( class->nickname, + magick->exception ); + return( -1 ); + } + + /* Have we filled the page. + */ + if( VIPS_RECT_BOTTOM( &hit ) == + VIPS_RECT_BOTTOM( &magick->position ) ) + vips_foreign_save_magick_end_image( magick ); + + pixels.top += hit.height; + pixels.height -= hit.height; + } while( pixels.height > 0 ); + + return( 0 ); +} + +static int +vips_foreign_save_magick_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) object; + + VipsImage *im; + +#ifdef DEBUG + printf( "vips_foreign_save_magick_build: %p\n", object ); +#endif /*DEBUG*/ + + if( VIPS_OBJECT_CLASS( vips_foreign_save_magick_parent_class )-> + build( object ) ) + return( -1 ); + + magick_genesis(); + + /* The image to save. + */ + im = save->ready; + + magick->exception = magick_acquire_exception(); + magick->image_info = CloneImageInfo( NULL ); + + switch( im->BandFmt ) { + case VIPS_FORMAT_UCHAR: + magick->storage_type = CharPixel; + break; + + case VIPS_FORMAT_USHORT: + magick->storage_type = ShortPixel; + break; + + case VIPS_FORMAT_UINT: + magick->storage_type = LongPixel; + break; + + case VIPS_FORMAT_FLOAT: + magick->storage_type = FloatPixel; + break; + + case VIPS_FORMAT_DOUBLE: + magick->storage_type = DoublePixel; + break; + + default: + vips_error( class->nickname, + "%s", _( "unsupported image format" ) ); + return( -1 ); + } + + switch( im->Bands ) { + case 1: + magick->map = g_strdup( "I" ); + break; + + case 2: + magick->map = g_strdup( "IA" ); + break; + + case 3: + magick->map = g_strdup( "RGB" ); + break; + + case 4: + if( im->Type == VIPS_INTERPRETATION_CMYK ) + magick->map = g_strdup( "CMYK" ); + else + magick->map = g_strdup( "RGBA" ); + break; + + case 5: + magick->map = g_strdup( "CMYKA" ); + break; + + default: + vips_error( class->nickname, + "%s", _( "unsupported number of image bands" ) ); + return( -1 ); + } + + if( magick->format ) { + vips_strncpy( magick->image_info->magick, + magick->format, MaxPathExtent ); + if( magick->filename ) + (void) vips_snprintf( magick->image_info->filename, + MaxPathExtent, "%s:%s", + magick->format, magick->filename ); + } + else if( magick->filename ) { + vips_strncpy( magick->image_info->filename, + magick->filename, MaxPathExtent ); + } + + if( magick->quality > 0 ) + magick->image_info->quality = magick->quality; + + magick->page_height = vips_image_get_page_height( im ); + + /* Get as a gvalue so we can keep a ref to the delay array while we + * need it. + */ + if( vips_image_get_typeof( im, "delay" ) ) { + g_value_unset( &magick->delay_gvalue ); + if( vips_image_get( im, "delay", &magick->delay_gvalue ) ) + return( -1 ); + magick->delays = vips_value_get_array_int( + &magick->delay_gvalue, &magick->delays_length ); + } + + if( vips_sink_disc( im, + vips_foreign_save_magick_write_block, magick ) ) + return( -1 ); + + if( magick->optimize_gif_frames ) { + if( !magick_optimize_image_layers( &magick->images, + magick->exception ) ) { + magick_inherit_exception( magick->exception, + magick->images ); + magick_vips_error( class->nickname, magick->exception ); + + return( -1 ); + } + } + + if( magick->optimize_gif_transparency ) { + if( !magick_optimize_image_transparency( magick->images, + magick->exception ) ) { + magick_inherit_exception( magick->exception, + magick->images ); + magick_vips_error( class->nickname, magick->exception ); + + return( -1 ); + } + } + + return( 0 ); +} + +/* We could call into libMagick and discover what save formats it supports, but + * that would mean starting up libMagick on libvips init, and that would add a + * lot of time. + * + * Instead, just list the commonly-used formats that all libMagicks support and + * that libvips does not. + */ +static const char *vips__save_magick_suffs[] = { ".gif", ".bmp", NULL }; + +/* Save a bit of typing. + */ +#define UC VIPS_FORMAT_UCHAR +#define US VIPS_FORMAT_USHORT +#define UI VIPS_FORMAT_UINT +#define F VIPS_FORMAT_FLOAT +#define D VIPS_FORMAT_DOUBLE + +static int bandfmt_magick[10] = { +/* UC C US S UI I F X D DX */ + UC, UC, US, US, UI, UI, F, F, D, D +}; + +static void +vips_foreign_save_magick_class_init( VipsForeignSaveMagickClass *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_magick_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "magicksave_base"; + object_class->description = _( "save with ImageMagick" ); + object_class->build = vips_foreign_save_magick_build; + + /* We need to be well to the back of the queue since vips's + * dedicated savers are usually preferable. + */ + foreign_class->priority = -100; + foreign_class->suffs = vips__save_magick_suffs; + + save_class->saveable = VIPS_SAVEABLE_ANY; + save_class->format_table = bandfmt_magick; + + VIPS_ARG_STRING( class, "format", 2, + _( "Format" ), + _( "Format to save in" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveMagick, format ), + NULL ); + + VIPS_ARG_INT( class, "quality", 3, + _( "Quality" ), + _( "Quality to use" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveMagick, quality ), + 0, 100, 0 ); + + VIPS_ARG_BOOL( class, "optimize_gif_frames", 4, + _( "Optimize_gif_frames" ), + _( "Apply GIF frames optimization" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveMagick, optimize_gif_frames ), + FALSE ); + + VIPS_ARG_BOOL( class, "optimize_gif_transparency", 5, + _( "Optimize_gif_transparency" ), + _( "Apply GIF transparency optimization" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveMagick, + optimize_gif_transparency ), + FALSE ); +} + +static void +vips_foreign_save_magick_init( VipsForeignSaveMagick *magick ) +{ + /* Init to an int just to have something there. It is swapped for an + * int array later. + */ + g_value_init( &magick->delay_gvalue, G_TYPE_INT ); +} + +typedef struct _VipsForeignSaveMagickFile { + VipsForeignSaveMagick parent_object; + + char *filename; + +} VipsForeignSaveMagickFile; + +typedef VipsForeignSaveMagickClass VipsForeignSaveMagickFileClass; + +G_DEFINE_TYPE( VipsForeignSaveMagickFile, vips_foreign_save_magick_file, + vips_foreign_save_magick_get_type() ); + +static int +vips_foreign_save_magick_file_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) object; + VipsForeignSaveMagickFile *file = (VipsForeignSaveMagickFile *) object; + + magick->filename = file->filename; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_magick_file_parent_class )-> + build( object ) ) + return( -1 ); + + if( !WriteImages( magick->image_info, magick->images, + magick->image_info->filename, magick->exception ) ) { + magick_inherit_exception( magick->exception, magick->images ); + magick_vips_error( class->nickname, magick->exception ); + + return( -1 ); + } + + return( 0 ); +} + +static void +vips_foreign_save_magick_file_class_init( + VipsForeignSaveMagickFileClass *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 = "magicksave"; + object_class->description = _( "save file with ImageMagick" ); + object_class->build = vips_foreign_save_magick_file_build; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveMagickFile, filename ), + NULL ); + +} + +static void +vips_foreign_save_magick_file_init( VipsForeignSaveMagickFile *file ) +{ +} + +typedef struct _VipsForeignSaveMagickBuffer { + VipsForeignSaveMagick parent_object; + + /* Save to a buffer. + */ + VipsArea *buf; + +} VipsForeignSaveMagickBuffer; + +typedef VipsForeignSaveMagickClass VipsForeignSaveMagickBufferClass; + +G_DEFINE_TYPE( VipsForeignSaveMagickBuffer, vips_foreign_save_magick_buffer, + vips_foreign_save_magick_get_type() ); + +static int +vips_foreign_save_magick_buffer_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsForeignSaveMagick *magick = (VipsForeignSaveMagick *) object; + VipsForeignSaveMagickBuffer *buffer = + (VipsForeignSaveMagickBuffer *) object; + + void *obuf; + size_t olen; + VipsBlob *blob; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_magick_buffer_parent_class )-> + build( object ) ) + return( -1 ); + + if( !(obuf = magick_images_to_blob( magick->image_info, magick->images, + &olen, magick->exception )) ) { + magick_inherit_exception( magick->exception, magick->images ); + magick_vips_error( class->nickname, magick->exception ); + + return( -1 ); + } + + blob = vips_blob_new( (VipsCallbackFn) vips_area_free_cb, obuf, olen ); + g_object_set( buffer, "buffer", blob, NULL ); + vips_area_unref( VIPS_AREA( blob ) ); + + return( 0 ); +} + +static void +vips_foreign_save_magick_buffer_class_init( + VipsForeignSaveMagickBufferClass *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 = "magicksave_buffer"; + object_class->description = _( "save image to magick buffer" ); + object_class->build = vips_foreign_save_magick_buffer_build; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to save to" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsForeignSaveMagickBuffer, buf ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_save_magick_buffer_init( VipsForeignSaveMagickBuffer *buffer ) +{ +} + +#endif /*ENABLE_MAGICKSAVE*/ diff --git a/libvips/module/magick.c b/libvips/module/magick.c new file mode 100644 index 00000000..8c720713 --- /dev/null +++ b/libvips/module/magick.c @@ -0,0 +1,92 @@ +/* magick as a dynamically loadable module + * + * 21/4/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_MAGICK6) || defined (HAVE_MAGICK7)) && defined(MAGICK_MODULE) + +/* This is called on module load. + */ +G_MODULE_EXPORT const gchar * +g_module_check_init( GModule *module ) +{ +#ifdef DEBUG + printf( "vips_magick: module init\n" ); +#endif /*DEBUG*/ + + extern GType vips_foreign_load_magick_file_get_type( void ); + extern GType vips_foreign_load_magick_buffer_get_type( void ); + extern GType vips_foreign_load_magick7_file_get_type( void ); + extern GType vips_foreign_load_magick7_buffer_get_type( void ); + extern GType vips_foreign_save_magick_file_get_type( void ); + extern GType vips_foreign_save_magick_buffer_get_type( void ); + +#ifdef ENABLE_MAGICKLOAD +#ifdef HAVE_MAGICK6 + vips_foreign_load_magick_file_get_type(); + vips_foreign_load_magick_buffer_get_type(); +#endif /*HAVE_MAGICK6*/ + +#ifdef HAVE_MAGICK7 + vips_foreign_load_magick7_file_get_type(); + vips_foreign_load_magick7_buffer_get_type(); +#endif /*HAVE_MAGICK7*/ +#endif /*ENABLE_MAGICKLOAD*/ + +#ifdef ENABLE_MAGICKSAVE + vips_foreign_save_magick_file_get_type(); + vips_foreign_save_magick_buffer_get_type(); +#endif /*ENABLE_MAGICKSAVE*/ + + /* We can't be unloaded, there would be chaos. + */ + g_module_make_resident( module ); + + return( NULL ); +} + +#endif /*(defined(HAVE_MAGICK6) || defined (HAVE_MAGICK7)) && defined(MAGICK_MODULE)*/