From 14953c19e7f35fe4d8e63c94bb5a0613576a3fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sun, 26 Jun 2016 23:59:58 +0200 Subject: [PATCH 01/17] Add support for .svgz files to svgload --- libvips/foreign/svgload.c | 77 ++++++++++++++++++++++++++++++---- test/images/vips-profile.svgz | Bin 0 -> 24962 bytes test/test_foreign.py | 4 ++ 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 test/images/vips-profile.svgz diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index 9bae681a..3861963d 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -54,6 +54,16 @@ #include #include +/* Old librsvg versions don't include librsvg-features.h by default, + * while newer versions deprecate direct inclusion. + */ +#ifndef LIBRSVG_FEATURES_H +#include +#endif + +#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZIP) +#include +#endif typedef struct _VipsForeignLoadSvg { VipsForeignLoad parent_object; @@ -315,6 +325,9 @@ vips_foreign_load_svg_file_header( VipsForeignLoad *load ) static const char *vips_foreign_svg_suffs[] = { ".svg", +#if LIBRSVG_CHECK_FEATURE(SVGZ) + ".svgz", +#endif NULL }; @@ -364,13 +377,66 @@ typedef VipsForeignLoadSvgClass VipsForeignLoadSvgBufferClass; G_DEFINE_TYPE( VipsForeignLoadSvgBuffer, vips_foreign_load_svg_buffer, vips_foreign_load_svg_get_type() ); +#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZIP) +static void * +vips_zalloc( void *opaque, unsigned items, unsigned size ) +{ + return( g_malloc0_n( items, size ) ); +} + +static void +vips_zfree( void *opaque, void *ptr ) +{ + return( g_free( ptr ) ); +} +#endif + static gboolean vips_foreign_load_svg_is_a_buffer( const void *buf, size_t len ) { - char *str = (char *) buf; + unsigned char *str = (unsigned char *) buf; int i; +#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZIP) + /* Check for SVGZ gzip signature and inflate. + * Minimum gzip size is 18 bytes, starting with 1F 8B. + */ + if( len >= 18 && str[0] == 0x1f && str[1] == 0x8b ) { + z_stream zs; + size_t opos = 0; + unsigned char obuf[224]; + + zs.zalloc = (alloc_func) vips_zalloc; + zs.zfree = (free_func) vips_zfree; + zs.opaque = Z_NULL; + zs.next_in = str; + zs.avail_in = len; + + if( inflateInit2(&zs, 15 | 32) != Z_OK ) { + vips_error( "svgload", + "%s", _( "Zlib init failed" ) ); + return( -1 ); + } + + do { + zs.avail_out = sizeof(obuf) - opos; + zs.next_out = obuf + opos; + if( inflate(&zs, Z_NO_FLUSH) < Z_OK ) { + vips_error( "svgload", + "%s", _( "Zlib inflate failed" ) ); + return( -1 ); + } + opos = sizeof(obuf) - zs.avail_out; + } while( opos < sizeof(obuf) && zs.avail_out == 0 ); + + inflateEnd(&zs); + + str = obuf; + len = opos; + } +#endif + /* SVG documents are very freeform. They normally look like: * * @@ -390,15 +456,10 @@ vips_foreign_load_svg_is_a_buffer( const void *buf, size_t len ) return( 0 ); for( i = 0; i < 24; i++ ) if( !isascii( str[i] ) ) - return( 0 ); + return( 0 ); for( i = 0; i < 200 && i < len - 5; i++ ) { - char txt[5]; - - /* 5, since we include the \0 at the end. - */ - vips_strncpy( txt, buf + i, 5 ); - if( strcasecmp( txt, "FSBTS=w6yWt_kN1B%or9-6QyB9xxEqnLw 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 PV?Ma4Mbe!dGLruTrKw~H literal 0 HcmV?d00001 diff --git a/test/test_foreign.py b/test/test_foreign.py index a4e904df..be4cc7db 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -50,6 +50,7 @@ class TestForeign(unittest.TestCase): self.pdf_file = "images/ISO_12233-reschart.pdf" self.cmyk_pdf_file = "images/cmyktest.pdf" self.svg_file = "images/vips-profile.svg" + self.svgz_file = "images/vips-profile.svgz" self.colour = Vips.Image.jpegload(self.jpeg_file) self.mono = self.colour.extract_band(1) @@ -499,6 +500,9 @@ class TestForeign(unittest.TestCase): self.file_loader("svgload", self.svg_file, svg_valid) self.buffer_loader("svgload_buffer", self.svg_file, svg_valid) + self.file_loader("svgload", self.svgz_file, svg_valid) + self.buffer_loader("svgload_buffer", self.svgz_file, svg_valid) + im = Vips.Image.new_from_file(self.svg_file) x = Vips.Image.new_from_file(self.svg_file, scale = 2) self.assertLess(abs(im.width * 2 - x.width), 2) From 5ab0001ec68a5f61396aecd8d2d7a619b1dbe1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Mon, 27 Jun 2016 03:03:45 +0200 Subject: [PATCH 02/17] Add configure check for zlib with inflateInit2 and replace old unused and poorly named FIND_ZIP. The new code prefers pkgconfig and only falls back to manual detection if needed. This also prioritizes detected zlib flags and includes to be preferred over the ones that are added by pkgconfig for libpng, tifflib etc. which would otherwise have caused the system default zlib to be used. --- configure.ac | 38 ++++++++---- libvips/foreign/svgload.c | 6 +- m4/zip.m4 | 117 ----------------------------------- m4/zlib.m4 | 124 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 132 deletions(-) delete mode 100644 m4/zip.m4 create mode 100644 m4/zlib.m4 diff --git a/configure.ac b/configure.ac index 7adf1160..92517f82 100644 --- a/configure.ac +++ b/configure.ac @@ -626,6 +626,29 @@ if test x"$with_rsvg" != x"no"; then ) fi +# zlib +# some platforms, like macosx, are missing the .pc files for zlib, so +# we fall back to FIND_ZLIB +AC_ARG_WITH([zlib], + AS_HELP_STRING([--without-zlib], [build without zlib (default: test)])) + +if test x"$with_zlib" != "xno"; then + PKG_CHECK_MODULES(ZLIB, zlib >= 0.4, + [AC_DEFINE(HAVE_ZLIB,1,[define if you have zlib installed.]) + with_zlib=yes + PACKAGES_USED="$PACKAGES_USED zlib" + ], + [FIND_ZLIB( + [with_zlib="yes (found by search)" + ], + [AC_MSG_WARN([zlib not found; disabling SVGZ buffer support]) + with_zlib=no + ] + ) + ] + ) +fi + # OpenSlide AC_ARG_WITH([openslide], AS_HELP_STRING([--without-openslide], @@ -786,15 +809,6 @@ fi AM_CONDITIONAL(ENABLE_PYVIPS8, test x"$enable_pyvips8" = x"yes") -# hmm, these don't have .pc files on ubuntu 5.10, how odd -FIND_ZIP( - [with_zip=yes - ], - [AC_MSG_WARN([libz not found; disabling ZIP support]) - with_zip=no - ] -) - # look for TIFF with pkg-config ... fall back to our tester # pkgconfig support for libtiff starts with libtiff-4 AC_ARG_WITH([tiff], @@ -943,14 +957,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 $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_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 $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $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="$PNG_INCLUDES $TIFF_INCLUDES $ZIP_INCLUDES $JPEG_INCLUDES" -VIPS_LIBS="$MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $ZIP_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_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 $MATIO_LIBS $EXIF_LIBS -lm" +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 $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $MATIO_LIBS $EXIF_LIBS -lm" AC_SUBST(VIPS_LIBDIR) diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index 3861963d..a0d1606a 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -61,7 +61,7 @@ #include #endif -#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZIP) +#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZLIB) #include #endif @@ -377,7 +377,7 @@ typedef VipsForeignLoadSvgClass VipsForeignLoadSvgBufferClass; G_DEFINE_TYPE( VipsForeignLoadSvgBuffer, vips_foreign_load_svg_buffer, vips_foreign_load_svg_get_type() ); -#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZIP) +#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZLIB) static void * vips_zalloc( void *opaque, unsigned items, unsigned size ) { @@ -398,7 +398,7 @@ vips_foreign_load_svg_is_a_buffer( const void *buf, size_t len ) int i; -#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZIP) +#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZLIB) /* Check for SVGZ gzip signature and inflate. * Minimum gzip size is 18 bytes, starting with 1F 8B. */ diff --git a/m4/zip.m4 b/m4/zip.m4 deleted file mode 100644 index d956a738..00000000 --- a/m4/zip.m4 +++ /dev/null @@ -1,117 +0,0 @@ -dnl From FIND_MOTIF and ACX_PTHREAD, without much understanding -dnl -dnl FIND_ZIP[ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]] -dnl ------------------------------------------------ -dnl -dnl Find ZIP libraries and headers -dnl -dnl Put includes stuff in ZIP_INCLUDES -dnl Put link stuff in ZIP_LIBS -dnl Define HAVE_ZIP if found -dnl -AC_DEFUN([FIND_ZIP], [ -AC_REQUIRE([AC_PATH_XTRA]) - -ZIP_INCLUDES="" -ZIP_LIBS="" - -AC_ARG_WITH(zip, - AS_HELP_STRING([--without-zip], [build without libx (default: test)])) -# Treat --without-zip like --without-zip-includes --without-zip-libraries. -if test "$with_zip" = "no"; then - ZIP_INCLUDES=no - ZIP_LIBS=no -fi - -AC_ARG_WITH(zip-includes, - AS_HELP_STRING([--with-zip-includes=DIR], [libz includes are in DIR]), - ZIP_INCLUDES="-I$withval") -AC_ARG_WITH(zip-libraries, - AS_HELP_STRING([--with-zip-libraries=DIR], [libz libraries are in DIR]), - ZIP_LIBS="-L$withval -lz") - -AC_MSG_CHECKING(for ZIP) - -# Look for zlib.h -if test "$ZIP_INCLUDES" = ""; then - # Check the standard search path - AC_TRY_COMPILE([#include ],[int a;],[ - ZIP_INCLUDES="" - ], [ - # zlib.h is not in the standard search path, try - # $prefix - zip_save_INCLUDES="$INCLUDES" - - INCLUDES="-I${prefix}/include $INCLUDES" - - AC_TRY_COMPILE([#include ],[int a;],[ - ZIP_INCLUDES="-I${prefix}/include" - ], [ - ZIP_INCLUDES="no" - ]) - - INCLUDES=$zip_save_INCLUDES - ]) -fi - -# Now for the libraries -if test "$ZIP_LIBS" = ""; then - zip_save_LIBS="$LIBS" - zip_save_INCLUDES="$INCLUDES" - - LIBS="-lz $LIBS" - INCLUDES="$ZIP_INCLUDES $INCLUDES" - - # Try the standard search path first - AC_TRY_LINK([#include ],[zlibVersion()], [ - ZIP_LIBS="-lz" - ], [ - # libz is not in the standard search path, try $prefix - - LIBS="-L${prefix}/lib $LIBS" - - AC_TRY_LINK([#include ],[zlibVersion()], [ - ZIP_LIBS="-L${prefix}/lib -lz" - ], [ - ZIP_LIBS=no - ]) - ]) - - LIBS="$zip_save_LIBS" - INCLUDES="$zip_save_INCLUDES" -fi - -AC_SUBST(ZIP_LIBS) -AC_SUBST(ZIP_INCLUDES) - -# Print a helpful message -zip_libraries_result="$ZIP_LIBS" -zip_includes_result="$ZIP_INCLUDES" - -if test x"$zip_libraries_result" = x""; then - zip_libraries_result="in default path" -fi -if test x"$zip_includes_result" = x""; then - zip_includes_result="in default path" -fi - -if test "$zip_libraries_result" = "no"; then - zip_libraries_result="(none)" -fi -if test "$zip_includes_result" = "no"; then - zip_includes_result="(none)" -fi - -AC_MSG_RESULT([libraries $zip_libraries_result, headers $zip_includes_result]) - -# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: -if test "$ZIP_INCLUDES" != "no" && test "$ZIP_LIBS" != "no"; then - AC_DEFINE(HAVE_ZIP,1,[Define if you have libz libraries and header files.]) - $1 -else - ZIP_LIBS="" - ZIP_INCLUDES="" - $2 -fi - -])dnl diff --git a/m4/zlib.m4 b/m4/zlib.m4 new file mode 100644 index 00000000..eaeb1c39 --- /dev/null +++ b/m4/zlib.m4 @@ -0,0 +1,124 @@ +dnl From FIND_MOTIF and ACX_PTHREAD, without much understanding +dnl +dnl FIND_ZLIB[ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]] +dnl ------------------------------------------------ +dnl +dnl Find zlib libraries and headers ... useful for platforms which are missing +dnl the zlib .pc file +dnl +dnl Put compile stuff in ZLIB_INCLUDES +dnl Put link stuff in ZLIB_LIBS +dnl Define HAVE_ZLIB if found +dnl +AC_DEFUN([FIND_ZLIB], [ +AC_REQUIRE([AC_PATH_XTRA]) + +ZLIB_INCLUDES="" +ZLIB_LIBS="" + +AC_ARG_WITH(zlib, + AS_HELP_STRING([--without-zlib], [build without zlib (default: test)])) +# Treat --without-zlib like --without-zlib-includes --without-zlib-libraries. +if test "$with_zlib" = "no"; then + ZLIB_INCLUDES=no + ZLIB_LIBS=no +fi + +AC_ARG_WITH(zlib-includes, + AS_HELP_STRING([--with-zlib-includes=DIR], [libz includes are in DIR]), + ZLIB_INCLUDES="-I$withval") +AC_ARG_WITH(zlib-libraries, + AS_HELP_STRING([--with-zlib-libraries=DIR], [libz libraries are in DIR]), + ZLIB_LIBS="-L$withval -lz") + +AC_MSG_CHECKING(for ZLIB) + +# Look for zlib.h +if test "$ZLIB_INCLUDES" = ""; then + # Check the standard search path + AC_TRY_COMPILE([#include + #include ],[int a;],[ + ZLIB_INCLUDES="" + ], [ + # zlib.h is not in the standard search path, try + # $prefix + zlib_save_INCLUDES="$INCLUDES" + + INCLUDES="-I${prefix}/include $INCLUDES" + + AC_TRY_COMPILE([#include + #include ],[int a;],[ + ZLIB_INCLUDES="-I${prefix}/include" + ], [ + ZLIB_INCLUDES="no" + ]) + + INCLUDES=$zlib_save_INCLUDES + ]) +fi + +# Now for the libraries +if test "$ZLIB_LIBS" = ""; then + zlib_save_LIBS="$LIBS" + zlib_save_INCLUDES="$INCLUDES" + + LIBS="-lz $LIBS" + INCLUDES="$ZLIB_INCLUDES $INCLUDES" + + # Try the standard search path first + AC_TRY_LINK([#include + #include + ],[z_stream zs;inflateInit2(&zs, 15 | 32)], [ + ZLIB_LIBS="-lz" + ], [ + # libz is not in the standard search path, try $prefix + + LIBS="-L${prefix}/lib $LIBS" + + AC_TRY_LINK([#include + #include + ],[z_stream zs;inflateInit2(&zs, 15 | 32)], [ + ZLIB_LIBS="-L${prefix}/lib -lz" + ], [ + ZLIB_LIBS=no + ]) + ]) + + LIBS="$zlib_save_LIBS" + INCLUDES="$zlib_save_INCLUDES" +fi + +AC_SUBST(ZLIB_LIBS) +AC_SUBST(ZLIB_INCLUDES) + +# Print a helpful message +zlib_libraries_result="$ZLIB_LIBS" +zlib_includes_result="$ZLIB_INCLUDES" + +if test x"$zlib_libraries_result" = x""; then + zlib_libraries_result="in default path" +fi +if test x"$zlib_includes_result" = x""; then + zlib_includes_result="in default path" +fi + +if test "$zlib_libraries_result" = "no"; then + zlib_libraries_result="(none)" +fi +if test "$zlib_includes_result" = "no"; then + zlib_includes_result="(none)" +fi + +AC_MSG_RESULT([libraries $zlib_libraries_result, headers $zlib_includes_result]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "$ZLIB_INCLUDES" != "no" && test "$ZLIB_LIBS" != "no"; then + AC_DEFINE(HAVE_ZLIB,1,[Define if you have zlib libraries and header files.]) + $1 +else + ZLIB_INCLUDES="" + ZLIB_LIBS="" + $2 +fi + +])dnl From e2eb1b8c1286684410fcff37a9ef285dc03e8dfb Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 26 Jul 2016 16:19:28 +0100 Subject: [PATCH 03/17] better gif loader - transparency was broken if image had no extension block - load image to memory, test for transparency and mono/colour, write 1, 2, 3, or 4 band image to output, depending on what we found --- ChangeLog | 1 + libvips/foreign/gifload.c | 168 ++++++++++++++++++++++++++++---------- 2 files changed, 125 insertions(+), 44 deletions(-) diff --git a/ChangeLog b/ChangeLog index b0d2ce2f..ab1fdf55 100644 --- a/ChangeLog +++ b/ChangeLog @@ -29,6 +29,7 @@ - better upsizing with vips_resize() - added vips_worley(), generate Worley noise - added vips_perlin(), generate Perlin noise +- gif loader can write 1, 2, 3, or 4 bands depending on file contents 18/5/16 started 8.3.2 - more robust vips image reading diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index abe3f89f..e9196b39 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -4,6 +4,9 @@ * - from svgload.c * 25/4/16 * - add giflib5 support + * 26/7/16 + * - transparency was wrong if there was no EXTENSION_RECORD + * - write 1, 2, 3, or 4 bands depending on file contents */ /* @@ -86,6 +89,14 @@ typedef struct _VipsForeignLoadGif { */ GifPixelType *line; + /* We decompress the whole thing to a huge RGBA memory image, and + * as we render, watch for bands and transparency. At the end of + * loading, we copy 1 or 3 bands, with or without transparency to + * output. + */ + gboolean has_transparency; + gboolean has_colour; + } VipsForeignLoadGif; typedef VipsForeignLoadClass VipsForeignLoadGifClass; @@ -302,39 +313,11 @@ vips_foreign_load_gif_is_a( const char *filename ) return( 0 ); } -static void -vips_foreign_load_gif_parse( VipsForeignLoadGif *gif, - VipsImage *out ) -{ - vips_image_init_fields( out, - gif->file->SWidth, gif->file->SHeight, - 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); - - /* We will have the whole GIF frame in memory, so we can render any - * area. - */ - vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL ); - - /* We need a line buffer to decompress to. - */ - gif->line = VIPS_ARRAY( gif, gif->file->SWidth, GifPixelType ); -} - -static int -vips_foreign_load_gif_header( VipsForeignLoad *load ) -{ - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; - - vips_foreign_load_gif_parse( gif, load->out ); - - return( 0 ); -} - static void vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, int width, VipsPel * restrict q, VipsPel * restrict p ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); ColorMapObject *map = gif->file->Image.ColorMap ? gif->file->Image.ColorMap : gif->file->SColorMap; @@ -343,8 +326,13 @@ vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, for( x = 0; x < width; x++ ) { VipsPel v = p[x]; - if( v != gif->transparency && - v < map->ColorCount ) { + if( v >= map->ColorCount ) { + vips_warn( class->nickname, + "%s", _( "pixel value out of range" ) ); + continue; + } + + if( v != gif->transparency ) { q[0] = map->Colors[v].Red; q[1] = map->Colors[v].Green; q[2] = map->Colors[v].Blue; @@ -370,6 +358,8 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); GifFileType *file = gif->file; + ColorMapObject *map = file->Image.ColorMap ? + file->Image.ColorMap : file->SColorMap; /* Check that the frame lies within our image. */ @@ -382,10 +372,27 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out ) return( -1 ); } + /* Check if we have a non-greyscale colourmap for this frame. + */ + if( !gif->has_colour ) { + int i; + + for( i = 0; i < map->ColorCount; i++ ) + if( map->Colors[i].Red != map->Colors[i].Green || + map->Colors[i].Green != map->Colors[i].Blue ) { + VIPS_DEBUG_MSG( "gifload: not mono\n" ); + gif->has_colour = TRUE; + break; + } + } + if( file->Image.Interlace ) { int i; - VIPS_DEBUG_MSG( "gifload: interlaced frame\n" ); + VIPS_DEBUG_MSG( "gifload: interlaced frame of " + "%d x %d pixels at %d x %d\n", + file->Image.Width, file->Image.Height, + file->Image.Left, file->Image.Top ); for( i = 0; i < 4; i++ ) { int y; @@ -410,6 +417,11 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out ) else { int y; + VIPS_DEBUG_MSG( "gifload: non-interlaced frame of " + "%d x %d pixels at %d x %d\n", + file->Image.Width, file->Image.Height, + file->Image.Left, file->Image.Top ); + for( y = 0; y < file->Image.Height; y++ ) { VipsPel *q = VIPS_IMAGE_ADDR( out, file->Image.Left, file->Image.Top + y ); @@ -429,20 +441,31 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out ) } static int -vips_foreign_load_gif_load( VipsForeignLoad *load ) +vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); int frame_n; GifRecordType record; - vips_foreign_load_gif_parse( gif, load->real ); + vips_image_init_fields( out, + gif->file->SWidth, gif->file->SHeight, + 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + + /* We will have the whole GIF frame in memory, so we can render any + * area. + */ + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL ); + + /* We need a line buffer to decompress to. + */ + gif->line = VIPS_ARRAY( gif, gif->file->SWidth, GifPixelType ); /* Turn out into a memory image which we then render the GIF frames * into. */ - if( vips_image_write_prepare( load->real ) ) + if( vips_image_write_prepare( out ) ) return( -1 ); /* Scan the GIF until we have enough to have completely rendered the @@ -467,7 +490,7 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) return( -1 ); } - if( vips_foreign_load_gif_render( gif, load->real ) ) + if( vips_foreign_load_gif_render( gif, out ) ) return( -1 ); frame_n += 1; @@ -495,6 +518,7 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) * transparency. */ gif->transparency = extension[4]; + gif->has_transparency = TRUE; VIPS_DEBUG_MSG( "gifload: " "seen transparency %d\n", @@ -549,6 +573,62 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) return( 0 ); } +static int +vips_foreign_load_gif_load( VipsForeignLoad *load ) +{ + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( load ), 4 ); + + VipsImage *im; + + /* Render to a memory image. + */ + im = t[0] = vips_image_new_memory(); + if( vips_foreign_load_gif_to_memory( gif, im ) ) + return( -1 ); + + /* Depending on what we found, transform and write to load->real. + */ + if( gif->has_colour && + gif->has_transparency ) { + /* Nothing to do. + */ + } + else if( gif->has_colour ) { + /* RGB. + */ + if( vips_extract_band( im, &t[1], 0, + "n", 3, + NULL ) ) + return( -1 ); + im = t[1]; + } + else if( gif->has_transparency ) { + /* GA. + */ + if( vips_extract_band( im, &t[1], 0, NULL ) || + vips_extract_band( im, &t[2], 3, NULL ) || + vips_bandjoin2( t[1], t[2], &t[3], NULL ) ) + return( -1 ); + im = t[3]; + im->Type = VIPS_INTERPRETATION_B_W; + } + else { + /* G. + */ + if( vips_extract_band( im, &t[1], 0, NULL ) ) + return( -1 ); + im = t[1]; + im->Type = VIPS_INTERPRETATION_B_W; + } + + if( vips_image_write( im, load->out ) ) + return( -1 ); + + return( 0 ); +} + static void vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) { @@ -566,7 +646,6 @@ vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) load_class->get_flags_filename = vips_foreign_load_gif_get_flags_filename; load_class->get_flags = vips_foreign_load_gif_get_flags; - load_class->load = vips_foreign_load_gif_load; VIPS_ARG_INT( class, "page", 10, _( "Page" ), @@ -580,6 +659,7 @@ vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) static void vips_foreign_load_gif_init( VipsForeignLoadGif *gif ) { + gif->transparency = -1; } typedef struct _VipsForeignLoadGifFile { @@ -607,7 +687,7 @@ vips_foreign_load_gif_file_header( VipsForeignLoad *load ) VIPS_SETSTR( load->out->filename, file->filename ); - return( vips_foreign_load_gif_header( load ) ); + return( vips_foreign_load_gif_load( load ) ); } static const char *vips_foreign_gif_suffs[] = { @@ -702,7 +782,7 @@ vips_foreign_load_gif_buffer_header( VipsForeignLoad *load ) vips_foreign_load_gif_buffer_read ) ) return( -1 ); - return( vips_foreign_load_gif_header( load ) ); + return( vips_foreign_load_gif_load( load ) ); } static void @@ -720,7 +800,7 @@ vips_foreign_load_gif_buffer_class_init( object_class->description = _( "load GIF with giflib" ); load_class->is_a_buffer = vips_foreign_load_gif_is_a_buffer; - load_class->header = vips_foreign_load_gif_buffer_header; + load_class->load = vips_foreign_load_gif_buffer_header; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), @@ -752,8 +832,8 @@ vips_foreign_load_gif_buffer_init( VipsForeignLoadGifBuffer *buffer ) * * Use @page to set page number (frame number) to read. * - * The whole GIF is parsed and read into memory on header access, the whole - * GIF is rendered on first pixel access. + * The whole GIF is rendered into memory on header access. The output image + * will be 1, 2, 3 or 4 bands depending on what the reader finds in the file. * * See also: vips_image_new_from_file(). * From d388f666cb153d9cad8f000a5faab31652f5cbc8 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 26 Jul 2016 17:28:54 +0100 Subject: [PATCH 04/17] fix tests --- libvips/foreign/gifload.c | 2 +- test/test_foreign.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index e9196b39..c6264266 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -800,7 +800,7 @@ vips_foreign_load_gif_buffer_class_init( object_class->description = _( "load GIF with giflib" ); load_class->is_a_buffer = vips_foreign_load_gif_is_a_buffer; - load_class->load = vips_foreign_load_gif_buffer_header; + load_class->header = vips_foreign_load_gif_buffer_header; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), diff --git a/test/test_foreign.py b/test/test_foreign.py index a4e904df..06cd84e6 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -58,9 +58,7 @@ class TestForeign(unittest.TestCase): self.cmyk = self.cmyk.copy(interpretation = Vips.Interpretation.CMYK) im = Vips.Image.new_from_file(self.gif_file) - # some libMagick will load this mono image as RGB, some as mono ... test - # band 0 to be safe - self.onebit = im[0] > 128 + self.onebit = im > 128 # we have test files for formats which have a clear standard def file_loader(self, loader, test_file, validate): @@ -475,10 +473,10 @@ class TestForeign(unittest.TestCase): def gif_valid(self, im): a = im(10, 10) - self.assertAlmostEqualObjects(a, [33, 33, 33, 255]) + self.assertAlmostEqualObjects(a, [33]) self.assertEqual(im.width, 159) self.assertEqual(im.height, 203) - self.assertEqual(im.bands, 4) + self.assertEqual(im.bands, 1) self.file_loader("gifload", self.gif_file, gif_valid) self.buffer_loader("gifload_buffer", self.gif_file, gif_valid) From 54aea13a8c350c01affa2cd74f56d5fcfead1611 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 27 Jul 2016 17:43:28 +0100 Subject: [PATCH 05/17] type comments for vips_text() --- libvips/create/text.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libvips/create/text.c b/libvips/create/text.c index 90344c6c..f2e604cf 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -341,11 +341,11 @@ vips_text_init( VipsText *text ) * * Optional arguments: * - * * @font: font to render with - * * @width: render within this many pixels across - * * @alignment: left/centre/right alignment - * * @dpi: render at this resolution - * * @spacing: space lines by this in points + * * @font: %gchararray, font to render with + * * @width: %gint, render within this many pixels across + * * @alignment: #VipsAlign, left/centre/right alignment + * * @dpi: %gint, render at this resolution + * * @spacing: %gint, space lines by this in points * * Draw the string @text to an image. @out is a one-band 8-bit * unsigned char image, with 0 for no text and 255 for text. Values inbetween From 394fbce9998934bc506f70e6833b2397462c98c6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 27 Jul 2016 23:02:28 +0100 Subject: [PATCH 06/17] fix docs for vips_text() oops, it's align, not alignment argh thanks gargsms --- libvips/create/text.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libvips/create/text.c b/libvips/create/text.c index f2e604cf..464ab59b 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -343,7 +343,7 @@ vips_text_init( VipsText *text ) * * * @font: %gchararray, font to render with * * @width: %gint, render within this many pixels across - * * @alignment: #VipsAlign, left/centre/right alignment + * * @align: #VipsAlign, left/centre/right alignment * * @dpi: %gint, render at this resolution * * @spacing: %gint, space lines by this in points * @@ -359,7 +359,7 @@ vips_text_init( VipsText *text ) * * @width is the maximum number of pixels across to draw within. If the * generated text is wider than this, it will wrap to a new line. In this - * case, @alignment can be used to set the alignment style for multi-line + * case, @align can be used to set the alignment style for multi-line * text. * * @dpi sets the resolution to render at. "sans 12" at 72 dpi draws characters From d8381c73dac0b29f494585f5ddf5dfada1d83d60 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 28 Jul 2016 09:46:10 +0100 Subject: [PATCH 07/17] more improvements to error handling during eval we were dropping the error buffer in tilecache, thanks David --- TODO | 38 ++++++++++++++++++++++++++++++++++ libvips/conversion/tilecache.c | 7 ++----- libvips/foreign/jpeg2vips.c | 14 +++++++------ 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/TODO b/TODO index 4ead5e91..3d9484c3 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,41 @@ +- try: + + $ vips avg broken.jpg[fail] + + about 50% of the time it'll trigger a range of out-of-order reads and lock + for 10s or so while seq waits + +- try: + + #!/usr/bin/python + + import sys + + import gi + gi.require_version('Vips', '8.0') + from gi.repository import Vips + + try: + im = Vips.Image.new_from_file(sys.argv[1]) + im.avg() + except Vips.Error as e: + print 'saw an error <<%s>>' % e.message + print 'vips error buffer start:' + print Vips.error_buffer() + print 'vips error buffer stop' + + see: + + $ ./read.py broken.jpg[fail] + vips warning: linecache: error in tile 0 x 192 + vips warning: linecache: error in tile 0 x 200 + saw an error <> + vips error buffer start: + + vips error buffer stop + + error msg is lost + - add more webp tests to py suite - try moving some more of the CLI tests to py diff --git a/libvips/conversion/tilecache.c b/libvips/conversion/tilecache.c index 39942b22..d7299d0e 100644 --- a/libvips/conversion/tilecache.c +++ b/libvips/conversion/tilecache.c @@ -703,11 +703,8 @@ vips_tile_cache_gen( VipsRegion *or, "error on tile %p\n", tile ); vips_warn( class->nickname, - _( "error reading tile %dx%d: " - "%s" ), - tile->pos.left, tile->pos.top, - vips_error_buffer() ); - vips_error_clear(); + _( "error in tile %d x %d" ), + tile->pos.left, tile->pos.top ); vips_region_black( tile->region ); diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index 1a1e42df..79d120a2 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -977,27 +977,29 @@ read_jpeg_generate( VipsRegion *or, * a vips_sequential(). */ if( r->top != jpeg->y_pos ) { + VIPS_GATE_STOP( "read_jpeg_generate: work" ); vips_error( "VipsJpeg", _( "out of order read at line %d" ), jpeg->y_pos ); + return( -1 ); } /* Here for longjmp() from vips__new_error_exit(). */ - if( setjmp( jpeg->eman.jmp ) ) + if( setjmp( jpeg->eman.jmp ) ) { + VIPS_GATE_STOP( "read_jpeg_generate: work" ); + return( -1 ); + } /* If --fail is set, we make read fail on any warnings. This will stop * on any errors from the previous jpeg_read_scanlines(). */ if( jpeg->eman.pub.num_warnings > 0 && jpeg->fail ) { - vips_error( "VipsJpeg", - _( "read gave %ld warnings" ), - jpeg->eman.pub.num_warnings ); - vips_error( NULL, "%s", vips_error_buffer() ); + VIPS_GATE_STOP( "read_jpeg_generate: work" ); - /* Make the message only appear once. + /* Only fail once. */ jpeg->eman.pub.num_warnings = 0; From bd9cc25278684339ca5bae8f473b12d199bac58c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 29 Jul 2016 10:16:33 +0100 Subject: [PATCH 08/17] tiny --- cplusplus/VImage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cplusplus/VImage.cpp b/cplusplus/VImage.cpp index 4b8b34f2..30899e6c 100644 --- a/cplusplus/VImage.cpp +++ b/cplusplus/VImage.cpp @@ -461,7 +461,7 @@ VImage::call_option_string( const char *operation_name, { VipsOperation *operation; - VIPS_DEBUG_MSG( "vips_call_by_name: starting for %s ...\n", + VIPS_DEBUG_MSG( "call_option_string: starting for %s ...\n", operation_name ); if( !(operation = vips_operation_new( operation_name )) ) { From d78f87702da231c0aec2b93fff8a9a05bb2168b3 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 29 Jul 2016 15:09:53 +0100 Subject: [PATCH 09/17] missing unref in cpp binding operation was not unreffed if build failed --- cplusplus/VImage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cplusplus/VImage.cpp b/cplusplus/VImage.cpp index 30899e6c..c8dbdd69 100644 --- a/cplusplus/VImage.cpp +++ b/cplusplus/VImage.cpp @@ -489,6 +489,7 @@ VImage::call_option_string( const char *operation_name, */ if( vips_cache_operation_buildp( &operation ) ) { vips_object_unref_outputs( VIPS_OBJECT( operation ) ); + g_object_unref( operation ); delete options; throw( VError() ); } From 7c55def6d76366cb909938bee223283c7b38c3d3 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 29 Jul 2016 15:13:59 +0100 Subject: [PATCH 10/17] remove error msg from start fail just a debugging thing now --- libvips/iofuncs/region.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index ad7035e2..ff474c23 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -231,9 +231,12 @@ vips__region_start( VipsRegion *region ) g_mutex_unlock( image->sslock ); if( !region->seq ) { - vips_error( "vips__region_start", - _( "start function failed for image %s" ), +#ifdef DEBUG + printf( "vips__region_start: " + "start function failed for image %s", image->filename ); +#endif /*DEBUG*/ + return( -1 ); } } From 920f2ea4886da04a8c8e8a3a1a4334f586bf90b6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 29 Jul 2016 15:25:28 +0100 Subject: [PATCH 11/17] tiny py stuff --- TODO | 42 +--------------------------- python/packages/gi/overrides/Vips.py | 2 +- 2 files changed, 2 insertions(+), 42 deletions(-) diff --git a/TODO b/TODO index 3d9484c3..c9790b1c 100644 --- a/TODO +++ b/TODO @@ -3,50 +3,12 @@ $ vips avg broken.jpg[fail] about 50% of the time it'll trigger a range of out-of-order reads and lock - for 10s or so while seq waits - -- try: - - #!/usr/bin/python - - import sys - - import gi - gi.require_version('Vips', '8.0') - from gi.repository import Vips - - try: - im = Vips.Image.new_from_file(sys.argv[1]) - im.avg() - except Vips.Error as e: - print 'saw an error <<%s>>' % e.message - print 'vips error buffer start:' - print Vips.error_buffer() - print 'vips error buffer stop' - - see: - - $ ./read.py broken.jpg[fail] - vips warning: linecache: error in tile 0 x 192 - vips warning: linecache: error in tile 0 x 200 - saw an error <> - vips error buffer start: - - vips error buffer stop - - error msg is lost + for 10s or so while seq times out - add more webp tests to py suite - try moving some more of the CLI tests to py -- the gif tests in the suite sometimes fail with giflib5 because of an - uninitialized struct in giflib, see - - https://sourceforge.net/p/giflib/bugs/94/ - - sadly ubuntu 16.04 only comes with giflib5, and giflib5 is currently broken - - I like the new int mask creator in reducev, can we use it in im_vips2imask() as well? @@ -56,8 +18,6 @@ - could load pdf thumbnails? -- still not happy about float->int mask conversion in im_vips2mask.c - - colour needs to split _build() into preprocess / process / postprocess phases diff --git a/python/packages/gi/overrides/Vips.py b/python/packages/gi/overrides/Vips.py index b015779e..2da074d0 100644 --- a/python/packages/gi/overrides/Vips.py +++ b/python/packages/gi/overrides/Vips.py @@ -176,7 +176,7 @@ class Error(Exception): """ def __init__(self, message, detail = None): self.message = message - if detail == None: + if detail == None or detail == "": detail = Vips.error_buffer() Vips.error_clear() self.detail = detail From 88148318eb6b001289a48cb7380acaf44b183eb1 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 30 Jul 2016 10:51:54 +0100 Subject: [PATCH 12/17] fix performance regression the extra check on bandfmt in sizeof() in 8.3.2 was causing some performance problems ... move the check to file read, so we only do it once per image, not once per pixel or scanline thanks Lovell! --- ChangeLog | 4 ++++ TODO | 7 ------- configure.ac | 6 +++--- libvips/include/vips/header.h | 1 + libvips/include/vips/image.h | 3 ++- libvips/iofuncs/header.c | 17 +++++++++++++++++ libvips/iofuncs/vips.c | 14 +++++++++++++- 7 files changed, 40 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index d7981499..18931555 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +30/7/16 started 8.3.3 +- fix performance regression in 8.3.2, thanks Lovell +- yet more robust vips file reading + 18/5/16 started 8.3.2 - more robust vips image reading - more robust tiff read [Matt Richards] diff --git a/TODO b/TODO index 1f1a7cfc..cf0dcd7a 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,3 @@ -- the gif tests in the suite sometimes fail with giflib5 because of an - uninitialized struct in giflib, see - - https://sourceforge.net/p/giflib/bugs/94/ - - sadly ubuntu 16.04 only comes with giflib5, and giflib5 is currently broken - - I like the new int mask creator in reducev, can we use it in im_vips2imask() as well? diff --git a/configure.ac b/configure.ac index 14424b8a..929b9b53 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # also update the version number in the m4 macros below -AC_INIT([vips], [8.3.2], [vipsip@jiscmail.ac.uk]) +AC_INIT([vips], [8.3.3], [vipsip@jiscmail.ac.uk]) # required for gobject-introspection AC_PREREQ(2.62) @@ -18,7 +18,7 @@ AC_CONFIG_MACRO_DIR([m4]) # user-visible library versioning m4_define([vips_major_version], [8]) m4_define([vips_minor_version], [3]) -m4_define([vips_micro_version], [2]) +m4_define([vips_micro_version], [3]) m4_define([vips_version], [vips_major_version.vips_minor_version.vips_micro_version]) @@ -38,7 +38,7 @@ VIPS_VERSION_STRING=$VIPS_VERSION-`date` # binary interface changes not backwards compatible?: reset age to 0 LIBRARY_CURRENT=46 -LIBRARY_REVISION=2 +LIBRARY_REVISION=3 LIBRARY_AGE=4 # patched into include/vips/version.h diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index 8831f933..ba3b94a3 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -109,6 +109,7 @@ extern "C" { #define VIPS_META_LOADER "vips-loader" guint64 vips_format_sizeof( VipsBandFormat format ); +guint64 vips_format_sizeof_unsafe( VipsBandFormat format ); int vips_image_get_width( const VipsImage *image ); int vips_image_get_height( const VipsImage *image ); diff --git a/libvips/include/vips/image.h b/libvips/include/vips/image.h index ee8d7a6b..d1228632 100644 --- a/libvips/include/vips/image.h +++ b/libvips/include/vips/image.h @@ -361,10 +361,11 @@ GType vips_image_get_type( void ); /* Has to be guint64 and not size_t/off_t since we have to be able to address * huge images on platforms with 32-bit files. */ + /* Pixel address calculation macros. */ #define VIPS_IMAGE_SIZEOF_ELEMENT( I ) \ - (vips_format_sizeof((I)->BandFmt)) + (vips_format_sizeof_unsafe((I)->BandFmt)) #define VIPS_IMAGE_SIZEOF_PEL( I ) \ (VIPS_IMAGE_SIZEOF_ELEMENT( I ) * (I)->Bands) #define VIPS_IMAGE_SIZEOF_LINE( I ) \ diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index 8d748507..19307cd9 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -201,6 +201,23 @@ vips_format_sizeof( VipsBandFormat format ) return( vips__image_sizeof_bandformat[format] ); } +/** + * vips_format_sizeof_unsafe: (skip) + * @format: format type + * + * A fast but dangerous version of vips_format_sizeof(). You must have + * previously range-checked @format or you'll crash. + * + * Returns: number of bytes for a band format. + */ +guint64 +vips_format_sizeof_unsafe( VipsBandFormat format ) +{ + g_assert( 0 <= format && format <= VIPS_FORMAT_DPCOMPLEX ); + + return( vips__image_sizeof_bandformat[format] ); +} + #ifdef DEBUG /* Check that this meta is on the hash table. */ diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index 9ed6b814..92dfb144 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -1,4 +1,4 @@ -/* Read and write a vips file +/* Read and write a vips file. * * 22/5/08 * - from im_open.c, im_openin.c, im_desc_hd.c, im_readhist.c, @@ -330,6 +330,18 @@ vips__read_header_bytes( VipsImage *im, unsigned char *from ) im->Xres = im->Xres_float; im->Yres = im->Yres_float; + /* Some protection against malicious files. We also check predicted + * (based on these values) against real file length, see below. + */ + im->Xsize = VIPS_CLIP( 1, im->Xsize, VIPS_MAX_COORD ); + im->Ysize = VIPS_CLIP( 1, im->Ysize, VIPS_MAX_COORD ); + im->Bands = VIPS_CLIP( 1, im->Bands, VIPS_MAX_COORD ); + im->BandFmt = VIPS_CLIP( 0, im->BandFmt, VIPS_FORMAT_LAST - 1 ); + + /* Type, Coding, Offset, Res, etc. don't affect vips file layout, just + * pixel interpretation, don't clip them. + */ + return( 0 ); } From 5637971a36bf9cc138ab3ebac159d7e7c8ec5992 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 31 Jul 2016 10:34:12 +0100 Subject: [PATCH 13/17] support --strip for pngsave --- ChangeLog | 1 + libvips/foreign/pngsave.c | 8 +++++--- libvips/foreign/vipspng.c | 24 ++++++++++++++++-------- libvips/foreign/vipspng.h | 4 ++-- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index 82beb035..b5debe04 100644 --- a/ChangeLog +++ b/ChangeLog @@ -30,6 +30,7 @@ - added vips_worley(), generate Worley noise - added vips_perlin(), generate Perlin noise - gif loader can write 1, 2, 3, or 4 bands depending on file contents +- support --strip for pngsave 30/7/16 started 8.3.3 - fix performance regression in 8.3.2, thanks Lovell diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index 5b9e533b..959c270a 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -164,8 +164,9 @@ vips_foreign_save_png_file_build( VipsObject *object ) build( object ) ) return( -1 ); - if( vips__png_write( save->ready, png_file->filename, - png->compression, png->interlace, png->profile, png->filter ) ) + if( vips__png_write( save->ready, + png_file->filename, png->compression, png->interlace, + png->profile, png->filter, save->strip ) ) return( -1 ); return( 0 ); @@ -223,7 +224,8 @@ vips_foreign_save_png_buffer_build( VipsObject *object ) return( -1 ); if( vips__png_write_buf( save->ready, &obuf, &olen, - png->compression, png->interlace, png->profile, png->filter ) ) + png->compression, png->interlace, png->profile, png->filter, + save->strip ) ) return( -1 ); /* vips__png_write_buf() makes a buffer that needs g_free(), not diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index 619ce725..0d35ba31 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -55,6 +55,9 @@ * 26/2/15 * - close the read down early for a header read ... this saves an * fd during file read, handy for large numbers of input images + * 31/7/16 + * - support --strip option + * */ /* @@ -808,8 +811,9 @@ write_png_block( VipsRegion *region, VipsRect *area, void *a ) /* Write a VIPS image to PNG. */ static int -write_vips( Write *write, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter ) +write_vips( Write *write, + int compress, int interlace, const char *profile, + VipsForeignPngFilter filter, gboolean strip ) { VipsImage *in = write->in; @@ -883,7 +887,8 @@ write_vips( Write *write, int compress, int interlace, const char *profile, /* Set ICC Profile. */ - if( profile ) { + if( profile && + !strip ) { if( strcmp( profile, "none" ) != 0 ) { void *data; size_t length; @@ -902,7 +907,8 @@ write_vips( Write *write, int compress, int interlace, const char *profile, PNG_COMPRESSION_TYPE_BASE, data, length ); } } - else if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { + else if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) && + !strip ) { void *data; size_t length; @@ -951,7 +957,7 @@ write_vips( Write *write, int compress, int interlace, const char *profile, int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter ) + VipsForeignPngFilter filter, gboolean strip ) { Write *write; @@ -970,7 +976,8 @@ vips__png_write( VipsImage *in, const char *filename, /* Convert it! */ - if( write_vips( write, compress, interlace, profile, filter ) ) { + if( write_vips( write, + compress, interlace, profile, filter, strip ) ) { vips_error( "vips2png", _( "unable to write \"%s\"" ), filename ); @@ -1026,7 +1033,7 @@ user_write_data( png_structp png_ptr, png_bytep data, png_size_t length ) int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, - const char *profile, VipsForeignPngFilter filter ) + const char *profile, VipsForeignPngFilter filter, gboolean strip ) { Write *write; @@ -1037,7 +1044,8 @@ vips__png_write_buf( VipsImage *in, /* Convert it! */ - if( write_vips( write, compression, interlace, profile, filter ) ) { + if( write_vips( write, + compression, interlace, profile, filter, strip ) ) { vips_error( "vips2png", "%s", _( "unable to write to buffer" ) ); diff --git a/libvips/foreign/vipspng.h b/libvips/foreign/vipspng.h index 053aad6b..62fe38ba 100644 --- a/libvips/foreign/vipspng.h +++ b/libvips/foreign/vipspng.h @@ -48,10 +48,10 @@ int vips__png_header_buffer( const void *buffer, size_t length, int vips__png_write( VipsImage *in, const char *filename, int compress, int interlace, const char *profile, - VipsForeignPngFilter filter ); + VipsForeignPngFilter filter, gboolean strip ); int vips__png_write_buf( VipsImage *in, void **obuf, size_t *olen, int compression, int interlace, - const char *profile, VipsForeignPngFilter filter ); + const char *profile, VipsForeignPngFilter filter, gboolean strip ); #ifdef __cplusplus } From 595f18cf641e2a6396e0946f57a60ba834d71bc5 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 1 Aug 2016 08:43:53 +0100 Subject: [PATCH 14/17] tiny speedup for vipsthumbnail --linear use scrgb for working space in vipsthumbnail, was xyz --- tools/vipsthumbnail.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 23b53dea..b6199d87 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -81,6 +81,8 @@ * - restore BandFmt after unpremultiply * 23/5/16 * - no need to guess max-alpha now premultiply does this for us + * 1/8/16 + * - use scRGB as the working space in linear mode */ #ifdef HAVE_CONFIG_H @@ -357,7 +359,7 @@ thumbnail_shrink( VipsObject *process, VipsImage *in ) { VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 ); VipsInterpretation interpretation = linear_processing ? - VIPS_INTERPRETATION_XYZ : VIPS_INTERPRETATION_sRGB; + VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB; /* TRUE if we've done the import of an ICC transform and still need to * export. From b6768a7e0fcc814125b73ea56716f84b7710111b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 1 Aug 2016 12:04:15 +0100 Subject: [PATCH 15/17] small speedup for reduceh --- libvips/iofuncs/region.c | 2 +- libvips/resample/reduceh.cpp | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index ff474c23..766f2104 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -893,7 +893,7 @@ vips_region_fill( VipsRegion *reg, VipsRect *r, VipsRegionFillFn fn, void *a ) * the pixels we need. If it does, we could copy them and only * generate the new ones. * - * However, we usually have neighboring regions on different threads, + * However, we usually have neighbouring regions on different threads, * so from the point of view of this thread, we will get no overlaps * on successive prepare requests. */ diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index d887240f..6ca45ab5 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -321,15 +321,30 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, VIPS_GATE_START( "vips_reduceh_gen: work" ); for( int y = 0; y < r->height; y ++ ) { + VipsPel *p0; VipsPel *q; + double X; q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); + X = r->left * reduceh->xshrink; + /* We want p0 to be the start (ie. x == 0) of the input + * scanline we are reading from. We can then calculate the p we + * need for each pixel with a single mul and avoid calling ADDR + * for each pixel. + * + * We can't get p0 directly with ADDR since it could be outside + * valid, so get the leftmost pixel in valid and subtract a + * bit. + */ + p0 = VIPS_REGION_ADDR( ir, ir->valid.left, r->top + y ) - + ir->valid.left * ps; + for( int x = 0; x < r->width; x++ ) { int ix = (int) X; - VipsPel *p = VIPS_REGION_ADDR( ir, ix, r->top + y ); + VipsPel *p = p0 + ix * ps; const int sx = X * VIPS_TRANSFORM_SCALE * 2; const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1); const int tx = (six + 1) >> 1; From b0e3035590beebbf8e8951247631f09209eccd4a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 1 Aug 2016 14:28:35 +0100 Subject: [PATCH 16/17] small cleanups to svgz support --- ChangeLog | 1 + TODO | 2 + configure.ac | 1 + libvips/foreign/svgload.c | 94 ++++++++++++++++++++++++--------------- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/ChangeLog b/ChangeLog index b5debe04..e31d008e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -31,6 +31,7 @@ - added vips_perlin(), generate Perlin noise - gif loader can write 1, 2, 3, or 4 bands depending on file contents - support --strip for pngsave +- add svgz support [Felix Bünemann] 30/7/16 started 8.3.3 - fix performance regression in 8.3.2, thanks Lovell diff --git a/TODO b/TODO index c9790b1c..a20ae074 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ +- can we hjave .svg.gz as a suffix? should be possible + - try: $ vips avg broken.jpg[fail] diff --git a/configure.ac b/configure.ac index 0a199762..c77b7a9c 100644 --- a/configure.ac +++ b/configure.ac @@ -1061,6 +1061,7 @@ PDF import with poppler-glib: $with_poppler (requires poppler-glib 0.16.0 or later) SVG import with librsvg-2.0: $with_rsvg (requires librsvg-2.0 2.34.0 or later) +zlib: $with_zlib file import with cfitsio: $with_cfitsio file import/export with libwebp: $with_libwebp (requires libwebp-0.1.3 or later) diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index a0d1606a..9fd813c9 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -2,6 +2,8 @@ * * 7/2/16 * - from svgload.c + * 1/8/16 felixbuenemann + * - add svgz support */ /* @@ -54,14 +56,21 @@ #include #include -/* Old librsvg versions don't include librsvg-features.h by default, - * while newer versions deprecate direct inclusion. + +/* Old librsvg versions don't include librsvg-features.h by default. + * Newer versions deprecate direct inclusion. */ #ifndef LIBRSVG_FEATURES_H #include #endif +/* A handy #define for we-will-handle-svgz. + */ #if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZLIB) +#define HANDLE_SVGZ +#endif + +#ifdef HANDLE_SVGZ #include #endif @@ -325,6 +334,8 @@ vips_foreign_load_svg_file_header( VipsForeignLoad *load ) static const char *vips_foreign_svg_suffs[] = { ".svg", + /* librsvg supports svgz directly, no need to check for zlib here. + */ #if LIBRSVG_CHECK_FEATURE(SVGZ) ".svgz", #endif @@ -377,65 +388,77 @@ typedef VipsForeignLoadSvgClass VipsForeignLoadSvgBufferClass; G_DEFINE_TYPE( VipsForeignLoadSvgBuffer, vips_foreign_load_svg_buffer, vips_foreign_load_svg_get_type() ); -#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZLIB) +#ifdef HANDLE_SVGZ static void * -vips_zalloc( void *opaque, unsigned items, unsigned size ) +vips_foreign_load_svg_zalloc( void *opaque, unsigned items, unsigned size ) { return( g_malloc0_n( items, size ) ); } static void -vips_zfree( void *opaque, void *ptr ) +vips_foreign_load_svg_zfree( void *opaque, void *ptr ) { return( g_free( ptr ) ); } -#endif +#endif /*HANDLE_SVGZ*/ static gboolean vips_foreign_load_svg_is_a_buffer( const void *buf, size_t len ) { - unsigned char *str = (unsigned char *) buf; + char *str; + +#ifdef HANDLE_SVGZ + /* If the buffer looks like a zip, deflate to here and then search + * that for = 18 && str[0] == 0x1f && str[1] == 0x8b ) { + if( len >= 18 && + str[0] == '\037' && + str[1] == '\213' ) { z_stream zs; - size_t opos = 0; - unsigned char obuf[224]; + size_t opos; - zs.zalloc = (alloc_func) vips_zalloc; - zs.zfree = (free_func) vips_zfree; + zs.zalloc = (alloc_func) vips_foreign_load_svg_zalloc; + zs.zfree = (free_func) vips_foreign_load_svg_zfree; zs.opaque = Z_NULL; - zs.next_in = str; + zs.next_in = (unsigned char *) str; zs.avail_in = len; - if( inflateInit2(&zs, 15 | 32) != Z_OK ) { - vips_error( "svgload", - "%s", _( "Zlib init failed" ) ); - return( -1 ); - } + /* There isn't really an error return from is_a_buffer() + */ + if( inflateInit2( &zs, 15 | 32 ) != Z_OK ) + return( FALSE ); + opos = 0; do { - zs.avail_out = sizeof(obuf) - opos; - zs.next_out = obuf + opos; - if( inflate(&zs, Z_NO_FLUSH) < Z_OK ) { - vips_error( "svgload", - "%s", _( "Zlib inflate failed" ) ); - return( -1 ); - } - opos = sizeof(obuf) - zs.avail_out; - } while( opos < sizeof(obuf) && zs.avail_out == 0 ); + zs.avail_out = sizeof( obuf ) - opos; + zs.next_out = (unsigned char *) obuf + opos; + if( inflate( &zs, Z_NO_FLUSH ) < Z_OK ) + return( FALSE ); + opos = sizeof( obuf ) - zs.avail_out; + } while( opos < sizeof( obuf ) && + zs.avail_in > 0 ); - inflateEnd(&zs); + inflateEnd( &zs ); str = obuf; len = opos; } -#endif +#endif /*HANDLE_SVGZ*/ /* SVG documents are very freeform. They normally look like: * @@ -456,14 +479,13 @@ vips_foreign_load_svg_is_a_buffer( const void *buf, size_t len ) return( 0 ); for( i = 0; i < 24; i++ ) if( !isascii( str[i] ) ) - return( 0 ); + return( FALSE ); - for( i = 0; i < 200 && i < len - 5; i++ ) { - if( strncmp( (const char *) str + i, " Date: Mon, 1 Aug 2016 14:57:33 +0100 Subject: [PATCH 17/17] add .svg.gz suffix allow .x.y suffixes --- TODO | 2 -- libvips/foreign/svgload.c | 3 ++- libvips/include/vips/util.h | 1 + libvips/iofuncs/util.c | 32 +++++++++++++++++++------------- test/images/vips-profile.svg.gz | Bin 0 -> 24962 bytes test/test_foreign.py | 3 +++ 6 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 test/images/vips-profile.svg.gz diff --git a/TODO b/TODO index a20ae074..c9790b1c 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -- can we hjave .svg.gz as a suffix? should be possible - - try: $ vips avg broken.jpg[fail] diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index 9fd813c9..aa055506 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -338,6 +338,7 @@ static const char *vips_foreign_svg_suffs[] = { */ #if LIBRSVG_CHECK_FEATURE(SVGZ) ".svgz", + ".svg.gz", #endif NULL }; @@ -482,7 +483,7 @@ vips_foreign_load_svg_is_a_buffer( const void *buf, size_t len ) return( FALSE ); for( i = 0; i < 200 && i < len - 5; i++ ) - if( strncasecmp( str + i, " m ) + return( FALSE ); + + return( strcasecmp( a + m - n, b ) == 0 ); +} + /* Test for string a starts string b. a is a known-good string, b may be * random data. */ @@ -461,30 +475,22 @@ int vips_filename_suffix_match( const char *path, const char *suffixes[] ) { char *basename; - char *suffix; char *q; - const char **p; int result; + const char **p; - /* Drop any directory components, we want ignore any '.' in there. + /* Drop any directory components. */ basename = g_path_get_basename( path ); - /* Zap any trailing options. + /* Zap any trailing [] options. */ if( (q = (char *) vips__find_rightmost_brackets( basename )) ) *q = '\0'; - /* And select just the '.' and to the right. - */ - if( (q = strrchr( basename, '.' )) ) - suffix = q; - else - suffix = basename; - result = 0; - for( p = suffixes; *p; p++ ) - if( g_ascii_strcasecmp( suffix, *p ) == 0 ) { + for( p = suffixes; *p; p++ ) + if( vips_iscasepostfix( basename, *p ) ) { result = 1; break; } diff --git a/test/images/vips-profile.svg.gz b/test/images/vips-profile.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..8ccb610f881106c557bfe831bca8770c338f2aec GIT binary patch literal 24962 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 PV?Ma4Mbe!dGLruTrKw~H literal 0 HcmV?d00001 diff --git a/test/test_foreign.py b/test/test_foreign.py index 80a84d8d..edbf9802 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -51,6 +51,7 @@ class TestForeign(unittest.TestCase): self.cmyk_pdf_file = "images/cmyktest.pdf" self.svg_file = "images/vips-profile.svg" self.svgz_file = "images/vips-profile.svgz" + self.svg_gz_file = "images/vips-profile.svg.gz" self.colour = Vips.Image.jpegload(self.jpeg_file) self.mono = self.colour.extract_band(1) @@ -501,6 +502,8 @@ class TestForeign(unittest.TestCase): self.file_loader("svgload", self.svgz_file, svg_valid) self.buffer_loader("svgload_buffer", self.svgz_file, svg_valid) + self.file_loader("svgload", self.svg_gz_file, svg_valid) + im = Vips.Image.new_from_file(self.svg_file) x = Vips.Image.new_from_file(self.svg_file, scale = 2) self.assertLess(abs(im.width * 2 - x.width), 2)