From 34c0f31643e797bf0b1af0e0029a73b86ccab0f1 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 21 Apr 2021 17:27:05 +0200 Subject: [PATCH] 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)*/