From 0e844c822108ef6f850d483d759b61ba0daf59d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janko=20Marohni=C4=87?= Date: Mon, 26 Mar 2018 23:30:10 +0200 Subject: [PATCH 01/36] Add "(out)" to "out" parameter in vips_addalpha() --- libvips/conversion/bandjoin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/conversion/bandjoin.c b/libvips/conversion/bandjoin.c index c2ce0505..f5327a48 100644 --- a/libvips/conversion/bandjoin.c +++ b/libvips/conversion/bandjoin.c @@ -501,7 +501,7 @@ vips_bandjoin_const1( VipsImage *in, VipsImage **out, double c, ... ) /* vips_addalpha: * @in: input image - * @out: output image + * @out: (out): output image * @...: %NULL-terminated list of optional named arguments * * Append an alpha channel. From 85615a7fbd8a6571eb0e1c8f18b4dd6f36da3474 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 29 Mar 2018 09:18:36 +0100 Subject: [PATCH 02/36] gtk-doc annotation update --- libvips/conversion/bandjoin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/conversion/bandjoin.c b/libvips/conversion/bandjoin.c index f5327a48..7f284907 100644 --- a/libvips/conversion/bandjoin.c +++ b/libvips/conversion/bandjoin.c @@ -499,7 +499,7 @@ vips_bandjoin_const1( VipsImage *in, VipsImage **out, double c, ... ) return( result ); } -/* vips_addalpha: +/* vips_addalpha: (method) * @in: input image * @out: (out): output image * @...: %NULL-terminated list of optional named arguments From 9c2ae52820cef5ca8e71fd2c338d9277a5458eed Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Mar 2018 11:59:57 +0100 Subject: [PATCH 03/36] start adding pdfium tests though they fail with link problems --- configure.ac | 16 ++++++ libvips/iofuncs/vips.c | 13 ++++- m4/pdfium.m4 | 127 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 m4/pdfium.m4 diff --git a/configure.ac b/configure.ac index cb71bb7e..e7f76f67 100644 --- a/configure.ac +++ b/configure.ac @@ -751,6 +751,21 @@ if test x"$with_OpenEXR" != "xno"; then ) fi +# pdfium +AC_ARG_WITH([pdfium], + AS_HELP_STRING([--without-pdfium], [build without pdfium (default: test)])) + +if test x"$with_pdfium" != x"no"; then + FIND_PDFIUM([ + AC_MSG_WARN([PDFium found, disabling poppler]) + with_pdfium=yes + with_poppler=no + ],[ + AC_MSG_WARN([PDFium not found]) + with_pdfium=no + ]) +fi + # poppler AC_ARG_WITH([poppler], AS_HELP_STRING([--without-poppler], [build without poppler (default: test)])) @@ -1253,6 +1268,7 @@ file import with OpenEXR: $with_OpenEXR file import with OpenSlide: $with_openslide (requires openslide-3.3.0 or later) file import with matio: $with_matio +PDF import with PDFium $with_pdfium PDF import with poppler-glib: $with_poppler (requires poppler-glib 0.16.0 or later) SVG import with librsvg-2.0: $with_rsvg diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index 49e6614f..25611ebb 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -69,12 +69,12 @@ #include #include #include -#include #include +#include +#include #ifdef HAVE_SYS_FILE_H #include #endif /*HAVE_SYS_FILE_H*/ -#include #ifdef HAVE_UNISTD_H #include #endif /*HAVE_UNISTD_H*/ @@ -190,6 +190,15 @@ vips__open_image_write( const char *filename, gboolean temp ) flags |= _O_TEMPORARY; #endif /*_O_TEMPORARY*/ +#ifdef O_TMPFILE + /* Added in linux 3.11, but still not available in most glibc. We + * unlink the file ourselves anyway, so it doesn't matter if this + * fails. + */ + if( temp ) + flags |= O_TMPFILE; +#endif /*O_TMPFILE*/ + if( (fd = vips_tracked_open( filename, flags, 0666 )) < 0 ) { vips_error_system( errno, "VipsImage", _( "unable to write to \"%s\"" ), diff --git a/m4/pdfium.m4 b/m4/pdfium.m4 new file mode 100644 index 00000000..bc0e1041 --- /dev/null +++ b/m4/pdfium.m4 @@ -0,0 +1,127 @@ +dnl From FIND_MOTIF and ACX_PTHREAD, without much understanding +dnl +dnl FIND_PDFIUM[ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]] +dnl --------------------------------------------------- +dnl +dnl Find pdfium libraries and headers +dnl +dnl Put -I stuff in PDFIUM_INCLUDES +dnl Put PDFium objects in PDFIUM_LIBS (add this to link line untouched!) +dnl Define HAVE_PDFIUM if found +dnl +AC_DEFUN([FIND_PDFIUM], [ +AC_REQUIRE([AC_PATH_XTRA]) + +ZLIB_INCLUDES="" +ZLIB_LIBS="" + +AC_ARG_WITH(pdfium, + AS_HELP_STRING([--without-pdfium], [build without pdfium (default: test)])) +# Treat --without-pdfium like --without-pdfium-includes +# --without-pdfium-libraries +if test "$with_pdfium" = "no"; then + PDFIUM_INCLUDES=no + PDFIUM_LIBS=no +fi + +# pdfium has a huge set of libraries, including its own jpeg ... we can't use +# -L/-l as usual, since the libjpeg.a here will break our -ljpeg later. +# Instead, just add these .a files plus their path to the list of objects to +# link. +pdfium_objects="libbigint.a libchrome_zlib.a libfdrm.a libformfiller.a \ + libfpdfapi.a libfpdfdoc.a libfpdftext.a libfx_agg.a libfxcodec.a \ + libfxcrt.a libfx_freetype.a libfxge.a libfxjs.a libfx_lcms2.a \ + libfx_libopenjpeg.a libfx_lpng.a libgmock_main.a libimage_diff.a \ + libjpeg.a libminizip.a libpdfium.a libpwl.a libsimd.a \ + libsimd_asm.a libyasm_utils.a libzlib_x86_simd.a" + +AC_ARG_WITH(pdfium-includes, + AS_HELP_STRING([--with-pdfium-includes=DIR], [pdfium includes are in DIR]), + PDFIUM_INCLUDES="-I$withval") +AC_ARG_WITH(pdfium-libraries, + AS_HELP_STRING([--with-pdfium-libraries=DIR], [pdfium libraries are in DIR]), + PDFIUM_LIBS="$withval") + +AC_MSG_CHECKING(for PDFIUM) + +# Look for fpdfview.h ... this is a documented header, so it should be a good +# target ... it won't be in the standard search path, but try $PREFIX +if test "$PDFIUM_INCLUDES" = ""; then + pdfium_save_CPPFLAGS="$CPPFLAGS" + + CPPFLAGS="-I${prefix}/include $CPPFLAGS" + + AC_TRY_COMPILE([#include ],[int a;],[ + PDFIUM_INCLUDES="-I${prefix}/include" + ], [ + PDFIUM_INCLUDES="no" + ]) + + CPPFLAGS="$pdfium_save_CPPFLAGS" +fi + +# Now for the libraries ... if there's nothing set, try $PREFIX/lib +if test "$PDFIUM_LIBS" = ""; then + pdfium_save_LIBS="$LIBS" + pdfium_save_CPPFLAGS="$CPPFLAGS" + + for i in $pdfium_objects; do + LIBS="$prefix/lib/$i $LIBS" + done + CPPFLAGS="$PDFIUM_INCLUDES $CPPFLAGS" + + AC_TRY_LINK([#include ], + [FPDF_DOCUMENT doc; doc = FPDF_LoadDocument("", "")], [ + echo link success + PDFIUM_LIBS="${prefix}/lib" + ], [ + echo link fail + PDFIUM_LIBS=no + ]) + + LIBS="$pdfium_save_LIBS" + CPPFLAGS="$pdfium_save_CPPFLAGS" +fi + +# Print a helpful message +pdfium_libraries_result="$PDFIUM_LIBS" +pdfium_includes_result="$PDFIUM_INCLUDES" + +if test x"$pdfium_libraries_result" = x""; then + pdfium_libraries_result="in default path" +fi +if test x"$pdfium_includes_result" = x""; then + pdfium_includes_result="in default path" +fi + +if test "$pdfium_libraries_result" = "no"; then + pdfium_libraries_result="(none)" +fi +if test "$pdfium_includes_result" = "no"; then + pdfium_includes_result="(none)" +fi + +AC_MSG_RESULT([libraries $pdfium_libraries_result, headers $pdfium_includes_result]) + +if test x"$PDFIUM_LIBS" != x"no"; then + dir="$PDFIUM_LIBS" + PDFIUM_LIBS="" + for i in $pdfium_objects; do + PDFIUM_LIBS="$PDFIUM_LIBS $dir/$i" + done +fi + +AC_SUBST(PDFIUM_LIBS) +AC_SUBST(PDFIUM_INCLUDES) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "$PDFIUM_INCLUDES" != "no" && test "$PDFIUM_LIBS" != "no"; then + AC_DEFINE(HAVE_PDFIUM,1,[Define if you have pdfium libraries and header files.]) + $1 +else + PDFIUM_INCLUDES="" + PDFIUM_LIBS="" + $2 +fi + +])dnl From 38f459711b2f924a6b2fb64989d7f69dda2fa7de Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Mar 2018 22:17:43 +0100 Subject: [PATCH 04/36] detects pdfium correctly and builds without anything else breaking --- configure.ac | 16 +++++++++------- m4/pdfium.m4 | 34 ++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/configure.ac b/configure.ac index e7f76f67..62da56c1 100644 --- a/configure.ac +++ b/configure.ac @@ -757,13 +757,15 @@ AC_ARG_WITH([pdfium], if test x"$with_pdfium" != x"no"; then FIND_PDFIUM([ - AC_MSG_WARN([PDFium found, disabling poppler]) - with_pdfium=yes - with_poppler=no - ],[ - AC_MSG_WARN([PDFium not found]) - with_pdfium=no - ]) + if test x"$with_poppler" != x"no"; then + AC_MSG_WARN([PDFium found, disabling poppler]) + with_poppler=no + fi + with_pdfium=yes + ],[ + with_pdfium=no + ] + ) fi # poppler diff --git a/m4/pdfium.m4 b/m4/pdfium.m4 index bc0e1041..e7ec5913 100644 --- a/m4/pdfium.m4 +++ b/m4/pdfium.m4 @@ -28,12 +28,10 @@ fi # -L/-l as usual, since the libjpeg.a here will break our -ljpeg later. # Instead, just add these .a files plus their path to the list of objects to # link. -pdfium_objects="libbigint.a libchrome_zlib.a libfdrm.a libformfiller.a \ - libfpdfapi.a libfpdfdoc.a libfpdftext.a libfx_agg.a libfxcodec.a \ - libfxcrt.a libfx_freetype.a libfxge.a libfxjs.a libfx_lcms2.a \ - libfx_libopenjpeg.a libfx_lpng.a libgmock_main.a libimage_diff.a \ - libjpeg.a libminizip.a libpdfium.a libpwl.a libsimd.a \ - libsimd_asm.a libyasm_utils.a libzlib_x86_simd.a" +pdfium_objects="libpdfium.a libfpdfapi.a libfxge.a libfpdfdoc.a \ + libfxcrt.a libfx_agg.a libfxcodec.a libfx_lpng.a libfx_libopenjpeg.a \ + libfx_lcms2.a libfx_freetype.a libjpeg.a libfdrm.a libpwl.a \ + libbigint.a libformfiller.a" AC_ARG_WITH(pdfium-includes, AS_HELP_STRING([--with-pdfium-includes=DIR], [pdfium includes are in DIR]), @@ -52,10 +50,11 @@ if test "$PDFIUM_INCLUDES" = ""; then CPPFLAGS="-I${prefix}/include $CPPFLAGS" AC_TRY_COMPILE([#include ],[int a;],[ - PDFIUM_INCLUDES="-I${prefix}/include" - ], [ - PDFIUM_INCLUDES="no" - ]) + PDFIUM_INCLUDES="-I${prefix}/include" + ], [ + PDFIUM_INCLUDES="no" + ] + ) CPPFLAGS="$pdfium_save_CPPFLAGS" fi @@ -68,16 +67,16 @@ if test "$PDFIUM_LIBS" = ""; then for i in $pdfium_objects; do LIBS="$prefix/lib/$i $LIBS" done + LIBS="$LIBS -L$prefix/lib -lc++ -licuuc -lm -lpthread" CPPFLAGS="$PDFIUM_INCLUDES $CPPFLAGS" AC_TRY_LINK([#include ], [FPDF_DOCUMENT doc; doc = FPDF_LoadDocument("", "")], [ - echo link success - PDFIUM_LIBS="${prefix}/lib" + PDFIUM_LIBS="${prefix}/lib" ], [ - echo link fail - PDFIUM_LIBS=no - ]) + PDFIUM_LIBS=no + ] + ) LIBS="$pdfium_save_LIBS" CPPFLAGS="$pdfium_save_CPPFLAGS" @@ -109,6 +108,8 @@ if test x"$PDFIUM_LIBS" != x"no"; then for i in $pdfium_objects; do PDFIUM_LIBS="$PDFIUM_LIBS $dir/$i" done + # needs -lm -lpthread too, but they will be added by other packages + PDFIUM_LIBS="$PDFIUM_LIBS -L$dir/lib -lc++ -licuuc" fi AC_SUBST(PDFIUM_LIBS) @@ -116,7 +117,8 @@ AC_SUBST(PDFIUM_INCLUDES) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "$PDFIUM_INCLUDES" != "no" && test "$PDFIUM_LIBS" != "no"; then - AC_DEFINE(HAVE_PDFIUM,1,[Define if you have pdfium libraries and header files.]) + AC_DEFINE(HAVE_PDFIUM,1, + [Define if you have pdfium libraries and header files.]) $1 else PDFIUM_INCLUDES="" From e686614f2ce4a130ecb8f264d100410ac55a7507 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 1 Apr 2018 10:32:48 +0100 Subject: [PATCH 05/36] drop incompatible profiles from save libpng has started throwing hard errors if the profile does not match the image -- this can happen all the time with perofiles inherited from images that have been processed. Test profiles before save and drop them (with a warning) if they are incompatible with the image. --- ChangeLog | 1 + libvips/colour/icc_transform.c | 37 ++++++++++++++++++++++++++++++++++ libvips/foreign/foreign.c | 17 ++++++++++++++++ libvips/foreign/vipspng.c | 3 ++- libvips/include/vips/colour.h | 2 ++ 5 files changed, 59 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 2f015aed..ff184274 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,7 @@ - add vips_rotate() ... a convenience method for vips_similarity() - svgload was missing is_a [lovell] - better header sniffing for small files +- drop incompatible ICC profiles before save 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges, thanks Adrià diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index 7b9d0d53..67053214 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -1226,6 +1226,36 @@ vips_icc_ac2rc( VipsImage *in, VipsImage **out, const char *profile_filename ) return( 0 ); } +/* TRUE if a profile is compatible with an image. + */ +gboolean +vips_icc_is_compatible_profile( VipsImage *image, + void *data, size_t data_length ) +{ + cmsHPROFILE profile; + + if( !(profile = cmsOpenProfileFromMem( data, data_length )) ) { + g_warning( "%s", _( "corrupt profile" ) ); + return( FALSE ); + } + + if( vips_image_expected_bands( image ) != + vips_icc_profile_needs_bands( profile ) ) { + VIPS_FREEF( cmsCloseProfile, profile ); + g_warning( "%s", + _( "profile incompatible with image" ) ); + return( FALSE ); + } + if( vips_image_expected_sig( image ) != cmsGetColorSpace( profile ) ) { + VIPS_FREEF( cmsCloseProfile, profile ); + g_warning( "%s", + _( "profile colourspace differs from image" ) ); + return( FALSE ); + } + + return( TRUE ); +} + #else /*!HAVE_LCMS2*/ #include @@ -1245,6 +1275,13 @@ vips_icc_ac2rc( VipsImage *in, VipsImage **out, const char *profile_filename ) return( -1 ); } +gboolean +vips_icc_is_compatible_profile( VipsImage *image, + void *data, size_t data_length ) +{ + return( TRUE ); +} + #endif /*HAVE_LCMS*/ /** diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 61c87e03..f32302b9 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -22,6 +22,8 @@ * - META_SEQ support moved here * 5/3/18 * - block _start if one start fails, see #893 + * 1/4/18 + * - drop incompatible ICC profiles before save */ /* @@ -1504,6 +1506,21 @@ vips__foreign_convert_saveable( VipsImage *in, VipsImage **ready, in = out; } + /* Some format libraries, like libpng, will throw a hard error if the + * profile is inappropriate for this image type. With profiles inherited + * from a source image, this can happen all the time, so we + * want to just drop the profile in this case. + */ + if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { + void *data; + size_t length; + + if( !vips_image_get_blob( in, VIPS_META_ICC_NAME, + &data, &length ) && + !vips_icc_is_compatible_profile( in, data, length ) ) + vips_image_remove( in, VIPS_META_ICC_NAME ); + } + *ready = in; return( 0 ); diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index b645da3a..a7aaf963 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -992,7 +992,8 @@ write_vips( Write *write, /* If we're an intel byte order CPU and this is a 16bit image, we need * to swap bytes. */ - if( bit_depth > 8 && !vips_amiMSBfirst() ) + if( bit_depth > 8 && + !vips_amiMSBfirst() ) png_set_swap( write->pPng ); if( interlace ) diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index 77404b9c..7978d325 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -177,6 +177,8 @@ int vips_icc_export( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_icc_ac2rc( VipsImage *in, VipsImage **out, const char *profile_filename ); +gboolean vips_icc_is_compatible_profile( VipsImage *image, + void *data, size_t data_length ); int vips_dE76( VipsImage *left, VipsImage *right, VipsImage **out, ... ) __attribute__((sentinel)); From 0dd6b095aa1366e20709e29b64da928299b63a6c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 3 Apr 2018 14:36:43 +0100 Subject: [PATCH 06/36] more conservative hasalpha The result of hasalpha is used to turn on things like premultiplication, so we should be rather conservative about when we signal this. We don't want to premultiply things that should not be premultiplied. Check Type as well as bands. See: https://github.com/jcupitt/libvips/issues/918 --- ChangeLog | 1 + libvips/iofuncs/image.c | 45 ++++++++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index ff184274..83949abd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,7 @@ - svgload was missing is_a [lovell] - better header sniffing for small files - drop incompatible ICC profiles before save +- better hasalpha rules 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges, thanks Adrià diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index 411e0b15..9045449f 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -12,8 +12,10 @@ * - vips_image_write() does not ref input for non-partial images * 29/10/16 * - add vips_image_hasalpha() - * 11/10/1 + * 11/10/17 * - more severing for vips_image_write() + * 3/4/18 + * - better rules for hasalpha */ /* @@ -2866,18 +2868,47 @@ vips_image_ispartial( VipsImage *image ) * vips_image_hasalpha: (method) * @image: image to check * - * libvips assumes an image has an alpha if it has two bands (ie. it is a - * monochrome image with an extra band), if it has four bands (unless it's been - * tagged as CMYK), or if it has more than four bands. + * Look at an image's interpretation and see if it has extra alpha bands. For + * example, a 4-band #VIPS_INTERPRETATION_RGB would, but a six-band + * #VIPS_INTERPRETATION_MULTIBAND would not. * * Return %TRUE if @image has an alpha channel. */ gboolean vips_image_hasalpha( VipsImage *image ) { - return( image->Bands == 2 || - (image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) || - image->Bands > 4 ); + /* The result of hasalpha is used to turn on things like + * premultiplication, so we are rather conservative about when we + * signal this. We don't want to premultiply things that should not be + * premultiplied. + */ + switch( image->Type ) { + case VIPS_INTERPRETATION_B_W: + case VIPS_INTERPRETATION_GREY16: + return( image->Bands > 1 ); + + case VIPS_INTERPRETATION_RGB: + case VIPS_INTERPRETATION_CMC: + case VIPS_INTERPRETATION_LCH: + case VIPS_INTERPRETATION_LABS: + case VIPS_INTERPRETATION_sRGB: + case VIPS_INTERPRETATION_YXY: + case VIPS_INTERPRETATION_XYZ: + case VIPS_INTERPRETATION_LAB: + case VIPS_INTERPRETATION_RGB16: + case VIPS_INTERPRETATION_scRGB: + case VIPS_INTERPRETATION_HSV: + return( image->Bands > 3 ); + + case VIPS_INTERPRETATION_CMYK: + return( image->Bands > 4 ); + + default: + /* We can't really infer anything about bands from things like + * HISTOGRAM or FOURIER. + */ + return( FALSE ); + } } /** From aebb8af803a45b3b09f1b5cb9ebbd93787c36f8d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Apr 2018 09:22:57 +0100 Subject: [PATCH 07/36] create funcs always make MULTIBAND Before, they could make B_W for one-band output. This caused problems with (for example) two black image bandjoined: the second band then looked like an alpha to hasalpha() and enabled premultiply/unpremultiply for operations like affine. Now, it's always MULTIBAND. This is the generic multiband image type, so you don't get any unexpected alpha handling. --- ChangeLog | 1 + libvips/create/black.c | 6 ++++-- libvips/create/gaussmat.c | 3 ++- libvips/create/gaussnoise.c | 2 +- libvips/create/logmat.c | 3 ++- libvips/create/perlin.c | 2 +- libvips/create/point.c | 7 +++---- libvips/create/text.c | 3 ++- libvips/create/worley.c | 3 ++- 9 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index 83949abd..2cde6127 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,7 @@ - better header sniffing for small files - drop incompatible ICC profiles before save - better hasalpha rules +- create funcs always make MULTIBAND (ie. no alpha) 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges, thanks Adrià diff --git a/libvips/create/black.c b/libvips/create/black.c index c613a154..33c4ab1c 100644 --- a/libvips/create/black.c +++ b/libvips/create/black.c @@ -15,6 +15,9 @@ * - gtkdoc * 31/10/11 * - redo as a class + * 3/4/18 + * - always write MULTIBAND, otherwise when we join up these things it'll + * look like we have an alpha */ /* @@ -97,8 +100,7 @@ vips_black_build( VipsObject *object ) vips_image_init_fields( create->out, black->width, black->height, black->bands, VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, - black->bands == 1 ? - VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_MULTIBAND, + VIPS_INTERPRETATION_MULTIBAND, 1.0, 1.0 ); vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); diff --git a/libvips/create/gaussmat.c b/libvips/create/gaussmat.c index fa67c305..4ac21562 100644 --- a/libvips/create/gaussmat.c +++ b/libvips/create/gaussmat.c @@ -134,7 +134,8 @@ vips_gaussmat_build( VipsObject *object ) vips_image_init_fields( create->out, width, height, 1, - VIPS_FORMAT_DOUBLE, VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, + VIPS_FORMAT_DOUBLE, VIPS_CODING_NONE, + VIPS_INTERPRETATION_MULTIBAND, 1.0, 1.0 ); vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); diff --git a/libvips/create/gaussnoise.c b/libvips/create/gaussnoise.c index 1566e39a..5ccf5510 100644 --- a/libvips/create/gaussnoise.c +++ b/libvips/create/gaussnoise.c @@ -134,7 +134,7 @@ vips_gaussnoise_build( VipsObject *object ) vips_image_init_fields( create->out, gaussnoise->width, gaussnoise->height, 1, VIPS_FORMAT_FLOAT, VIPS_CODING_NONE, - VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + VIPS_INTERPRETATION_MULTIBAND, 1.0, 1.0 ); vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); diff --git a/libvips/create/logmat.c b/libvips/create/logmat.c index 5f15b3ea..43d753c7 100644 --- a/libvips/create/logmat.c +++ b/libvips/create/logmat.c @@ -153,7 +153,8 @@ vips_logmat_build( VipsObject *object ) vips_image_init_fields( create->out, width, height, 1, - VIPS_FORMAT_DOUBLE, VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, + VIPS_FORMAT_DOUBLE, VIPS_CODING_NONE, + VIPS_INTERPRETATION_MULTIBAND, 1.0, 1.0 ); vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); diff --git a/libvips/create/perlin.c b/libvips/create/perlin.c index 5181f365..a6651f90 100644 --- a/libvips/create/perlin.c +++ b/libvips/create/perlin.c @@ -249,7 +249,7 @@ vips_perlin_build( VipsObject *object ) vips_image_init_fields( create->out, perlin->width, perlin->height, 1, perlin->uchar ? VIPS_FORMAT_UCHAR : VIPS_FORMAT_FLOAT, - VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, + VIPS_CODING_NONE, VIPS_INTERPRETATION_MULTIBAND, 1.0, 1.0 ); vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); diff --git a/libvips/create/point.c b/libvips/create/point.c index 06e14229..f5f00841 100644 --- a/libvips/create/point.c +++ b/libvips/create/point.c @@ -109,10 +109,9 @@ vips_point_build( VipsObject *object ) return( -1 ); in = t[2]; - /* uchar mode always does B_W. We don't want FOURIER or - * whatever in this case. + /* We don't want FOURIER or whatever in this case. */ - in->Type = VIPS_INTERPRETATION_B_W; + in->Type = VIPS_INTERPRETATION_MULTIBAND; } if( vips_image_write( in, create->out ) ) @@ -137,7 +136,7 @@ vips_point_class_init( VipsPointClass *class ) class->point = NULL; class->min = -1.0; class->max = 1.0; - class->interpretation = VIPS_INTERPRETATION_B_W; + class->interpretation = VIPS_INTERPRETATION_MULTIBAND; VIPS_ARG_INT( class, "width", 2, _( "Width" ), diff --git a/libvips/create/text.c b/libvips/create/text.c index 8d26ed10..e8631638 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -379,7 +379,8 @@ vips_text_build( VipsObject *object ) vips_image_init_fields( create->out, text->bitmap.width, text->bitmap.rows, 1, - VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, + VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, + VIPS_INTERPRETATION_MULTIBAND, 1.0, 1.0 ); vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); diff --git a/libvips/create/worley.c b/libvips/create/worley.c index 520fdd47..e2cdf424 100644 --- a/libvips/create/worley.c +++ b/libvips/create/worley.c @@ -281,7 +281,8 @@ vips_worley_build( VipsObject *object ) vips_image_init_fields( create->out, worley->width, worley->height, 1, - VIPS_FORMAT_FLOAT, VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, + VIPS_FORMAT_FLOAT, VIPS_CODING_NONE, + VIPS_INTERPRETATION_MULTIBAND, 1.0, 1.0 ); vips_image_pipelinev( create->out, VIPS_DEMAND_STYLE_ANY, NULL ); From 632bce3c782c6725d460f784882cc35d8083d3f7 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Apr 2018 16:25:35 +0100 Subject: [PATCH 08/36] reduce stack use for radsave fixes a crash on very low stack libcs like musl --- ChangeLog | 1 + libvips/foreign/radiance.c | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 482b0d4b..65a91921 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges, thanks Adrià +- lower stack use in radsave to help musl [Jacob Thrane Lund] 12/2/18 started 8.6.3 - use pkg-config to find libjpeg, if we can diff --git a/libvips/foreign/radiance.c b/libvips/foreign/radiance.c index 77b556b5..231d9b19 100644 --- a/libvips/foreign/radiance.c +++ b/libvips/foreign/radiance.c @@ -19,6 +19,8 @@ * - add buffer save functions * 28/2/17 * - use dbuf for buffer output + * 4/4/17 + * - reduce stack use to help musl */ /* @@ -885,10 +887,12 @@ rle_scanline_write( COLR *scanline, int width, } } -/* Write a single scanline. +/* Write a single scanline. buffer is at least MAX_LINE bytes and is used to + * construct the RLE scanline. Don't allocate this on the stack so we don't + * die too horribly on small-stack libc. */ static int -scanline_write( COLR *scanline, int width, FILE *fp ) +scanline_write( unsigned char *buffer, COLR *scanline, int width, FILE *fp ) { if( width < MINELEN || width > MAXELEN ) @@ -898,11 +902,12 @@ scanline_write( COLR *scanline, int width, FILE *fp ) else { /* An RLE scanline. */ - unsigned char buffer[MAX_LINE]; int length; rle_scanline_write( scanline, width, buffer, &length ); + g_assert( length <= MAX_LINE ); + return( fwrite( buffer, 1, length, fp ) - length ); } } @@ -1294,12 +1299,18 @@ static int vips2rad_put_data_block( VipsRegion *region, VipsRect *area, void *a ) { Write *write = (Write *) a; + size_t size; + unsigned char *buffer = vips_dbuf_get_write( &write->dbuf, &size ); + int i; + g_assert( size >= MAX_LINE ); + for( i = 0; i < area->height; i++ ) { VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + i ); - if( scanline_write( (COLR *) p, area->width, write->fout ) ) + if( scanline_write( buffer, + (COLR *) p, area->width, write->fout ) ) return( -1 ); } @@ -1333,6 +1344,11 @@ vips__rad_save( VipsImage *in, const char *filename ) write->filename = vips_strdup( NULL, filename ); write->fout = vips__file_open_write( filename, FALSE ); + /* scanline_write() needs a buffer to write compressed scanlines to. + * We use the dbuf ... why not. + */ + vips_dbuf_allocate( &write->dbuf, MAX_LINE ); + if( !write->filename || !write->fout || vips2rad_put_header( write ) || From 915226db212f684d6e2ba8bdcc1785c0630aff79 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Apr 2018 17:46:14 +0100 Subject: [PATCH 09/36] oop missing a seek --- libvips/foreign/radiance.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/radiance.c b/libvips/foreign/radiance.c index 231d9b19..955d4c98 100644 --- a/libvips/foreign/radiance.c +++ b/libvips/foreign/radiance.c @@ -1299,11 +1299,16 @@ static int vips2rad_put_data_block( VipsRegion *region, VipsRect *area, void *a ) { Write *write = (Write *) a; - size_t size; - unsigned char *buffer = vips_dbuf_get_write( &write->dbuf, &size ); + size_t size; + unsigned char *buffer; int i; + /* You have to seek back after a write. + */ + buffer = vips_dbuf_get_write( &write->dbuf, &size ); + vips_dbuf_seek( &write->dbuf, 0, SEEK_SET ); + g_assert( size >= MAX_LINE ); for( i = 0; i < area->height; i++ ) { From 83d4ee0b0b0cfba73d264df7b7e8b2e7c46f01dd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 5 Apr 2018 12:10:43 +0100 Subject: [PATCH 10/36] update find_pdfium for latest packaging hey ho ouch --- m4/pdfium.m4 | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/m4/pdfium.m4 b/m4/pdfium.m4 index e7ec5913..33c18ea0 100644 --- a/m4/pdfium.m4 +++ b/m4/pdfium.m4 @@ -24,15 +24,6 @@ if test "$with_pdfium" = "no"; then PDFIUM_LIBS=no fi -# pdfium has a huge set of libraries, including its own jpeg ... we can't use -# -L/-l as usual, since the libjpeg.a here will break our -ljpeg later. -# Instead, just add these .a files plus their path to the list of objects to -# link. -pdfium_objects="libpdfium.a libfpdfapi.a libfxge.a libfpdfdoc.a \ - libfxcrt.a libfx_agg.a libfxcodec.a libfx_lpng.a libfx_libopenjpeg.a \ - libfx_lcms2.a libfx_freetype.a libjpeg.a libfdrm.a libpwl.a \ - libbigint.a libformfiller.a" - AC_ARG_WITH(pdfium-includes, AS_HELP_STRING([--with-pdfium-includes=DIR], [pdfium includes are in DIR]), PDFIUM_INCLUDES="-I$withval") @@ -43,7 +34,9 @@ AC_ARG_WITH(pdfium-libraries, AC_MSG_CHECKING(for PDFIUM) # Look for fpdfview.h ... this is a documented header, so it should be a good -# target ... it won't be in the standard search path, but try $PREFIX +# target +# +# it won't be in the standard search path, but try $PREFIX if test "$PDFIUM_INCLUDES" = ""; then pdfium_save_CPPFLAGS="$CPPFLAGS" @@ -59,14 +52,13 @@ if test "$PDFIUM_INCLUDES" = ""; then CPPFLAGS="$pdfium_save_CPPFLAGS" fi -# Now for the libraries ... if there's nothing set, try $PREFIX/lib +# Now for the libraries ... if there's nothing set, try $PREFIX/lib and +# $PREFIX/obj if test "$PDFIUM_LIBS" = ""; then pdfium_save_LIBS="$LIBS" pdfium_save_CPPFLAGS="$CPPFLAGS" - for i in $pdfium_objects; do - LIBS="$prefix/lib/$i $LIBS" - done + LIBS="$prefix/lib/pdfium-obj/*.a $LIBS" LIBS="$LIBS -L$prefix/lib -lc++ -licuuc -lm -lpthread" CPPFLAGS="$PDFIUM_INCLUDES $CPPFLAGS" @@ -104,10 +96,7 @@ AC_MSG_RESULT([libraries $pdfium_libraries_result, headers $pdfium_includes_resu if test x"$PDFIUM_LIBS" != x"no"; then dir="$PDFIUM_LIBS" - PDFIUM_LIBS="" - for i in $pdfium_objects; do - PDFIUM_LIBS="$PDFIUM_LIBS $dir/$i" - done + PDFIUM_LIBS="$prefix/lib/pdfium-obj/*.a" # needs -lm -lpthread too, but they will be added by other packages PDFIUM_LIBS="$PDFIUM_LIBS -L$dir/lib -lc++ -licuuc" fi From 56fe608cfbf991afd541ce4ceab636f8485a7b5b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 6 Apr 2018 09:29:45 +0100 Subject: [PATCH 11/36] start adding pdfium load --- libvips/foreign/pdfload_pdfium.c | 693 +++++++++++++++++++++++++++++++ 1 file changed, 693 insertions(+) create mode 100644 libvips/foreign/pdfload_pdfium.c diff --git a/libvips/foreign/pdfload_pdfium.c b/libvips/foreign/pdfload_pdfium.c new file mode 100644 index 00000000..a5cde37e --- /dev/null +++ b/libvips/foreign/pdfload_pdfium.c @@ -0,0 +1,693 @@ +/* load PDF with PDFium + * + * 5/4/18 + * - 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 + +#ifdef HAVE_PDFIUM + +#include + +typedef struct _VipsForeignLoadPdf { + VipsForeignLoad parent_object; + + /* Load this page. + */ + int page_no; + + /* Load this many pages. + */ + int n; + + /* Render at this DPI. + */ + double dpi; + + /* Calculate this from DPI. At 72 DPI, we render 1:1. + */ + double scale; + + FPDF_DOCUMENT *doc; + FPDF_PAGE *page; + int current_page; + + /* Doc has this many pages. + */ + int n_pages; + + /* We need to read out the side of each page we will render, and lay + * them out in the final image. + */ + VipsRect image; + VipsRect *pages; + +} VipsForeignLoadPdf; + +typedef VipsForeignLoadClass VipsForeignLoadPdfClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPdf, vips_foreign_load_pdf, + VIPS_TYPE_FOREIGN_LOAD ); + +static void * +vips_pdfium_init_cb( void *dummy ) +{ + FPDF_LIBRARY_CONFIG config; + + config.version = 2; + config.m_pUserFontPaths = NULL; + config.m_pIsolate = NULL; + config.m_v8EmbedderSlot = 0; + + FPDF_InitLibraryWithConfig( &config ); +} + +static void +vips_pdfium_init( void *dummy ) +{ + static GOnce once = G_ONCE_INIT; + + VIPS_ONCE( &once, vips_pdfium_init_cb, NULL ); +} + +static char *vips_pdfium_errors[] = { + "no error", + "unknown error", + "file not found or could not be opened", + "file not in PDF format or corrupted", + "password required or incorrect password", + "unsupported security scheme", + "page not found or content error" +}; +static int vips_pdfium_n_errors = VIPS_NUMBER( vips_pdfium_errors ); + +static void +vips_pdfium_error( void ) +{ + int err = FPDF_GetLastError(); + + if( err >= 0 && + err < VIPS_NUMBER( vips_pdfium_errors ) ) + vips_error( "pdfload", "%s", _( vips_pdfium_errors[err] ) ); + else + vips_error( "pdfload", "%s", _( "unknown error" ) ); +} + +static void +vips_foreign_load_pdf_dispose( GObject *gobject ) +{ + VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) gobject; + + VIPS_FREEF( FPDF_CloseDocument, pdf->doc ); + VIPS_FREEF( FPDF_ClosePage, pdf->page ); + + G_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_pdf_build( VipsObject *object ) +{ + VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object; + + if( !vips_object_argument_isset( object, "scale" ) ) + pdf->scale = pdf->dpi / 72.0; + + 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 gboolean +vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len ) +{ + const guchar *str = (const guchar *) buf; + + if( len >= 4 && + str[0] == '%' && + str[1] == 'P' && + str[2] == 'D' && + str[3] == 'F' ) + return( 1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_pdf_is_a( const char *filename ) +{ + unsigned char buf[4]; + + if( vips__get_bytes( filename, buf, 4 ) == 4 && + vips_foreign_load_pdf_is_a_buffer( buf, 4 ) ) + return( 1 ); + + return( 0 ); +} + +static int +vips_foreign_load_pdf_get_page( VipsForeignLoadPdf *pdf, int page_no ) +{ + if( pdf->current_page != page_no ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( pdf ); + + VIPS_FREEF( FPDF_ClosePage, pdf->page ); + pdf->current_page = -1; + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_get_page: %d\n", page_no ); +#endif /*DEBUG*/ + + if( !(pdf->page = FPDF_LoadPage( pdf->doc, page_no )) ) { + vips_pdfium_error(); + 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 *tag; // as understood by PDFium + char *field; // as understood by libvips +} VipsForeignLoadPdfMetadata; + +static VipsForeignLoadPdfMetadata vips_foreign_load_pdf_metadata[] = { + { "Title", "pdf-title" }, + { "Author", "pdf-author" }, + { "Subject", "pdf-subject" }, + { "Keywords", "pdf-keywords" }, + { "Creator", "pdf-creator" }, + { "Producer", "pdf-producer" }, + // poppler has "metadata" as well, but pdfium does not support this +}; +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. + */ + vips_image_set_int( out, "pdf-n_pages", pdf->n_pages ); + + for( i = 0; i < n_metadata; i++ ) { + VipsForeignLoadPdfMetadata *metadata = + &vips_foreign_load_pdf_metadata[i]; + + char text[1024]; + + if( FPDF_GetMetaText( pdf->doc, metadata->tag, text, 1024 ) ) { + // FPDF is utf16, we must swap to utf8 + vips_image_set_string( out, metadata->field, text ); + } + } + + /* 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 = (VipsForeignLoadPdf *) load; + + int top; + int i; + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_header: %p\n", pdf ); +#endif /*DEBUG*/ + + pdf->n_pages = FPDF_GetPageCount( 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 ); + pdf->pages[i].left = 0; + pdf->pages[i].top = top; + pdf->pages[i].width = FPDF_GetPageWidth( pdf->page ) * + pdf->scale; + pdf->pages[i].height = FPDF_GetPageHeight( pdf->page ) * + pdf->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 size, we can tag this as a toilet roll + * image and tiffsave will be able to save it as a multipage tiff. + */ + for( i = 1; i < pdf->n; i++ ) + if( pdf->pages[i].width != pdf->pages[0].width || + pdf->pages[i].height != pdf->pages[0].height ) + break; + if( i == 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 ); + + return( 0 ); +} + +static int +vips_foreign_load_pdf_generate( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) 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. Use 255 (white) for the + * bg, PDFs generally assume a paper backgrocund colour. + */ + vips_region_paint( or, r, 255 ); + + /* 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->scale, pdf->scale ); + cairo_translate( cr, + (pdf->pages[i].left - rect.left) / pdf->scale, + (pdf->pages[i].top - rect.top) / pdf->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__cairo2rgba( + (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 = (VipsForeignLoadPdf *) 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(); + + vips_foreign_load_pdf_set_image( pdf, t[0] ); + if( vips_image_generate( t[0], + NULL, vips_foreign_load_pdf_generate, NULL, pdf, NULL ) ) + return( -1 ); + + /* Don't use tilecache to keep the number of calls to + * pdf_page_render() low. Don't thread the cache, we rely on + * locking to keep pdf single-threaded. Use a large strip size to + * (again) keep the number of calls to page_render low. + */ + if( vips_linecache( t[0], &t[1], + "tile_height", 5000, + NULL ) ) + return( -1 ); + if( 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->load = vips_foreign_load_pdf_load; + + VIPS_ARG_INT( class, "page", 10, + _( "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", 11, + _( "n" ), + _( "Load this many pages" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdf, n ), + -1, 100000, 1 ); + + VIPS_ARG_DOUBLE( class, "dpi", 12, + _( "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", 13, + _( "Scale" ), + _( "Scale output by this factor" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdf, scale ), + 0.001, 100000.0, 1.0 ); + +} + +static void +vips_foreign_load_pdf_init( VipsForeignLoadPdf *pdf ) +{ + pdf->dpi = 72.0; + pdf->scale = 1.0; + pdf->n = 1; + pdf->current_page = -1; +} + +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 ) +{ + VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; + VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load; + + char *path; + + /* 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->doc = FPDF_LoadDocument( file->uri, NULL )) ) { + vips_pdfium_error(); + vips_error( "pdfload", + _( "unable to load \"%s\"" ), file->uri ); + return( -1 ); + } + + VIPS_SETSTR( load->out->filename, file->filename ); + + return( vips_foreign_load_pdf_header( load ) ); +} + +static const char *vips_foreign_pdf_suffs[] = { + ".pdf", + NULL +}; + +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"; + + 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_header( VipsForeignLoad *load ) +{ + VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; + VipsForeignLoadPdfBuffer *buffer = + (VipsForeignLoadPdfBuffer *) load; + + /* + if( !(pdf->doc = poppler_document_new_from_data( + buffer->buf->data, buffer->buf->length, NULL, &error )) ) { + vips_g_error( &error ); + return( -1 ); + } + */ + + return( vips_foreign_load_pdf_header( load ) ); +} + +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"; + + load_class->is_a_buffer = vips_foreign_load_pdf_is_a_buffer; + load_class->header = vips_foreign_load_pdf_buffer_header; + + 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 ) +{ +} + +#endif /*HAVE_PDFIUM*/ From 05456125b6a93d49638e2f4c021df607883ccf8c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 7 Apr 2018 17:36:52 +0100 Subject: [PATCH 12/36] builds, links and mostly runs --- README.md | 7 +++ configure.ac | 4 +- libvips/foreign/Makefile.am | 1 + libvips/foreign/pdfload_pdfium.c | 87 +++++++++++++++++++------------- m4/pdfium.m4 | 41 ++++++++++++--- 5 files changed, 97 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 111f9a07..ec471f70 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,13 @@ via imagemagick instead. The usual SVG loader. If this is not present, vips will try to load SVGs via imagemagick instead. +### PDFium + +If present, libvips will attempt to load PDFs via PDFium. This library must be +packaged by https://github.com/jcupitt/build-pdfium + +If PDFium is not detected, libvips will look for poppler-glib instead. + ### libpoppler The usual PDF loader. If this is not present, vips will try to load PDFs diff --git a/configure.ac b/configure.ac index 62da56c1..549f8b55 100644 --- a/configure.ac +++ b/configure.ac @@ -1169,14 +1169,14 @@ fi # Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS # sort includes to get longer, more specific dirs first # helps, for example, selecting graphicsmagick over imagemagick -VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS +VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_INCLUDES $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS do echo $i done | sort -ru` VIPS_CFLAGS=`echo $VIPS_CFLAGS` VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS" VIPS_INCLUDES="$ZLIB_INCLUDES $PNG_INCLUDES $TIFF_INCLUDES $JPEG_INCLUDES" -VIPS_LIBS="$ZLIB_LIBS $MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" +VIPS_LIBS="$ZLIB_LIBS $MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" AC_SUBST(VIPS_LIBDIR) diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index e49c6e4d..2f17c1aa 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -6,6 +6,7 @@ libforeign_la_SOURCES = \ gifload.c \ cairo.c \ pdfload.c \ + pdfload_pdfium.c \ svgload.c \ radiance.c \ radload.c \ diff --git a/libvips/foreign/pdfload_pdfium.c b/libvips/foreign/pdfload_pdfium.c index a5cde37e..bed4737e 100644 --- a/libvips/foreign/pdfload_pdfium.c +++ b/libvips/foreign/pdfload_pdfium.c @@ -31,6 +31,20 @@ */ +/* TODO + * + * - more code sharing with pdfload.c, eg. vips_foreign_load_pdf_is_a_buffer() + * and get_flags etc. + * - could share the page layout code too + * - make pdf.c with base stuff in? + * - FPDF_GetMetaText() results needs mapping from utf16 to utf8 + * - what about filename encodings + * - do we need to clear the background to white in generate()? + * - I guess we must write RGBA to match poppler output + * - new_from_buffer stuff + * + */ + /* #define DEBUG */ @@ -45,6 +59,10 @@ #include #include +/* Just until we get rid of the final bits of poppler + */ +#include + #include #include #include @@ -52,6 +70,7 @@ #ifdef HAVE_PDFIUM #include +#include typedef struct _VipsForeignLoadPdf { VipsForeignLoad parent_object; @@ -93,27 +112,6 @@ typedef VipsForeignLoadClass VipsForeignLoadPdfClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPdf, vips_foreign_load_pdf, VIPS_TYPE_FOREIGN_LOAD ); -static void * -vips_pdfium_init_cb( void *dummy ) -{ - FPDF_LIBRARY_CONFIG config; - - config.version = 2; - config.m_pUserFontPaths = NULL; - config.m_pIsolate = NULL; - config.m_v8EmbedderSlot = 0; - - FPDF_InitLibraryWithConfig( &config ); -} - -static void -vips_pdfium_init( void *dummy ) -{ - static GOnce once = G_ONCE_INIT; - - VIPS_ONCE( &once, vips_pdfium_init_cb, NULL ); -} - static char *vips_pdfium_errors[] = { "no error", "unknown error", @@ -123,7 +121,6 @@ static char *vips_pdfium_errors[] = { "unsupported security scheme", "page not found or content error" }; -static int vips_pdfium_n_errors = VIPS_NUMBER( vips_pdfium_errors ); static void vips_pdfium_error( void ) @@ -145,8 +142,30 @@ vips_foreign_load_pdf_dispose( GObject *gobject ) VIPS_FREEF( FPDF_CloseDocument, pdf->doc ); VIPS_FREEF( FPDF_ClosePage, pdf->page ); - G_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> - dispose( gobject ); + G_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )->dispose( gobject ); +} + +static void * +vips_pdfium_init_cb( void *dummy ) +{ + FPDF_LIBRARY_CONFIG config; + + config.version = 2; + config.m_pUserFontPaths = NULL; + config.m_pIsolate = NULL; + config.m_v8EmbedderSlot = 0; + + FPDF_InitLibraryWithConfig( &config ); + + return( NULL ); +} + +static void +vips_pdfium_init( void ) +{ + static GOnce once = G_ONCE_INIT; + + VIPS_ONCE( &once, vips_pdfium_init_cb, NULL ); } static int @@ -154,6 +173,8 @@ vips_foreign_load_pdf_build( VipsObject *object ) { VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object; + vips_pdfium_init(); + if( !vips_object_argument_isset( object, "scale" ) ) pdf->scale = pdf->dpi / 72.0; @@ -328,17 +349,14 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load ) 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 ); pdf->pages[i].left = 0; pdf->pages[i].top = top; - pdf->pages[i].width = FPDF_GetPageWidth( pdf->page ) * - pdf->scale; - pdf->pages[i].height = FPDF_GetPageHeight( pdf->page ) * - pdf->scale; + pdf->pages[i].width = + FPDF_GetPageWidth( pdf->page ) * pdf->scale; + pdf->pages[i].height = + FPDF_GetPageHeight( pdf->page ) * pdf->scale; if( pdf->pages[i].width > pdf->image.width ) pdf->image.width = pdf->pages[i].width; @@ -381,7 +399,7 @@ vips_foreign_load_pdf_generate( VipsRegion *or, */ /* Poppler won't always paint the background. Use 255 (white) for the - * bg, PDFs generally assume a paper backgrocund colour. + * bg, PDFs generally assume a paper background colour. */ vips_region_paint( or, r, 255 ); @@ -419,7 +437,7 @@ vips_foreign_load_pdf_generate( VipsRegion *or, */ if( vips_foreign_load_pdf_get_page( pdf, pdf->page_no + i ) ) return( -1 ); - poppler_page_render( pdf->page, cr ); + //poppler_page_render( pdf->page, cr ); cairo_destroy( cr ); @@ -566,6 +584,7 @@ vips_foreign_load_pdf_file_header( VipsForeignLoad *load ) VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load; char *path; + GError *error = NULL; /* We need an absolute path for a URI. */ @@ -645,11 +664,11 @@ G_DEFINE_TYPE( VipsForeignLoadPdfBuffer, vips_foreign_load_pdf_buffer, static int vips_foreign_load_pdf_buffer_header( VipsForeignLoad *load ) { + /* VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; VipsForeignLoadPdfBuffer *buffer = (VipsForeignLoadPdfBuffer *) load; - /* if( !(pdf->doc = poppler_document_new_from_data( buffer->buf->data, buffer->buf->length, NULL, &error )) ) { vips_g_error( &error ); diff --git a/m4/pdfium.m4 b/m4/pdfium.m4 index 33c18ea0..5cc6a0e1 100644 --- a/m4/pdfium.m4 +++ b/m4/pdfium.m4 @@ -6,7 +6,7 @@ dnl dnl Find pdfium libraries and headers dnl dnl Put -I stuff in PDFIUM_INCLUDES -dnl Put PDFium objects in PDFIUM_LIBS (add this to link line untouched!) +dnl Put PDFium objects in PDFIUM_LIBS (add this to the link line untouched!) dnl Define HAVE_PDFIUM if found dnl AC_DEFUN([FIND_PDFIUM], [ @@ -15,6 +15,29 @@ AC_REQUIRE([AC_PATH_XTRA]) ZLIB_INCLUDES="" ZLIB_LIBS="" +# PDFium has a set of object archives that must be linked firectly into your +# app, and a couple of true libraries. The objects must be linked in this +# order. +pdfium_objects="\ + libpdfium.a \ + libfpdfapi.a \ + libfxge.a \ + libfpdfdoc.a \ + libfxcrt.a \ + libfx_agg.a \ + libfxcodec.a \ + libfx_lpng.a \ + libfx_libopenjpeg.a \ + libfx_lcms2.a \ + libfx_freetype.a \ + libjpeg.a \ + libfdrm.a \ + libpwl.a \ + libbigint.a \ + libformfiller.a \ + libjavascript.a \ + libfxedit.a" + AC_ARG_WITH(pdfium, AS_HELP_STRING([--without-pdfium], [build without pdfium (default: test)])) # Treat --without-pdfium like --without-pdfium-includes @@ -52,13 +75,15 @@ if test "$PDFIUM_INCLUDES" = ""; then CPPFLAGS="$pdfium_save_CPPFLAGS" fi -# Now for the libraries ... if there's nothing set, try $PREFIX/lib and -# $PREFIX/obj +# Now for the libraries ... if there's nothing set, try $PREFIX/lib if test "$PDFIUM_LIBS" = ""; then pdfium_save_LIBS="$LIBS" pdfium_save_CPPFLAGS="$CPPFLAGS" - LIBS="$prefix/lib/pdfium-obj/*.a $LIBS" + LIBS="" + for i in $pdfium_objects; do + LIBS="$LIBS $prefix/lib/pdfium-obj/$i" + done LIBS="$LIBS -L$prefix/lib -lc++ -licuuc -lm -lpthread" CPPFLAGS="$PDFIUM_INCLUDES $CPPFLAGS" @@ -96,9 +121,11 @@ AC_MSG_RESULT([libraries $pdfium_libraries_result, headers $pdfium_includes_resu if test x"$PDFIUM_LIBS" != x"no"; then dir="$PDFIUM_LIBS" - PDFIUM_LIBS="$prefix/lib/pdfium-obj/*.a" - # needs -lm -lpthread too, but they will be added by other packages - PDFIUM_LIBS="$PDFIUM_LIBS -L$dir/lib -lc++ -licuuc" + PDFIUM_LIBS="" + for i in $pdfium_objects; do + PDFIUM_LIBS="$PDFIUM_LIBS $prefix/lib/pdfium-obj/$i" + done + PDFIUM_LIBS="$PDFIUM_LIBS -L$dir -lc++ -licuuc -lm -lpthread" fi AC_SUBST(PDFIUM_LIBS) From f3842dcc4bcaa69208b3807f6ab11a43d003e922 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 8 Apr 2018 11:44:15 +0100 Subject: [PATCH 13/36] update cpp example thanks fangqiao see https://github.com/jcupitt/libvips/issues/932 --- ChangeLog | 4 +-- doc/using-cpp.xml | 83 +++++++++++++++++++---------------------------- 2 files changed, 35 insertions(+), 52 deletions(-) diff --git a/ChangeLog b/ChangeLog index 65a91921..627d8e64 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,6 @@ 12/3/18 started 8.6.4 -- better fitting of fonts with overhanging edges, thanks Adrià -- lower stack use in radsave to help musl [Jacob Thrane Lund] +- better fitting of fonts with overhanging edges [Adrià] +- revise C++ example [fangqiao] 12/2/18 started 8.6.3 - use pkg-config to find libjpeg, if we can diff --git a/doc/using-cpp.xml b/doc/using-cpp.xml index a5fb9d39..be1cbeae 100644 --- a/doc/using-cpp.xml +++ b/doc/using-cpp.xml @@ -26,7 +26,7 @@ /* compile with: - * g++ -g -Wall try.cc `pkg-config vips-cpp --cflags --libs` + * g++ -g -Wall example.cc `pkg-config vips-cpp --cflags --libs` */ #include <vips/vips8> @@ -34,64 +34,47 @@ using namespace vips; int -main( int argc, char **argv ) -{ - GOptionContext *context; - GOptionGroup *main_group; - GError *error = NULL; +main (int argc, char **argv) +{ + if (VIPS_INIT (argv[0])) + vips_error_exit (NULL); + + if (argc != 3) + vips_error_exit ("usage: %s input-file output-file", argv[0]); - if( VIPS_INIT( argv[0] ) ) - vips_error_exit( NULL ); - - context = g_option_context_new( "" ); - - main_group = g_option_group_new( NULL, NULL, NULL, NULL, NULL ); - g_option_context_set_main_group( context, main_group ); - g_option_context_add_group( context, vips_get_option_group() ); - - if( !g_option_context_parse( context, &argc, &argv, &error ) ) { - if( error ) { - fprintf( stderr, "%s\n", error->message ); - g_error_free( error ); - } - - vips_error_exit( NULL ); - } - - VImage in = VImage::new_from_file( argv[1], - VImage::option()-> - set( "access", VIPS_ACCESS_SEQUENTIAL ) ); - - double avg = in.avg(); - - printf( "avg = %g\n", avg ); - printf( "width = %d\n", in.width() ); - - VImage in = VImage::new_from_file( argv[1], - VImage::option()-> - set( "access", VIPS_ACCESS_SEQUENTIAL ) ); - - VImage out = in.embed( 10, 10, 1000, 1000, - VImage::option()-> - set( "extend", "background" )-> - set( "background", 128 ) ); - - out.write_to_file( argv[2] ); - - vips_shutdown(); - - return( 0 ); + VImage in = VImage::new_from_file (argv[1], + VImage::option ()->set ("access", VIPS_ACCESS_SEQUENTIAL)); + + double avg = in.avg (); + + printf ("avg = %g\n", avg); + printf ("width = %d\n", in.width ()); + + in = VImage::new_from_file (argv[1], + VImage::option ()->set ("access", VIPS_ACCESS_SEQUENTIAL)); + + VImage out = in.embed (10, 10, 1000, 1000, + VImage::option ()-> + set ("extend", "background")-> + set ("background", 128)); + + out.write_to_file (argv[2]); + + vips_shutdown (); + + return (0); } Everything before VImage in = VImage::.. is exactly - as the C API. This boilerplate gives the example a set of standard - command-line flags. + as the C API. vips_error_exit() just prints the arguments plus the + libvips error log and exits with an error code. - This line is the C++ equivalent of vips_image_new_from_file(). It works + VImage in = VImage::.. is the C++ equivalent of + vips_image_new_from_file(). It works in the same way, the differences being: From 2a82744a103623864e3e328602a216af0f35e610 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 8 Apr 2018 18:04:06 +0100 Subject: [PATCH 14/36] working at a basic level TODO - more code sharing with pdfload.c, eg. vips_foreign_load_pdf_is_a_buffer() and get_flags etc. - could share the page layout code too - make pdf.c with base stuff in? - what about filename encodings - test transparency - new_from_buffer needs doing --- README.md | 2 +- libvips/foreign/foreign.c | 6 ++ libvips/foreign/pdfload_pdfium.c | 120 ++++++++++++------------------- m4/pdfium.m4 | 4 +- 4 files changed, 53 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index ec471f70..9b924d97 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ via imagemagick instead. ### PDFium If present, libvips will attempt to load PDFs via PDFium. This library must be -packaged by https://github.com/jcupitt/build-pdfium +packaged by https://github.com/jcupitt/docker-builds/tree/master/pdfium If PDFium is not detected, libvips will look for poppler-glib instead. diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index f32302b9..2cc90e50 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1866,6 +1866,12 @@ vips_foreign_operation_init( void ) vips_foreign_load_pdf_buffer_get_type(); #endif /*HAVE_POPPLER*/ +#ifdef HAVE_PDFIUM + vips_foreign_load_pdf_get_type(); + vips_foreign_load_pdf_file_get_type(); + vips_foreign_load_pdf_buffer_get_type(); +#endif /*HAVE_PDFIUM*/ + #ifdef HAVE_RSVG vips_foreign_load_svg_get_type(); vips_foreign_load_svg_file_get_type(); diff --git a/libvips/foreign/pdfload_pdfium.c b/libvips/foreign/pdfload_pdfium.c index bed4737e..5687283a 100644 --- a/libvips/foreign/pdfload_pdfium.c +++ b/libvips/foreign/pdfload_pdfium.c @@ -37,9 +37,7 @@ * and get_flags etc. * - could share the page layout code too * - make pdf.c with base stuff in? - * - FPDF_GetMetaText() results needs mapping from utf16 to utf8 * - what about filename encodings - * - do we need to clear the background to white in generate()? * - I guess we must write RGBA to match poppler output * - new_from_buffer stuff * @@ -160,20 +158,14 @@ vips_pdfium_init_cb( void *dummy ) return( NULL ); } -static void -vips_pdfium_init( void ) -{ - static GOnce once = G_ONCE_INIT; - - VIPS_ONCE( &once, vips_pdfium_init_cb, NULL ); -} - static int vips_foreign_load_pdf_build( VipsObject *object ) { + static GOnce once = G_ONCE_INIT; + VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object; - vips_pdfium_init(); + VIPS_ONCE( &once, vips_pdfium_init_cb, NULL ); if( !vips_object_argument_isset( object, "scale" ) ) pdf->scale = pdf->dpi / 72.0; @@ -292,10 +284,20 @@ vips_foreign_load_pdf_set_image( VipsForeignLoadPdf *pdf, VipsImage *out ) &vips_foreign_load_pdf_metadata[i]; char text[1024]; + int len; - if( FPDF_GetMetaText( pdf->doc, metadata->tag, text, 1024 ) ) { - // FPDF is utf16, we must swap to utf8 - vips_image_set_string( out, metadata->field, text ); + len = FPDF_GetMetaText( pdf->doc, metadata->tag, text, 1024 ); + if( len > 0 ) { + char *str; + + /* Silently ignore coding errors. + */ + if( (str = g_utf16_to_utf8( (gunichar2 *) text, len, + NULL, NULL, NULL )) ) { + vips_image_set_string( out, + metadata->field, str ); + g_free( str ); + } } } @@ -399,7 +401,7 @@ vips_foreign_load_pdf_generate( VipsRegion *or, */ /* Poppler won't always paint the background. Use 255 (white) for the - * bg, PDFs generally assume a paper background colour. + * bg, PDFs generally assume a paper backgrocund colour. */ vips_region_paint( or, r, 255 ); @@ -414,43 +416,41 @@ vips_foreign_load_pdf_generate( VipsRegion *or, top = r->top; while( top < VIPS_RECT_BOTTOM( r ) ) { VipsRect rect; - cairo_surface_t *surface; - cairo_t *cr; + FPDF_BITMAP bitmap; 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->scale, pdf->scale ); - cairo_translate( cr, - (pdf->pages[i].left - rect.left) / pdf->scale, - (pdf->pages[i].top - rect.top) / pdf->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 ); + /* 4 means RGBA. + */ + bitmap = FPDFBitmap_CreateEx( rect.width, rect.height, 4, + VIPS_REGION_ADDR( or, rect.left, rect.top ), + VIPS_REGION_LSKIP( or ) ); + + FPDF_RenderPageBitmap( bitmap, pdf->page, + 0, 0, rect.width, rect.height, + 0, 0 ); + + FPDFBitmap_Destroy( bitmap ); top += rect.height; i += 1; } - /* Cairo makes pre-multipled BRGA, we must byteswap and unpremultiply. + /* PDFium writes BRGA, we must swap. */ - for( y = 0; y < r->height; y++ ) - vips__cairo2rgba( - (guint32 *) VIPS_REGION_ADDR( or, r->left, r->top + y ), - r->width ); + for( y = 0; y < r->height; y++ ) { + VipsPel *p; + int x; + + p = VIPS_REGION_ADDR( or, r->left, r->top + y ); + for( x = 0; x < r->width; x++ ) { + VIPS_SWAP( VipsPel, p[0], p[2] ); + p += 4; + } + } return( 0 ); } @@ -475,13 +475,11 @@ vips_foreign_load_pdf_load( VipsForeignLoad *load ) NULL, vips_foreign_load_pdf_generate, NULL, pdf, NULL ) ) return( -1 ); - /* Don't use tilecache to keep the number of calls to - * pdf_page_render() low. Don't thread the cache, we rely on - * locking to keep pdf single-threaded. Use a large strip size to - * (again) keep the number of calls to page_render low. + /* PDFium does not like rendering parts of pages :-( always render + * complete ones. */ if( vips_linecache( t[0], &t[1], - "tile_height", 5000, + "tile_height", pdf->pages[0].height, NULL ) ) return( -1 ); if( vips_image_write( t[1], load->real ) ) @@ -556,8 +554,6 @@ typedef struct _VipsForeignLoadPdfFile { */ char *filename; - char *uri; - } VipsForeignLoadPdfFile; typedef VipsForeignLoadPdfClass VipsForeignLoadPdfFileClass; @@ -565,41 +561,16 @@ 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 ) { VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load; - 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->doc = FPDF_LoadDocument( file->uri, NULL )) ) { + if( !(pdf->doc = FPDF_LoadDocument( file->filename, NULL )) ) { vips_pdfium_error(); vips_error( "pdfload", - _( "unable to load \"%s\"" ), file->uri ); + _( "unable to load \"%s\"" ), file->filename ); return( -1 ); } @@ -622,7 +593,6 @@ vips_foreign_load_pdf_file_class_init( 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; diff --git a/m4/pdfium.m4 b/m4/pdfium.m4 index 5cc6a0e1..b777c8a1 100644 --- a/m4/pdfium.m4 +++ b/m4/pdfium.m4 @@ -34,9 +34,7 @@ pdfium_objects="\ libfdrm.a \ libpwl.a \ libbigint.a \ - libformfiller.a \ - libjavascript.a \ - libfxedit.a" + libformfiller.a" AC_ARG_WITH(pdfium, AS_HELP_STRING([--without-pdfium], [build without pdfium (default: test)])) From 06802e41107d7125f12168b32fcbb045782d4c13 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 9 Apr 2018 13:27:57 +0100 Subject: [PATCH 15/36] use O_TMPFILE, if available If O_TNMPFILE is available, use it. This is a linux extension that creates an unlinked file, so it'll be closed by the system when the last associated fd is closed. see https://github.com/jcupitt/libvips/pull/930 --- ChangeLog | 1 + libvips/iofuncs/vips.c | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index c1d3066f..e81501d0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,7 @@ - drop incompatible ICC profiles before save - better hasalpha rules - create funcs always make MULTIBAND (ie. no alpha) +- use O_TMPFILE, if available [Alexander--] 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges, thanks Adrià diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index 49e6614f..2a537e43 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -21,6 +21,8 @@ * - use expat for xml read, printf for xml write * 16/8/17 * - validate strs as being utf-8 before we write + * 9/4/18 Alexander-- + * - use O_TMPFILE, if available */ /* @@ -55,6 +57,10 @@ #define DEBUG */ +/* Enable linux extensions like O_TMPFILE, if available. + */ +#define _GNU_SOURCE + #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -69,12 +75,12 @@ #include #include #include -#include #include +#include +#include #ifdef HAVE_SYS_FILE_H #include #endif /*HAVE_SYS_FILE_H*/ -#include #ifdef HAVE_UNISTD_H #include #endif /*HAVE_UNISTD_H*/ @@ -181,8 +187,26 @@ vips__open_image_write( const char *filename, gboolean temp ) flags = MODE_WRITE; +#ifdef O_TMPFILE + /* Linux-only extension creates an unlinked file. CREAT and TRUNC must + * be clear. The filename arg to open() must name a directory. + */ + if( temp ) { + char *dirname; + + flags |= O_TMPFILE; + flags &= ~O_CREAT; + flags &= ~O_TRUNC; + + dirname = g_path_get_dirname( filename ); + fd = vips_tracked_open( dirname, flags, 0666 ); + g_free( dirname ); + } + else + fd = vips_tracked_open( filename, flags, 0666 ); +#else /*!O_TMPFILE*/ #ifdef _O_TEMPORARY - /* On Windows, setting O_TEMP gets the file automatically + /* On Windows, setting _O_TEMPORARY gets the file automatically * deleted on process exit, even if the processes crashes. See * vips_image_rewind() for what we do to help on *nix. */ @@ -190,10 +214,12 @@ vips__open_image_write( const char *filename, gboolean temp ) flags |= _O_TEMPORARY; #endif /*_O_TEMPORARY*/ - if( (fd = vips_tracked_open( filename, flags, 0666 )) < 0 ) { + fd = vips_tracked_open( filename, flags, 0666 ); +#endif /*O_TMPFILE*/ + + if( fd < 0 ) { vips_error_system( errno, "VipsImage", - _( "unable to write to \"%s\"" ), - filename ); + _( "unable to write to \"%s\"" ), filename ); return( -1 ); } From 15abbbe9863629227e7b0a04c78ef1b2d3aeb4b2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 9 Apr 2018 17:14:38 +0100 Subject: [PATCH 16/36] set "interlaced" for jpg and png set "interlaced" for interlaced png and jpg images --- ChangeLog | 1 + libvips/foreign/jpeg2vips.c | 7 +++++++ libvips/foreign/vipspng.c | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/ChangeLog b/ChangeLog index e81501d0..41d6dd9d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,7 @@ - better hasalpha rules - create funcs always make MULTIBAND (ie. no alpha) - use O_TMPFILE, if available [Alexander--] +- set "interlaced=1" for interlaced JPG and PNG images 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges, thanks Adrià diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index ceed5918..a3320606 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -94,6 +94,8 @@ * - revert previous warning change: libvips reports serious corruption, * like a truncated file, as a warning and we need to be able to catch * that + * 9/4/18 + * - set interlaced=1 for interlaced images */ /* @@ -414,6 +416,11 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) (void) vips_image_set_int( out, "jpeg-multiscan", jpeg_has_multiple_scans( cinfo ) ); + /* 8.7 adds this for PNG as well, so we have a new format-neutral name. + */ + if( jpeg_has_multiple_scans( cinfo ) ) + vips_image_set_int( out, "interlaced", 1 ); + /* Look for EXIF and ICC profile. */ for( p = cinfo->marker_list; p; p = p->next ) { diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index a7aaf963..1a98000b 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -65,6 +65,8 @@ * - better behaviour for truncated png files, thanks Yury * 26/4/17 * - better @fail handling with truncated PNGs + * 9/4/18 + * - set interlaced=1 for interlaced images */ /* @@ -438,6 +440,11 @@ png2vips_header( Read *read, VipsImage *out ) return( -1 ); } + /* Let our caller know. These are very expensive to decode. + */ + if( interlace_type != PNG_INTERLACE_NONE ) + vips_image_set_int( out, "interlaced", 1 ); + return( 0 ); } From f1f2a9de55effda07295009b40e485a7b964db7f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 10 Apr 2018 15:14:09 +0100 Subject: [PATCH 17/36] fix a warning if tiff is disabled --- libvips/deprecated/im_tiff2vips.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/deprecated/im_tiff2vips.c b/libvips/deprecated/im_tiff2vips.c index d94019e9..a5ded395 100644 --- a/libvips/deprecated/im_tiff2vips.c +++ b/libvips/deprecated/im_tiff2vips.c @@ -54,6 +54,7 @@ static int tiff2vips( const char *name, IMAGE *out, gboolean header_only ) { +#ifdef HAVE_TIFF char filename[FILENAME_MAX]; char mode[FILENAME_MAX]; char *p, *q; @@ -84,7 +85,6 @@ tiff2vips( const char *name, IMAGE *out, gboolean header_only ) * malloc if all we are doing is looking at fields. */ -#ifdef HAVE_TIFF if( !header_only && !seq && !vips__istifftiled( filename ) && From d1dd41a21fe8251dd57d9069ae388e1b7ae30764 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 10 Apr 2018 15:18:18 +0100 Subject: [PATCH 18/36] strict round down on jpeg shrink libjpeg rounds up on shrink-on-load. In some cases this can leave a dark line along the right and bottom edge, since it only contains (for example) 1/4 of a pixel of data. This change adds a crop after jpeg load so that only complete pixels are output. See https://github.com/lovell/sharp/issues/1185 --- ChangeLog | 1 + libvips/foreign/jpeg2vips.c | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 627d8e64..c0c417a3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [Adrià] - revise C++ example [fangqiao] +- strict round down on jpeg shrink on load [davidwood] 12/2/18 started 8.6.3 - use pkg-config to find libjpeg, if we can diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index eb59427e..cf4b39d1 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -94,6 +94,8 @@ * - revert previous warning change: libvips reports serious corruption, * like a truncated file, as a warning and we need to be able to catch * that + * 10/4/18 + * - strict round down on shrink-on-load */ /* @@ -151,8 +153,6 @@ /* Stuff we track during a read. */ typedef struct _ReadJpeg { - VipsImage *out; - /* Shrink by this much during load. 1, 2, 4, 8. */ int shrink; @@ -177,6 +177,13 @@ typedef struct _ReadJpeg { * during load. */ gboolean autorotate; + + /* cinfo->output_width and height can be larger than we want since + * libjpeg rounds up on shrink-on-load. This is the real size we will + * output, as opposed to the size we decompress to. + */ + int output_width; + int output_height; } ReadJpeg; /* This can be called many times. @@ -228,7 +235,6 @@ readjpeg_new( VipsImage *out, int shrink, gboolean fail, gboolean autorotate ) if( !(jpeg = VIPS_NEW( out, ReadJpeg )) ) return( NULL ); - jpeg->out = out; jpeg->shrink = shrink; jpeg->fail = fail; jpeg->filename = NULL; @@ -408,6 +414,18 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); + /* cinfo->output_width and cinfo->output_height round up with + * shrink-on-load. For example, if the image is 1801 pixels across and + * we shrink by 4, the output will be 450.25 pixels across, + * cinfo->output_width with be 451, and libjpeg will write a black + * column of pixels down the right. + * + * We must strictly round down, since we don't want fractional pixels + * along the bottom and right. + */ + jpeg->output_width = cinfo->image_width / jpeg->shrink; + jpeg->output_height = cinfo->image_height / jpeg->shrink; + /* Interlaced jpegs need lots of memory to read, so our caller needs * to know. */ @@ -691,12 +709,14 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) if( vips_image_generate( t[0], NULL, read_jpeg_generate, NULL, jpeg, NULL ) || - vips_sequential( t[0], &t[1], + vips_extract_area( t[0], &t[1], + 0, 0, jpeg->output_width, jpeg->output_height, NULL ) || + vips_sequential( t[1], &t[2], "tile_height", 8, NULL ) ) return( -1 ); - im = t[1]; + im = t[2]; if( jpeg->autorotate ) im = read_jpeg_rotate( VIPS_OBJECT( out ), im ); @@ -735,6 +755,11 @@ vips__jpeg_read( ReadJpeg *jpeg, VipsImage *out, gboolean header_only ) if( read_jpeg_header( jpeg, out ) ) return( -1 ); + /* Patch in the correct size. + */ + out->Xsize = jpeg->output_width; + out->Ysize = jpeg->output_height; + /* Swap width and height if we're going to rotate this image. */ if( jpeg->autorotate ) { From 24b146790f0628c65500e66effd29855c3fb1d14 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 10 Apr 2018 15:39:51 +0100 Subject: [PATCH 19/36] oop reorder crop to come after cache or we'll write beyond the buffer end --- libvips/foreign/jpeg2vips.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index cf4b39d1..08265baa 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -706,14 +706,17 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) printf( "read_jpeg_image: starting decompress\n" ); #endif /*DEBUG*/ + /* We must crop after the seq, or our generate may not be asked for + * full lines of pixels and will attempt to write beyond the buffer. + */ if( vips_image_generate( t[0], NULL, read_jpeg_generate, NULL, jpeg, NULL ) || - vips_extract_area( t[0], &t[1], - 0, 0, jpeg->output_width, jpeg->output_height, NULL ) || - vips_sequential( t[1], &t[2], + vips_sequential( t[0], &t[1], "tile_height", 8, - NULL ) ) + NULL ) || + vips_extract_area( t[1], &t[2], + 0, 0, jpeg->output_width, jpeg->output_height, NULL ) ) return( -1 ); im = t[2]; From c0569d25bff946ccb37ff8cf4ce1493beef83fcd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 11 Apr 2018 18:53:38 +0100 Subject: [PATCH 20/36] final fixups - add buffer load - test under valgrind - code sharing with the poppler pdf loader --- ChangeLog | 1 + README.md | 2 +- libvips/foreign/pdfload.c | 64 +++++++++++++++++-------------- libvips/foreign/pdfload_pdfium.c | 66 ++++++++------------------------ libvips/foreign/pforeign.h | 3 ++ 5 files changed, 57 insertions(+), 79 deletions(-) diff --git a/ChangeLog b/ChangeLog index 29436f5c..3ad4ef91 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,7 @@ - create funcs always make MULTIBAND (ie. no alpha) - use O_TMPFILE, if available [Alexander--] - set "interlaced=1" for interlaced JPG and PNG images +- add PDFium PDF loader 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [Adrià] diff --git a/README.md b/README.md index 9b924d97..a586fa75 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ If PDFium is not detected, libvips will look for poppler-glib instead. ### libpoppler The usual PDF loader. If this is not present, vips will try to load PDFs -via imagemagick instead. +via imagemagick. ### libgsf-1 diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index 89f1c960..e48a5920 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -55,6 +55,8 @@ #include #include +#include "pforeign.h" + #ifdef HAVE_POPPLER #include @@ -91,7 +93,7 @@ typedef struct _VipsForeignLoadPdf { */ int n_pages; - /* We need to read out the side of each page we will render, and lay + /* We need to read out the size of each page we will render, and lay * them out in the final image. */ VipsRect image; @@ -145,33 +147,6 @@ vips_foreign_load_pdf_get_flags( VipsForeignLoad *load ) return( VIPS_FOREIGN_PARTIAL ); } -static gboolean -vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len ) -{ - const guchar *str = (const guchar *) buf; - - if( len >= 4 && - str[0] == '%' && - str[1] == 'P' && - str[2] == 'D' && - str[3] == 'F' ) - return( 1 ); - - return( 0 ); -} - -static gboolean -vips_foreign_load_pdf_is_a( const char *filename ) -{ - unsigned char buf[4]; - - if( vips__get_bytes( filename, buf, 4 ) == 4 && - vips_foreign_load_pdf_is_a_buffer( buf, 4 ) ) - return( 1 ); - - return( 0 ); -} - static int vips_foreign_load_pdf_get_page( VipsForeignLoadPdf *pdf, int page_no ) { @@ -429,7 +404,7 @@ vips_foreign_load_pdf_load( VipsForeignLoad *load ) * (again) keep the number of calls to page_render low. */ if( vips_linecache( t[0], &t[1], - "tile_height", 5000, + "tile_height", VIPS_MIN( 5000, pdf->pages[0].height ), NULL ) ) return( -1 ); if( vips_image_write( t[1], load->real ) ) @@ -658,6 +633,37 @@ vips_foreign_load_pdf_buffer_init( VipsForeignLoadPdfBuffer *buffer ) #endif /*HAVE_POPPLER*/ +/* Also used by the pdfium loader. + */ +gboolean +vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len ) +{ + const guchar *str = (const guchar *) buf; + + if( len >= 4 && + str[0] == '%' && + str[1] == 'P' && + str[2] == 'D' && + str[3] == 'F' ) + return( 1 ); + + return( 0 ); +} + +/* Also used by the pdfium loader. + */ +gboolean +vips_foreign_load_pdf_is_a( const char *filename ) +{ + unsigned char buf[4]; + + if( vips__get_bytes( filename, buf, 4 ) == 4 && + vips_foreign_load_pdf_is_a_buffer( buf, 4 ) ) + return( 1 ); + + return( 0 ); +} + /** * vips_pdfload: * @filename: file to load diff --git a/libvips/foreign/pdfload_pdfium.c b/libvips/foreign/pdfload_pdfium.c index 5687283a..b9e6bd4f 100644 --- a/libvips/foreign/pdfload_pdfium.c +++ b/libvips/foreign/pdfload_pdfium.c @@ -33,14 +33,8 @@ /* TODO * - * - more code sharing with pdfload.c, eg. vips_foreign_load_pdf_is_a_buffer() - * and get_flags etc. - * - could share the page layout code too - * - make pdf.c with base stuff in? * - what about filename encodings - * - I guess we must write RGBA to match poppler output - * - new_from_buffer stuff - * + * - need to test on Windows */ /* @@ -57,14 +51,12 @@ #include #include -/* Just until we get rid of the final bits of poppler - */ -#include - #include #include #include +#include "pforeign.h" + #ifdef HAVE_PDFIUM #include @@ -97,7 +89,7 @@ typedef struct _VipsForeignLoadPdf { */ int n_pages; - /* We need to read out the side of each page we will render, and lay + /* We need to read out the size of each page we will render, and lay * them out in the final image. */ VipsRect image; @@ -137,8 +129,8 @@ vips_foreign_load_pdf_dispose( GObject *gobject ) { VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) gobject; - VIPS_FREEF( FPDF_CloseDocument, pdf->doc ); VIPS_FREEF( FPDF_ClosePage, pdf->page ); + VIPS_FREEF( FPDF_CloseDocument, pdf->doc ); G_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )->dispose( gobject ); } @@ -180,7 +172,8 @@ vips_foreign_load_pdf_build( VipsObject *object ) static VipsForeignFlags vips_foreign_load_pdf_get_flags_filename( const char *filename ) { - /* We can render any part of the page on demand. + /* We can't render any part of the page on demand, but we can render + * separate pages. Might as well call ourselves partial. */ return( VIPS_FOREIGN_PARTIAL ); } @@ -191,33 +184,6 @@ vips_foreign_load_pdf_get_flags( VipsForeignLoad *load ) return( VIPS_FOREIGN_PARTIAL ); } -static gboolean -vips_foreign_load_pdf_is_a_buffer( const void *buf, size_t len ) -{ - const guchar *str = (const guchar *) buf; - - if( len >= 4 && - str[0] == '%' && - str[1] == 'P' && - str[2] == 'D' && - str[3] == 'F' ) - return( 1 ); - - return( 0 ); -} - -static gboolean -vips_foreign_load_pdf_is_a( const char *filename ) -{ - unsigned char buf[4]; - - if( vips__get_bytes( filename, buf, 4 ) == 4 && - vips_foreign_load_pdf_is_a_buffer( buf, 4 ) ) - return( 1 ); - - return( 0 ); -} - static int vips_foreign_load_pdf_get_page( VipsForeignLoadPdf *pdf, int page_no ) { @@ -246,8 +212,8 @@ vips_foreign_load_pdf_get_page( VipsForeignLoadPdf *pdf, int page_no ) /* String-based metadata fields we extract. */ typedef struct _VipsForeignLoadPdfMetadata { - char *tag; // as understood by PDFium - char *field; // as understood by libvips + char *tag; /* as understood by PDFium */ + char *field; /* as understood by libvips */ } VipsForeignLoadPdfMetadata; static VipsForeignLoadPdfMetadata vips_foreign_load_pdf_metadata[] = { @@ -257,7 +223,7 @@ static VipsForeignLoadPdfMetadata vips_foreign_load_pdf_metadata[] = { { "Keywords", "pdf-keywords" }, { "Creator", "pdf-creator" }, { "Producer", "pdf-producer" }, - // poppler has "metadata" as well, but pdfium does not support this + /* poppler has "metadata" as well, but pdfium does not support this */ }; static int n_metadata = VIPS_NUMBER( vips_foreign_load_pdf_metadata ); @@ -440,6 +406,8 @@ vips_foreign_load_pdf_generate( VipsRegion *or, } /* PDFium writes BRGA, we must swap. + * + * FIXME ... this is a bit slow. */ for( y = 0; y < r->height; y++ ) { VipsPel *p; @@ -634,17 +602,17 @@ G_DEFINE_TYPE( VipsForeignLoadPdfBuffer, vips_foreign_load_pdf_buffer, static int vips_foreign_load_pdf_buffer_header( VipsForeignLoad *load ) { - /* VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; VipsForeignLoadPdfBuffer *buffer = (VipsForeignLoadPdfBuffer *) load; - if( !(pdf->doc = poppler_document_new_from_data( - buffer->buf->data, buffer->buf->length, NULL, &error )) ) { - vips_g_error( &error ); + if( !(pdf->doc = FPDF_LoadMemDocument( buffer->buf->data, + buffer->buf->length, NULL )) ) { + vips_pdfium_error(); + vips_error( "pdfload", + "%s", _( "unable to load from buffer" ) ); return( -1 ); } - */ return( vips_foreign_load_pdf_header( load ) ); } diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 08973569..a513f5b0 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -239,6 +239,9 @@ int vips__openslide_read( const char *filename, VipsImage *out, 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 ); + #ifdef __cplusplus } #endif /*__cplusplus*/ From fbcdb58f7b85df29b1e9957690a1c49b93bab124 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 11 Apr 2018 19:36:18 +0100 Subject: [PATCH 21/36] remove precise from travis --- .travis.yml | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 29520296..4b6b0750 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,27 +14,6 @@ matrix: - os: osx fast_finish: true include: - - os: linux - sudo: required - dist: precise - env: - - JPEG=/usr - - JOBS=`nproc` - - LIBTOOLFLAGS=--quiet - cache: ccache - before_install: - - sudo apt-get update -qq - - sudo apt-get install -y - automake gtk-doc-tools - gobject-introspection - libfftw3-dev libjpeg-turbo8-dev - libpng12-dev libwebp-dev libtiff4-dev libexpat1-dev - swig libmagick++-dev bc - libcfitsio3-dev libgsl0-dev libmatio-dev - liborc-0.4-dev liblcms2-dev libpoppler-glib-dev - librsvg2-dev libgif-dev - libpango1.0-dev - - os: linux sudo: required dist: trusty @@ -58,18 +37,14 @@ matrix: - os: osx osx_image: xcode7.3 env: - - JPEG=/usr/local/opt/mozjpeg - JOBS="`sysctl -n hw.ncpu`" - PATH="/usr/local/opt/ccache/libexec:$PATH" cache: ccache before_install: - - brew tap homebrew/science - brew update - brew install ccache - - brew install --ignore-dependencies gtk-doc - brew install - gobject-introspection - fftw mozjpeg libexif + fftw libexif libpng webp swig imagemagick cfitsio libmatio From 1dbd03eefe8c2e909e9a590bc62e44f173b04f84 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 12 Apr 2018 13:10:30 +0100 Subject: [PATCH 22/36] update for new pdfium package --- m4/pdfium.m4 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/m4/pdfium.m4 b/m4/pdfium.m4 index b777c8a1..393745dd 100644 --- a/m4/pdfium.m4 +++ b/m4/pdfium.m4 @@ -82,7 +82,7 @@ if test "$PDFIUM_LIBS" = ""; then for i in $pdfium_objects; do LIBS="$LIBS $prefix/lib/pdfium-obj/$i" done - LIBS="$LIBS -L$prefix/lib -lc++ -licuuc -lm -lpthread" + LIBS="$LIBS -L$prefix/lib -lm -lpthread" CPPFLAGS="$PDFIUM_INCLUDES $CPPFLAGS" AC_TRY_LINK([#include ], @@ -123,7 +123,7 @@ if test x"$PDFIUM_LIBS" != x"no"; then for i in $pdfium_objects; do PDFIUM_LIBS="$PDFIUM_LIBS $prefix/lib/pdfium-obj/$i" done - PDFIUM_LIBS="$PDFIUM_LIBS -L$dir -lc++ -licuuc -lm -lpthread" + PDFIUM_LIBS="$PDFIUM_LIBS -L$dir -lm -lpthread" fi AC_SUBST(PDFIUM_LIBS) From b8ff9c2069809c0ce1119ccdd269c9e220b579df Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 13 Apr 2018 10:15:41 +0100 Subject: [PATCH 23/36] remove progressive pdfload advice does not apply to pdfium, and hardly to poppler --- libvips/foreign/pdfload.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index e48a5920..ae593c06 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -684,18 +684,6 @@ vips_foreign_load_pdf_is_a( const char *filename ) * converted. If you need CMYK bitmaps, you should use vips_magickload() * instead. * - * Rendering is progressive, that is, the image is rendered in strips equal in - * height to the tile height. If your PDF contains large image files and - * they span several strips in the output image, they will be decoded multiple - * times. To fix this, increase the the tile height, for example: - * - * |[ - * vips copy huge.pdf x.png --vips-tile-height=1024 - * ]| - * - * Will process images in 1024-pixel high strips, potentially much faster, - * though of course also using a lot more memory. - * * Use @page to select a page to render, numbering from zero. * * Use @n to select the number of pages to render. The default is 1. Pages are From 12cbe7c2e495299981a233b8cb8acb87debc91c4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 23 Apr 2018 13:44:56 +0100 Subject: [PATCH 24/36] some work on fixing GM graphicsmagick + magicksave is a bit tricky :( --- configure.ac | 20 ++++++++++++++++++++ libvips/foreign/magick.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/configure.ac b/configure.ac index 549f8b55..687a270b 100644 --- a/configure.ac +++ b/configure.ac @@ -670,6 +670,26 @@ if test x"$magick6" = x"yes"; then CFLAGS="$save_CFLAGS" fi +if test x"$magick6" = x"yes"; then + # more recent magick6s have AcquireImage rather than AllocateImage argh + save_LIBS="$LIBS" + LIBS="$LIBS $MAGICK_LIBS" + AC_CHECK_FUNCS(AcquireImage, + AC_DEFINE(HAVE_ACQUIREIMAGE,1, + [define if your magick has AcquireImage.])) + LIBS="$save_LIBS" +fi + +if test x"$magick6" = x"yes"; then + # more recent magick6s have SetImageExtent + save_LIBS="$LIBS" + LIBS="$LIBS $MAGICK_LIBS" + AC_CHECK_FUNCS(SetImageExtent, + AC_DEFINE(HAVE_SETIMAGEEXTENT,1, + [define if your magick has SetImageExtent.])) + LIBS="$save_LIBS" +fi + # have flags to turn load and save off independently ... some people will want # save but not load, for example AC_ARG_ENABLE([magickload], diff --git a/libvips/foreign/magick.c b/libvips/foreign/magick.c index fde689f0..f44a3dd8 100644 --- a/libvips/foreign/magick.c +++ b/libvips/foreign/magick.c @@ -104,7 +104,13 @@ Image* magick_acquire_image(const ImageInfo *image_info, ExceptionInfo *exception) { (void) exception; +#ifdef HAVE_ACQUIREIMAGE return( AcquireImage( image_info ) ); +#else /*!HAVE_ACQUIREIMAGE*/ + /* IM5-ish and GraphicsMagick use AllocateImage(). + */ + return( AllocateImage( image_info ) ); +#endif } void @@ -112,7 +118,13 @@ magick_acquire_next_image( const ImageInfo *image_info, Image *image, ExceptionInfo *exception ) { (void) exception; +#ifdef HAVE_ACQUIREIMAGE AcquireNextImage( image_info, image ); +#else /*!HAVE_ACQUIREIMAGE*/ + /* IM5-ish and GraphicsMagick use AllocateNextImage(). + */ + AllocateNextImage( image_info, image ); +#endif } int @@ -120,7 +132,18 @@ magick_set_image_size( Image *image, const size_t width, const size_t height, ExceptionInfo *exception ) { (void) exception; +#ifdef HAVE_SETIMAGEEXTENT return( SetImageExtent( image, width, height ) ); +#else /*!HAVE_SETIMAGEEXTENT*/ + image->columns = width; + image->rows = height; + + /* imagemagick does a SyncImagePixelCache() at the end of + * SetImageExtent(), but GM does not really have an equivalent. Just + * always return True. + */ + return( MagickTrue ); +#endif /*HAVE_SETIMAGEEXTENT*/ } int @@ -129,6 +152,17 @@ magick_import_pixels( Image *image, const ssize_t x, const ssize_t y, const StorageType type,const void *pixels, ExceptionInfo *exception ) { (void) exception; + + /* GM does not seem to have a simple equivalent, unfortunately. + * + * extern MagickExport PixelPacket + * *SetImagePixels(Image *image,const long x,const long y, + * const unsigned long columns,const unsigned + * long rows); + * + * gets a pointer into the image which we can then write to, use that + * perhaps? + */ return( ImportImagePixels( image, x, y, width, height, map, type, pixels ) ); } From 18c50c3231370060486f2614a581728ab164216a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 23 Apr 2018 15:17:53 +0100 Subject: [PATCH 25/36] typo in configure.ac --- configure.ac | 2 +- libvips/include/vips/enumtypes.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index d3d19705..71321a85 100644 --- a/configure.ac +++ b/configure.ac @@ -482,7 +482,7 @@ PKG_CHECK_MODULES(TYPE_INIT, glib-2.0 < 2.36, ) # from 2.40 we have g_win32_get_command_line() on win -PKG_CHECK_MODULES(TYPE_INIT, glib-2.0 >= 2.40, +PKG_CHECK_MODULES(WIN32_GET_COMMAND_LINE, glib-2.0 >= 2.40, [if test x"$vips_os_win32" = x"yes"; then AC_DEFINE(HAVE_G_WIN32_GET_COMMAND_LINE,1,[define if your glib has g_win32_get_command_line().]) have_g_win32_get_command_line=yes diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 339f3d22..18ad5b21 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -5,6 +5,7 @@ #define VIPS_ENUM_TYPES_H G_BEGIN_DECLS + /* enumerations from "../../../libvips/include/vips/resample.h" */ GType vips_kernel_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_KERNEL (vips_kernel_get_type()) From c410dc2db8abab702f9fe30f9d393f3bda848068 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 24 Apr 2018 15:06:52 +0100 Subject: [PATCH 26/36] add format option to magickload Some magick coders (eg. ICO) don't sniff the filetype from the data, so when you try to load from a string, imagemagick is unable to pick the right decode path. Add a @format option so callers can hint the filetype. see https://github.com/jcupitt/pyvips/issues/39 --- ChangeLog | 1 + libvips/foreign/magick.c | 2 +- libvips/foreign/magick2vips.c | 30 +++++++++++++++++-------- libvips/foreign/magick7load.c | 34 ++++++++++++++++++++++------- libvips/foreign/magickload.c | 41 ++++++++++++++++++++++++++++------- libvips/foreign/pforeign.h | 18 ++++++++++----- 6 files changed, 94 insertions(+), 32 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3ad4ef91..d7cbda80 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,7 @@ - use O_TMPFILE, if available [Alexander--] - set "interlaced=1" for interlaced JPG and PNG images - add PDFium PDF loader +- add @format option to magickload 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [Adrià] diff --git a/libvips/foreign/magick.c b/libvips/foreign/magick.c index f44a3dd8..40c6939e 100644 --- a/libvips/foreign/magick.c +++ b/libvips/foreign/magick.c @@ -216,7 +216,7 @@ magick_genesis_cb( void *client ) printf( "magick_genesis_cb:\n" ); #endif /*DEBUG*/ -#ifdef HAVE_MAGICKCOREGENESIS +#if defined(HAVE_MAGICKCOREGENESIS) || defined(HAVE_MAGICK7) MagickCoreGenesis( vips_get_argv0(), MagickFalse ); #else /*!HAVE_MAGICKCOREGENESIS*/ InitializeMagick( "" ); diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c index 738a9279..7278b883 100644 --- a/libvips/foreign/magick2vips.c +++ b/libvips/foreign/magick2vips.c @@ -55,6 +55,8 @@ * - remove @all_frames, add @n * 23/2/17 * - try using GetImageChannelDepth() instead of ->depth + * 24/4/18 + * - add format hint */ /* @@ -172,7 +174,7 @@ read_close( VipsImage *im, Read *read ) static Read * read_new( const char *filename, VipsImage *im, - const char *density, int page, int n ) + const char *format, const char *density, int page, int n ) { Read *read; static int inited = 0; @@ -215,6 +217,12 @@ read_new( const char *filename, VipsImage *im, vips_strncpy( read->image_info->filename, filename, MaxTextExtent ); + /* The file format hint, eg. "ICO". + */ + if( format ) + vips_strncpy( read->image_info->magick, + format, MaxTextExtent ); + /* Canvas resolution for rendering vector formats like SVG. */ VIPS_SETSTR( read->image_info->density, density ); @@ -743,7 +751,8 @@ magick_fill_region( VipsRegion *out, int vips__magick_read( const char *filename, - VipsImage *out, const char *density, int page, int n ) + VipsImage *out, const char *format, const char *density, + int page, int n ) { Read *read; @@ -751,7 +760,7 @@ vips__magick_read( const char *filename, printf( "magick2vips: vips__magick_read: %s\n", filename ); #endif /*DEBUG*/ - if( !(read = read_new( filename, out, density, page, n )) ) + if( !(read = read_new( filename, out, format, density, page, n )) ) return( -1 ); #ifdef DEBUG @@ -782,7 +791,8 @@ vips__magick_read( const char *filename, */ int vips__magick_read_header( const char *filename, - VipsImage *out, const char *density, int page, int n ) + VipsImage *out, const char *format, const char *density, + int page, int n ) { Read *read; @@ -790,7 +800,7 @@ vips__magick_read_header( const char *filename, printf( "vips__magick_read_header: %s\n", filename ); #endif /*DEBUG*/ - if( !(read = read_new( filename, out, density, page, n )) ) + if( !(read = read_new( filename, out, format, density, page, n )) ) return( -1 ); #ifdef DEBUG @@ -824,7 +834,8 @@ vips__magick_read_header( const char *filename, int vips__magick_read_buffer( const void *buf, const size_t len, - VipsImage *out, const char *density, int page, int n ) + VipsImage *out, const char *format, const char *density, + int page, int n ) { Read *read; @@ -832,7 +843,7 @@ vips__magick_read_buffer( const void *buf, const size_t len, printf( "magick2vips: vips__magick_read_buffer: %p %zu\n", buf, len ); #endif /*DEBUG*/ - if( !(read = read_new( NULL, out, density, page, n )) ) + if( !(read = read_new( NULL, out, format, density, page, n )) ) return( -1 ); #ifdef DEBUG @@ -859,7 +870,8 @@ vips__magick_read_buffer( const void *buf, const size_t len, int vips__magick_read_buffer_header( const void *buf, const size_t len, - VipsImage *out, const char *density, int page, int n ) + VipsImage *out, const char *format, const char *density, + int page, int n ) { Read *read; @@ -867,7 +879,7 @@ vips__magick_read_buffer_header( const void *buf, const size_t len, printf( "vips__magick_read_buffer_header: %p %zu\n", buf, len ); #endif /*DEBUG*/ - if( !(read = read_new( NULL, out, density, page, n )) ) + if( !(read = read_new( NULL, out, format, density, page, n )) ) return( -1 ); #ifdef DEBUG diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index 01be0930..34d261ec 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -4,6 +4,8 @@ * - from magickload * 25/11/16 * - add @n, deprecate @all_frames (just sets n = -1) + * 24/4/18 + * - add format hint */ /* @@ -56,6 +58,8 @@ #include +#include "magick.h" + typedef struct _VipsForeignLoadMagick7 { VipsForeignLoad parent_object; @@ -63,6 +67,7 @@ typedef struct _VipsForeignLoadMagick7 { */ gboolean all_frames; + char *format; /* Format hint */ char *density; /* Load at this resolution */ int page; /* Load this page (frame) */ int n; /* Load this many pages */ @@ -307,7 +312,7 @@ vips_foreign_load_magick7_build( VipsObject *object ) printf( "vips_foreign_load_magick7_build: %p\n", object ); #endif /*DEBUG*/ - vips_foreign_load_magick7_genesis(); + magick_genesis(); magick7->image_info = CloneImageInfo( NULL ); magick7->exception = AcquireExceptionInfo(); @@ -319,6 +324,12 @@ vips_foreign_load_magick7_build( VipsObject *object ) if( magick7->all_frames ) magick7->n = -1; + /* The file format hint, eg. "ICO". + */ + if( magick7->format ) + vips_strncpy( magick7->image_info->magick, + magick7->format, MaxTextExtent ); + /* Canvas resolution for rendering vector formats like SVG. */ VIPS_SETSTR( magick7->image_info->density, magick7->density ); @@ -377,12 +388,12 @@ vips_foreign_load_magick7_class_init( VipsForeignLoadMagick7Class *class ) vips_foreign_load_magick7_get_flags_filename; load_class->get_flags = vips_foreign_load_magick7_get_flags; - VIPS_ARG_BOOL( class, "all_frames", 3, - _( "all_frames" ), - _( "Read all frames from an image" ), - VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, - G_STRUCT_OFFSET( VipsForeignLoadMagick7, all_frames ), - FALSE ); + VIPS_ARG_STRING( class, "format", 3, + _( "Format" ), + _( "Image format hint" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagick7, format ), + NULL ); VIPS_ARG_STRING( class, "density", 4, _( "Density" ), @@ -405,6 +416,13 @@ vips_foreign_load_magick7_class_init( VipsForeignLoadMagick7Class *class ) G_STRUCT_OFFSET( VipsForeignLoadMagick7, n ), -1, 100000, 1 ); + VIPS_ARG_BOOL( class, "all_frames", 7, + _( "all_frames" ), + _( "Read all frames from an image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignLoadMagick7, all_frames ), + FALSE ); + } static void @@ -579,7 +597,7 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, return( 0 ); } -/* We don't bother with GetPixelReadMask((), assume it's everywhere. Don't +/* We don't bother with GetPixelReadMask(), assume it's everywhere. Don't * bother with traits, assume taht's always update. * * We do skip index channels. Palette images add extra index channels diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c index 8c84fde4..79add3e1 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 + * 24/4/18 + * - add format hint */ /* @@ -72,6 +74,7 @@ typedef struct _VipsForeignLoadMagick { gboolean all_frames; char *density; /* Load at this resolution */ + char *format; /* Load format hint */ int page; /* Load this page (frame) */ int n; /* Load this many pages */ @@ -122,28 +125,35 @@ vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class ) vips_foreign_load_magick_get_flags_filename; load_class->get_flags = vips_foreign_load_magick_get_flags; - VIPS_ARG_BOOL( class, "all_frames", 3, + VIPS_ARG_STRING( class, "format", 3, + _( "Format" ), + _( "Image format hint" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMagick, format ), + NULL ); + + VIPS_ARG_BOOL( class, "all_frames", 4, _( "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", 4, + VIPS_ARG_STRING( class, "density", 5, _( "Density" ), _( "Canvas resolution for rendering vector formats like SVG" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadMagick, density ), NULL ); - VIPS_ARG_INT( class, "page", 5, + VIPS_ARG_INT( class, "page", 6, _( "Page" ), _( "Load this page from the file" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadMagick, page ), 0, 100000, 0 ); - VIPS_ARG_INT( class, "n", 6, + VIPS_ARG_INT( class, "n", 7, _( "n" ), _( "Load this many pages" ), VIPS_ARGUMENT_OPTIONAL_INPUT, @@ -177,7 +187,7 @@ ismagick( const char *filename ) t = vips_image_new(); vips_error_freeze(); - result = vips__magick_read_header( filename, t, NULL, 0, 1 ); + result = vips__magick_read_header( filename, t, NULL, NULL, 0, 1 ); g_object_unref( t ); vips_error_thaw(); @@ -202,7 +212,8 @@ vips_foreign_load_magick_file_header( VipsForeignLoad *load ) magick->n = -1; if( vips__magick_read( magick_file->filename, - load->out, magick->density, magick->page, magick->n ) ) + load->out, magick->format, magick->density, + magick->page, magick->n ) ) return( -1 ); VIPS_SETSTR( load->out->filename, magick_file->filename ); @@ -262,7 +273,8 @@ vips_foreign_load_magick_buffer_is_a_buffer( const void *buf, size_t len ) t = vips_image_new(); vips_error_freeze(); - result = vips__magick_read_buffer_header( buf, len, t, NULL, 0, 1 ); + result = vips__magick_read_buffer_header( buf, len, t, + NULL, NULL, 0, 1 ); g_object_unref( t ); vips_error_thaw(); @@ -288,7 +300,8 @@ vips_foreign_load_magick_buffer_header( VipsForeignLoad *load ) if( vips__magick_read_buffer( magick_buffer->buf->data, magick_buffer->buf->length, - load->out, magick->density, magick->page, magick->n ) ) + load->out, magick->format, magick->density, magick->page, + magick->n ) ) return( -1 ); return( 0 ); @@ -338,6 +351,7 @@ vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer ) * * Optional arguments: * + * * @format: string, format hint, eg. "JPG" * * @page: %gint, load from this page * * @n: %gint, load this many pages * * @density: string, canvas resolution for rendering vector formats like SVG @@ -352,6 +366,10 @@ vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer ) * The reader should also work with most versions of GraphicsMagick. See the * "--with-magickpackage" configure option. * + * The file format is usually guessed from the filename suffix. You can + * override this with @format -- for example `"ICO"` selects Windows icon + * format. See the ImageMagick documentation for a list of format names. + * * Normally it will only load the first image in a many-image sequence (such * as a GIF or a PDF). Use @page and @n to set the start page and number of * pages to load. Set @n to -1 to load all pages from @page onwards. @@ -387,6 +405,7 @@ vips_magickload( const char *filename, VipsImage **out, ... ) * * Optional arguments: * + * * @format: string, format hint, eg. "JPG" * * @page: %gint, load from this page * * @n: %gint, load this many pages * * @density: string, canvas resolution for rendering vector formats like SVG @@ -394,6 +413,12 @@ vips_magickload( const char *filename, VipsImage **out, ... ) * Read an image memory block using libMagick into a VIPS image. Exactly as * vips_magickload(), but read from a memory source. * + * The file format is usually guessed from the buffer contents, but this does + * not work for all image formats. You can + * set the format explicitly with @format -- for example `"ICO"` selects + * Windows icon + * format. See the ImageMagick documentation for a list of format names. + * * You must not free the buffer while @out is active. The * #VipsObject::postclose signal on @out is a good place to free. * diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index a513f5b0..1f326705 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -123,14 +123,18 @@ int vips__fits_read( const char *filename, VipsImage *out ); int vips__fits_write( VipsImage *in, const char *filename ); int vips__magick_read( const char *filename, - VipsImage *out, const char *density, int page, int n ); + VipsImage *out, const char *format, const char *density, + int page, int n ); int vips__magick_read_header( const char *filename, - VipsImage *out, const char *density, int page, int n ); + VipsImage *out, const char *format, const char *density, + int page, int n ); int vips__magick_read_buffer( const void *buf, const size_t len, - VipsImage *out, const char *density, int page, int n ); + VipsImage *out, const char *format, const char *density, + int page, int n ); int vips__magick_read_buffer_header( const void *buf, const size_t len, - VipsImage *out, const char *density, int page, int n ); + VipsImage *out, const char *format, const char *density, + int page, int n ); extern const char *vips__mat_suffs[]; @@ -162,12 +166,14 @@ int vips__jpeg_write_file( VipsImage *in, const char *filename, int Q, const char *profile, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, int quant_table ); + gboolean overshoot_deringing, gboolean optimize_scans, + int quant_table ); int vips__jpeg_write_buffer( VipsImage *in, void **obuf, size_t *olen, int Q, const char *profile, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, int quant_table ); + gboolean overshoot_deringing, gboolean optimize_scans, + int quant_table ); int vips__isjpeg_buffer( const void *buf, size_t len ); int vips__isjpeg( const char *filename ); From 38ea5937ca7c43279250df6558f7b68d69b6621b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 24 Apr 2018 17:14:51 +0100 Subject: [PATCH 27/36] more help for graphicsmagick but still not quite there --- configure.ac | 20 ++++++++++++++++++++ libvips/foreign/magick.c | 28 ++++++++++------------------ libvips/foreign/magick.h | 4 ++-- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/configure.ac b/configure.ac index 4164099b..5fdb7f50 100644 --- a/configure.ac +++ b/configure.ac @@ -690,6 +690,26 @@ if test x"$magick6" = x"yes"; then LIBS="$save_LIBS" fi +if test x"$magick6" = x"yes"; then + # GM uses SetImageAttribute(), IM uses SetImageProperty() + save_LIBS="$LIBS" + LIBS="$LIBS $MAGICK_LIBS" + AC_CHECK_FUNCS(SetImageProperty, + AC_DEFINE(HAVE_SETIMAGEPROPERTY,1, + [define if your magick has SetImageProperty.])) + LIBS="$save_LIBS" +fi + +if test x"$magick6" = x"yes"; then + # GM is missing InheritException + save_LIBS="$LIBS" + LIBS="$LIBS $MAGICK_LIBS" + AC_CHECK_FUNCS(InheritException, + AC_DEFINE(HAVE_INHERITEXCEPTION,1, + [define if your magick has InheritException.])) + LIBS="$save_LIBS" +fi + # have flags to turn load and save off independently ... some people will want # save but not load, for example AC_ARG_ENABLE([magickload], diff --git a/libvips/foreign/magick.c b/libvips/foreign/magick.c index 40c6939e..500132b1 100644 --- a/libvips/foreign/magick.c +++ b/libvips/foreign/magick.c @@ -82,13 +82,6 @@ magick_set_property( Image *image, const char *property, const char *value, (void) SetImageProperty( image, property, value, exception ); } -int -magick_set_image_colorspace( Image *image, const ColorspaceType colorspace, - ExceptionInfo *exception) -{ - return( SetImageColorspace( image, colorspace, exception ) ); -} - void magick_inherit_exception( ExceptionInfo *exception, Image *image ) { @@ -149,19 +142,20 @@ magick_set_image_size( Image *image, const size_t width, const size_t height, int magick_import_pixels( Image *image, const ssize_t x, const ssize_t y, const size_t width, const size_t height, const char *map, - const StorageType type,const void *pixels, ExceptionInfo *exception ) + const StorageType type, const void *pixels, ExceptionInfo *exception ) { (void) exception; /* GM does not seem to have a simple equivalent, unfortunately. + * + * Looks like we'd need to call * * extern MagickExport PixelPacket * *SetImagePixels(Image *image,const long x,const long y, * const unsigned long columns,const unsigned * long rows); * - * gets a pointer into the image which we can then write to, use that - * perhaps? + * then repack pixels into that area using map and storage_type. */ return( ImportImagePixels( image, x, y, width, height, map, type, pixels ) ); @@ -172,21 +166,19 @@ magick_set_property( Image *image, const char *property, const char *value, ExceptionInfo *exception ) { (void) exception; +#ifdef HAVE_SETIMAGEPROPERTY (void) SetImageProperty( image, property, value ); -} - -int -magick_set_image_colorspace( Image *image, const ColorspaceType colorspace, - ExceptionInfo *exception ) -{ - (void) exception; - return( SetImageColorspace( image, colorspace ) ); +#else /*!HAVE_SETIMAGEPROPERTY*/ + (void) SetImageAttribute( image, property, value ); +#endif /*HAVE_SETIMAGEPROPERTY*/ } void magick_inherit_exception( ExceptionInfo *exception, Image *image ) { +#ifdef HAVE_INHERITEXCEPTION InheritException( exception, &image->exception ); +#endif /*HAVE_INHERITEXCEPTION*/ } #endif /*HAVE_MAGICK6*/ diff --git a/libvips/foreign/magick.h b/libvips/foreign/magick.h index 97690f81..507e7e11 100644 --- a/libvips/foreign/magick.h +++ b/libvips/foreign/magick.h @@ -46,8 +46,8 @@ Image *magick_acquire_image( const ImageInfo *image_info, ExceptionInfo *exception ); void magick_acquire_next_image( const ImageInfo *image_info, Image *image, ExceptionInfo *exception ); -int magick_set_image_size( Image *image, const size_t width, const size_t height, - ExceptionInfo *exception ); +int magick_set_image_size( Image *image, + const size_t width, const size_t height, ExceptionInfo *exception ); int magick_import_pixels( Image *image, const ssize_t x, const ssize_t y, const size_t width, const size_t height, const char *map, const StorageType type,const void *pixels, ExceptionInfo *exception ); From 4f22e8d1dcbcab485dd7ce588d58b3ee6ee3e055 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 25 Apr 2018 16:04:20 +0100 Subject: [PATCH 28/36] add jpeg-chroma-subsample to jpegload The jpeg loader now sets the field jpeg-chroma-subsample to record chroma subsample. See https://github.com/jcupitt/libvips/issues/954 --- ChangeLog | 2 ++ libvips/foreign/jpeg2vips.c | 22 ++++++++++++++++++++++ libvips/foreign/jpegload.c | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/ChangeLog b/ChangeLog index d7cbda80..f52b3304 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,8 @@ - set "interlaced=1" for interlaced JPG and PNG images - add PDFium PDF loader - add @format option to magickload +- jpegload adds a jpeg-chroma-subsample field with eg. 4:4:4 for no + chrominance subsampling. 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [Adrià] diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index 04c59f36..d67239e9 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -279,6 +279,25 @@ readjpeg_file( ReadJpeg *jpeg, const char *filename ) return( 0 ); } +static const char * +find_chroma_subsample( struct jpeg_decompress_struct *cinfo ) +{ + gboolean has_subsample; + + /* libjpeg only uses 4:4:4 and 4:2:0, confusingly. + * + * http://poynton.ca/PDFs/Chroma_subsampling_notation.pdf + */ + has_subsample = cinfo->max_h_samp_factor > 1 || + cinfo->max_v_samp_factor > 1; + if( cinfo->num_components > 3 ) + /* A cmyk image. + */ + return( has_subsample ? "4:2:0:4" : "4:4:4:4" ); + else + return( has_subsample ? "4:2:0" : "4:4:4" ); +} + static int attach_blob( VipsImage *im, const char *field, void *data, int data_length ) { @@ -439,6 +458,9 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) if( jpeg_has_multiple_scans( cinfo ) ) vips_image_set_int( out, "interlaced", 1 ); + (void) vips_image_set_string( out, "jpeg-chroma-subsample", + find_chroma_subsample( cinfo ) ); + /* Look for EXIF and ICC profile. */ for( p = cinfo->marker_list; p; p = p->next ) { diff --git a/libvips/foreign/jpegload.c b/libvips/foreign/jpegload.c index 06ec6c81..607e54f8 100644 --- a/libvips/foreign/jpegload.c +++ b/libvips/foreign/jpegload.c @@ -374,6 +374,11 @@ vips_foreign_load_jpeg_buffer_init( VipsForeignLoadJpegBuffer *buffer ) * memory to load, so this field gives callers a chance to handle these * images differently. * + * The string-valued field "jpeg-chroma-subsample" gives the chroma subsample + * in standard notation. 4:4:4 means no subsample, 4:2:0 means YCbCr with + * Cb and Cr subsampled horizontally and vertically, 4:4:4:4 means a CMYK + * image with no subsampling. + * * The EXIF thumbnail, if present, is attached to the image as * "jpeg-thumbnail-data". See vips_image_get_blob(). * From da6f4fd04319677319173505b6a1ecc3ccf4c59f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Apr 2018 08:55:31 +0100 Subject: [PATCH 29/36] add n-pages metadata item tiff, magick and pdf load now attach an n-pages metadata item recording the number of pages in the orginal file see https://github.com/jcupitt/libvips/issues/953 --- ChangeLog | 1 + libvips/foreign/magick2vips.c | 13 +++++++++- libvips/foreign/magick7load.c | 43 +++++++++++--------------------- libvips/foreign/pdfload.c | 3 ++- libvips/foreign/pdfload_pdfium.c | 3 ++- libvips/foreign/tiff2vips.c | 12 ++++++++- libvips/include/vips/header.h | 7 ++++++ 7 files changed, 50 insertions(+), 32 deletions(-) diff --git a/ChangeLog b/ChangeLog index f52b3304..9ef70630 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ - add @format option to magickload - jpegload adds a jpeg-chroma-subsample field with eg. 4:4:4 for no chrominance subsampling. +- tiffload, pdfload, magickload set VIPS_META_N_PAGES "n-pages" metadata item 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [Adrià] diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c index 7278b883..476910ce 100644 --- a/libvips/foreign/magick2vips.c +++ b/libvips/foreign/magick2vips.c @@ -134,7 +134,14 @@ typedef struct _Read { ImageInfo *image_info; ExceptionInfo exception; + /* Number of pages in image. + */ + int n_pages; + + /* Number of pages we will read. + */ int n_frames; + Image **frames; int frame_height; @@ -203,6 +210,7 @@ read_new( const char *filename, VipsImage *im, read->image = NULL; read->image_info = CloneImageInfo( NULL ); GetExceptionInfo( &read->exception ); + read->n_pages = 0; read->n_frames = 0; read->frames = NULL; read->frame_height = 0; @@ -488,6 +496,7 @@ parse_header( Read *read ) which says this is a volumetric image */ + read->n_pages = GetImageListLength( image ); read->n_frames = 0; for( p = image; p; (p = GetNextImageInList( p )) ) { if( p->columns != (unsigned int) im->Xsize || @@ -512,7 +521,7 @@ parse_header( Read *read ) read->n_frames = 1; #ifdef DEBUG - printf( "image has %d frames\n", read->n_frames ); + printf( "will read %d frames\n", read->n_frames ); #endif /*DEBUG*/ if( read->n != -1 ) @@ -533,6 +542,8 @@ parse_header( Read *read ) im->Ysize *= read->n_frames; } + vips_image_set_int( im, VIPS_META_N_PAGES, read->n_pages ); + return( 0 ); } diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index 34d261ec..e8361743 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -76,7 +76,11 @@ typedef struct _VipsForeignLoadMagick7 { ImageInfo *image_info; ExceptionInfo *exception; - int n_frames; /* Number of frames in file */ + /* Number of pages in image. + */ + int n_pages; + + int n_frames; /* Number of frames we will read */ Image **frames; /* An Image* for each frame */ CacheView **cache_view; /* A CacheView for each frame */ int frame_height; @@ -283,26 +287,6 @@ vips_foreign_load_magick7_dispose( GObject *gobject ) dispose( gobject ); } -static void * -vips_foreign_load_magick7_genesis_cb( void *client ) -{ -#ifdef DEBUG - printf( "vips_foreign_load_magick7_genesis:\n" ); -#endif /*DEBUG*/ - - MagickCoreGenesis( vips_get_argv0(), MagickFalse ); - - return( NULL ); -} - -static void -vips_foreign_load_magick7_genesis( void ) -{ - static GOnce once = G_ONCE_INIT; - - VIPS_ONCE( &once, vips_foreign_load_magick7_genesis_cb, NULL ); -} - static int vips_foreign_load_magick7_build( VipsObject *object ) { @@ -578,14 +562,15 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, which says this is a volumetric image */ - magick7->n_frames = GetImageListLength( GetFirstImageInList( image ) ); + magick7->n_pages = GetImageListLength( GetFirstImageInList( image ) ); #ifdef DEBUG - printf( "image has %d frames\n", magick7->n_frames ); + printf( "image has %d pages\n", magick7->n_pages ); #endif /*DEBUG*/ - if( magick7->n != -1 ) - magick7->n_frames = VIPS_MIN( magick7->n_frames, magick7->n ); + magick7->n_frames = magick7->n != -1 ? + VIPS_MIN( magick7->n_frames, magick7->n ) : + magick7->n_pages; /* So we can finally set the height. */ @@ -594,11 +579,13 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, out->Ysize *= magick7->n_frames; } + vips_image_set_int( out, VIPS_META_N_PAGES, magick7->n_pages ); + return( 0 ); } /* We don't bother with GetPixelReadMask(), assume it's everywhere. Don't - * bother with traits, assume taht's always update. + * bother with traits, assume that's always updated. * * We do skip index channels. Palette images add extra index channels * containing the index value from the file before colourmap lookup. @@ -753,7 +740,7 @@ ismagick7( const char *filename ) ExceptionInfo *exception; int result; - vips_foreign_load_magick7_genesis(); + magick_genesis(); /* Horribly slow :-( */ @@ -853,7 +840,7 @@ vips_foreign_load_magick7_buffer_is_a_buffer( const void *buf, size_t len ) ExceptionInfo *exception; int result; - vips_foreign_load_magick7_genesis(); + magick_genesis(); /* Horribly slow :-( */ diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index ae593c06..980dd162 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -204,9 +204,10 @@ vips_foreign_load_pdf_set_image( VipsForeignLoadPdf *pdf, VipsImage *out ) */ vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); - /* Extract and attach metadata. + /* 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 = diff --git a/libvips/foreign/pdfload_pdfium.c b/libvips/foreign/pdfload_pdfium.c index b9e6bd4f..79bb2598 100644 --- a/libvips/foreign/pdfload_pdfium.c +++ b/libvips/foreign/pdfload_pdfium.c @@ -241,9 +241,10 @@ vips_foreign_load_pdf_set_image( VipsForeignLoadPdf *pdf, VipsImage *out ) */ vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); - /* Extract and attach metadata. + /* 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 = diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 49220307..70b7a810 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -177,6 +177,8 @@ * - remove missing res warning * 19/5/17 * - page > 0 could break edge tiles or strips + * 26/4/18 + * - add n-pages metadata item */ /* @@ -283,6 +285,10 @@ typedef struct _Rtiff { */ TIFF *tiff; + /* Number of pages (directories) in image. + */ + int n_pages; + /* The current page we have set. */ int current_page; @@ -1230,6 +1236,8 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, rtiff->header.height ); + vips_image_set_int( out, VIPS_META_N_PAGES, rtiff->n_pages ); + /* Even though we could end up serving tiled data, always hint * THINSTRIP, since we're quite happy doing that too, and it could need * a lot less memory. @@ -1984,6 +1992,7 @@ rtiff_new( VipsImage *out, int page, int n, gboolean autorotate ) rtiff->n = n; rtiff->autorotate = autorotate; rtiff->tiff = NULL; + rtiff->n_pages = 0; rtiff->current_page = -1; rtiff->sfn = NULL; rtiff->client = NULL; @@ -2167,8 +2176,9 @@ rtiff_header_read_all( Rtiff *rtiff ) /* -1 means "to the end". */ + rtiff->n_pages = rtiff_n_pages( rtiff ); if( rtiff->n == -1 ) - rtiff->n = rtiff_n_pages( rtiff ) - rtiff->page; + rtiff->n = rtiff->n_pages - rtiff->page; /* If we're to read many pages, verify that they are all identical. */ diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index 922f92ba..7fdf84f7 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -143,6 +143,13 @@ extern "C" { */ #define VIPS_META_PAGE_HEIGHT "page-height" +/** + * VIPS_META_N_PAGES: + * + * If set, the number of pages in the original file. + */ +#define VIPS_META_N_PAGES "n-pages" + guint64 vips_format_sizeof( VipsBandFormat format ); guint64 vips_format_sizeof_unsafe( VipsBandFormat format ); From 05fd7d9301cbf18cb710e3afa25a6262736f359f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Apr 2018 11:49:47 +0100 Subject: [PATCH 30/36] oop typo --- libvips/foreign/magick7load.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index e8361743..ffa01476 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -569,7 +569,7 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, #endif /*DEBUG*/ magick7->n_frames = magick7->n != -1 ? - VIPS_MIN( magick7->n_frames, magick7->n ) : + VIPS_MIN( magick7->n_pages, magick7->n ) : magick7->n_pages; /* So we can finally set the height. @@ -686,7 +686,8 @@ vips_foreign_load_magick7_load( VipsForeignLoadMagick7 *magick7 ) /* Record frame pointers. */ g_assert( !magick7->frames ); - if( !(magick7->frames = VIPS_ARRAY( NULL, magick7->n_frames, Image * )) ) + if( !(magick7->frames = + VIPS_ARRAY( NULL, magick7->n_frames, Image * )) ) return( -1 ); p = magick7->image; for( i = 0; i < magick7->n_frames; i++ ) { From 3a9d3225748a645481ed5a81340dd18024b51201 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Apr 2018 13:45:50 +0100 Subject: [PATCH 31/36] magick7 tests for all frames equal before load it used to crash if pages changed in size --- libvips/foreign/magick7load.c | 71 ++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index ffa01476..e4b834a7 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -425,6 +425,26 @@ vips_foreign_load_magick7_error( VipsForeignLoadMagick7 *magick7 ) magick7->exception->description ); } +static int +magick7_get_bands( Image *image ) +{ + int bands; + int i; + + /* We skip all index channels. Lots of images can have these, it's not + * just the palette ones. + */ + bands = 0; + for( i = 0; i < GetPixelChannels( image ); i++ ) { + PixelChannel channel = GetPixelChannelChannel( image, i ); + + if( channel != IndexPixelChannel ) + bands += 1; + } + + return( bands ); +} + static int vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, Image *image, VipsImage *out ) @@ -432,7 +452,7 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( magick7 ); const char *key; - int i; + Image *p; #ifdef DEBUG printf( "image->depth = %zd\n", image->depth ); @@ -448,17 +468,7 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, out->Xsize = image->columns; out->Ysize = image->rows; magick7->frame_height = image->rows; - - /* We skip all index channels. Lots of images can have these, it's not - * just the palette ones. - */ - out->Bands = 0; - for( i = 0; i < GetPixelChannels( image ); i++ ) { - PixelChannel channel = GetPixelChannelChannel( image, i ); - - if( channel != IndexPixelChannel ) - out->Bands += 1; - } + out->Bands = magick7_get_bands( image ); /* Depth can be 'fractional'. You'd think we should use * GetImageDepth() but that seems to compute something very complex. @@ -556,21 +566,46 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, vips_image_set_string( out, vips_buf_all( &name ), value ); } + magick7->n_pages = GetImageListLength( GetFirstImageInList( image ) ); +#ifdef DEBUG + printf( "image has %d pages\n", magick7->n_pages ); +#endif /*DEBUG*/ + /* Do we have a set of equal-sized frames? Append them. FIXME ... there must be an attribute somewhere from dicom read which says this is a volumetric image */ - magick7->n_pages = GetImageListLength( GetFirstImageInList( image ) ); - + magick7->n_frames = 0; + for( p = image; p; (p = GetNextImageInList( p )) ) { + if( p->columns != (unsigned int) out->Xsize || + p->rows != (unsigned int) out->Ysize || + magick7_get_bands( p ) != out->Bands ) { #ifdef DEBUG - printf( "image has %d pages\n", magick7->n_pages ); + printf( "frame %d differs\n", magick7->n_frames ); + printf( "%zdx%zd, %d bands\n", + p->columns, p->rows, magick7_get_bands( p ) ); + printf( "first frame is %dx%d, %d bands\n", + out->Xsize, out->Ysize, out->Bands ); #endif /*DEBUG*/ - magick7->n_frames = magick7->n != -1 ? - VIPS_MIN( magick7->n_pages, magick7->n ) : - magick7->n_pages; + break; + } + + magick7->n_frames += 1; + } + if( p ) + /* Nope ... just do the first image in the list. + */ + magick7->n_frames = 1; + +#ifdef DEBUG + printf( "will read %d frames\n", magick7->n_frames ); +#endif /*DEBUG*/ + + if( magick7->n != -1 ) + magick7->n_frames = VIPS_MIN( magick7->n_frames, magick7->n ); /* So we can finally set the height. */ From 9714dcdee331c859d159a84899d7e1c3309f403c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Apr 2018 13:57:33 +0100 Subject: [PATCH 32/36] test depth as well in magickload We were testing width / height / bands when verifying that it was OK to load a toilet-roll image ... of course we should test depth as well. --- libvips/foreign/magick2vips.c | 6 +++++- libvips/foreign/magick7load.c | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c index 476910ce..d4f358f2 100644 --- a/libvips/foreign/magick2vips.c +++ b/libvips/foreign/magick2vips.c @@ -499,9 +499,13 @@ parse_header( Read *read ) read->n_pages = GetImageListLength( image ); read->n_frames = 0; for( p = image; p; (p = GetNextImageInList( p )) ) { + int p_depth = + GetImageChannelDepth( p, AllChannels, &p->exception ); + if( p->columns != (unsigned int) im->Xsize || p->rows != (unsigned int) im->Ysize || - get_bands( p ) != im->Bands ) { + get_bands( p ) != im->Bands || + p_depth != depth ) { #ifdef DEBUG printf( "frame %d differs\n", read->n_frames ); printf( "%zdx%zd, %d bands\n", diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index e4b834a7..35d18864 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -581,7 +581,8 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, for( p = image; p; (p = GetNextImageInList( p )) ) { if( p->columns != (unsigned int) out->Xsize || p->rows != (unsigned int) out->Ysize || - magick7_get_bands( p ) != out->Bands ) { + magick7_get_bands( p ) != out->Bands || + p->depth != image->depth ) { #ifdef DEBUG printf( "frame %d differs\n", magick7->n_frames ); printf( "%zdx%zd, %d bands\n", From 6952c57ca0cc3bff49c78ce25e2dcac9fa4e65da Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Apr 2018 16:12:01 +0100 Subject: [PATCH 33/36] better temp filename handling - make access() fail only if we are certain the file does not exist - remove the g_mkstemp() from vips__temp_name() should help selinux see https://github.com/jcupitt/libvips/pull/930 --- libvips/iofuncs/util.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/libvips/iofuncs/util.c b/libvips/iofuncs/util.c index 871a0af9..c7026333 100644 --- a/libvips/iofuncs/util.c +++ b/libvips/iofuncs/util.c @@ -1118,9 +1118,9 @@ vips__ftruncate( int fd, gint64 pos ) return( 0 ); } -/* Test for file exists. +/* TRUE if file exists. */ -int +gboolean vips_existsf( const char *name, ... ) { va_list ap; @@ -1135,7 +1135,12 @@ vips_existsf( const char *name, ... ) g_free( path ); - return( !result ); + /* access() can fail for various reasons, especially under things + * like selinux. Only return FALSE if we are certain the file does not + * exist. + */ + return( result == 0 || + errno != ENOENT ); } #ifdef OS_WIN32 @@ -1624,32 +1629,30 @@ vips__temp_dir( void ) /* Make a temporary file name. The format parameter is something like "%s.jpg" * and will be expanded to something like "/tmp/vips-12-34587.jpg". * - * You need to free the result. A real file will also be created, though we - * delete it for you. + * You need to free the result. */ char * vips__temp_name( const char *format ) { - static int serial = 1; + static int serial = 0; char file[FILENAME_MAX]; char file2[FILENAME_MAX]; - char *name; - int fd; - vips_snprintf( file, FILENAME_MAX, "vips-%d-XXXXXX", serial++ ); + vips_snprintf( file, FILENAME_MAX, "vips-%d-%u", + serial++, g_random_int() ); vips_snprintf( file2, FILENAME_MAX, format, file ); name = g_build_filename( vips__temp_dir(), file2, NULL ); - if( (fd = g_mkstemp( name )) == -1 ) { - vips_error( "tempfile", - _( "unable to make temporary file %s" ), name ); - g_free( name ); - return( NULL ); - } - close( fd ); - g_unlink( name ); + /* We could use something like g_mkstemp() to guarantee uniqueness + * across processes, but the extra FS calls can be difficult for + * selinux. + * + * g_random_int() should be safe enough -- it's seeded from time(), so + * it ought not to collide often -- and on linux at least we never + * actually use these filenames in the filesystem anyway. + */ return( name ); } From 6ee7789095054ba924d7496e871591483fa26893 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Apr 2018 16:28:09 +0100 Subject: [PATCH 34/36] tiny docs update --- libvips/iofuncs/image.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index 9045449f..56903e7f 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -2451,9 +2451,6 @@ vips_get_disc_threshold( void ) * will default to /tmp. On Windows, vips uses GetTempPath() to find the * temporary directory. * - * vips uses g_mkstemp() to make the temporary filename. They generally look - * something like "vips-12-EJKJFGH.v". - * * See also: vips_image_new(). * * Returns: the new #VipsImage, or %NULL on error. From 4d683559aab858fbbcda8db61879867baeee4a65 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Apr 2018 21:03:40 +0100 Subject: [PATCH 35/36] add a fontfile option to text use @fontfile to give a font to load ... note that this just loads the font, you'll need to set it with @font as well see https://github.com/jcupitt/libvips/issues/950 --- ChangeLog | 1 + libvips/create/text.c | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 9ef70630..bc8aefff 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,7 @@ - jpegload adds a jpeg-chroma-subsample field with eg. 4:4:4 for no chrominance subsampling. - tiffload, pdfload, magickload set VIPS_META_N_PAGES "n-pages" metadata item +- add fontfile option to vips_text() [fangqiao] 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [Adrià] diff --git a/libvips/create/text.c b/libvips/create/text.c index e8631638..c0b14694 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -20,6 +20,8 @@ * - implement auto fitting of text inside bounds * 12/3/18 * - better fitting of fonts with overhanging edges, thanks Adrià + * 26/4/18 fangqiao + * - add fontfile option */ /* @@ -80,6 +82,7 @@ typedef struct _VipsText { int spacing; VipsAlign align; int dpi; + char *fontfile; FT_Bitmap bitmap; PangoContext *context; @@ -337,6 +340,15 @@ vips_text_build( VipsObject *object ) if( !vips_text_fontmap ) vips_text_fontmap = pango_ft2_font_map_new(); + if( text->fontfile && + !FcConfigAppFontAddFile( NULL, + (const FcChar8 *) text->fontfile ) ) { + vips_error( class->nickname, + _( "unable to load font \"%s\"" ), text->fontfile ); + g_mutex_unlock( vips_text_lock ); + return( -1 ); + } + /* If our caller set height and not dpi, we adjust dpi until * we get a fit. */ @@ -477,6 +489,13 @@ vips_text_class_init( VipsTextClass *class ) G_STRUCT_OFFSET( VipsText, spacing ), 0, 1000000, 0 ); + VIPS_ARG_STRING( class, "fontfile", 12, + _( "Font file" ), + _( "Load this font file" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsText, fontfile ), + NULL ); + } static void @@ -499,6 +518,7 @@ vips_text_init( VipsText *text ) * Optional arguments: * * * @font: %gchararray, font to render with + * * @fontfile: %gchararray, load this font file * * @width: %gint, image should be no wider than this many pixels * * @height: %gint, image should be no higher than this many pixels * * @align: #VipsAlign, left/centre/right alignment @@ -516,6 +536,9 @@ vips_text_init( VipsText *text ) * @font is the font to render with, as a fontconfig name. Examples might be * "sans 12" or perhaps "bitstream charter bold 10". * + * You can specify a font to load with @fontfile. You'll need to also set the + * name of the font with @font. + * * @width is the number of pixels to word-wrap at. Lines of text wider than * this will be broken at word bounaries. * @align can be used to set the alignment style for multi-line @@ -532,7 +555,7 @@ vips_text_init( VipsText *text ) * @dpi sets the resolution to render at. "sans 12" at 72 dpi draws characters * approximately 12 pixels high. * - * @spacing sets the line spacing, in points. It would typicallly be something + * @spacing sets the line spacing, in points. It would typically be something * like font size times 1.2. * * See also: vips_xyz(), vips_text(), vips_gaussnoise(). From dd07d6ab7dd2c10518ee0908bb5b5d675a33a148 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 27 Apr 2018 11:34:56 +0100 Subject: [PATCH 36/36] note g_free() for write_to_memory result see https://github.com/jcupitt/libvips/issues/929 --- libvips/iofuncs/image.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index 56903e7f..48a0dee2 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -2657,7 +2657,7 @@ vips_image_write_to_buffer( VipsImage *in, * * Writes @in to memory as a simple, unformatted C-style array. * - * The caller is responsible for freeing this memory. + * The caller is responsible for freeing this memory with g_free(). * * See also: vips_image_write_to_buffer(). *