diff --git a/ChangeLog b/ChangeLog index e3a0ef8b..3c99096a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,13 @@ - support webp and zstd compression in tiff - loaders use "minimise" to close input files earlier - integrate support for oss-fuzz [omira-sch] +- add vips_switch() / vips_case() ... fast many-way ifthenelse +- better const handling for arithmetic operators fixes comparisons against out + of range values +- handle alpha in heifload / heifsave [meyermarcel] + +31/8/19 started 8.8.3 +- revert sharpen restoring the input colourspace 9/7/19 started 8.8.2 - better early shutdown in readers @@ -18,6 +25,17 @@ - fix build with GM - add locks for pdfium load - fix build with MSVC +- fix a problem with shinkv tail processing [angelmixu] +- fix a read one byte beyond buffer bug in jpegload +- make GIF parsing less strict +- better feof() handling in GIF load +- clip coding and interpretation on vips image read +- check image bounds for GIF load +- prevent over-pre-shrink in thumbnail [kleisauke] +- fix sharpen with sigma 0.5 [2h4dl] +- sharpen restores input colourspace +- verify bands/format for coded images +- improve data_length handling for jpeg metadata 24/5/19 started 8.8.1 - improve realpath() use on older libc diff --git a/configure.ac b/configure.ac index 5db7d1e2..77869c1e 100644 --- a/configure.ac +++ b/configure.ac @@ -560,18 +560,6 @@ if test x"$expat_found" = x"no"; then exit 1 fi -# enable vips7 C++ binding ... this defaults off, the vips8 C++ binding -# defaults on -AC_ARG_ENABLE([cpp7], - AS_HELP_STRING([--enable-cpp7], - [enable deprecated vips7 C++ binding (default: no)]), - [enable_cpp7=$enableval - ], - [enable_cpp7="no (default)" - ] -) -AM_CONDITIONAL(ENABLE_CPP7, [test x"$enable_cpp7" = x"yes"]) - # optional supporting libraries AC_ARG_WITH([gsf], @@ -1124,40 +1112,6 @@ if test x"$with_pangoft2" != x"no"; then ) fi -# install vips8 python -AC_ARG_ENABLE([pyvips8], - AS_HELP_STRING([--enable-pyvips8], - [install vips8 Python overrides (default: no)]), - [enable_pyvips8=$enableval - ], - [enable_pyvips8="no (default)" - ] -) - -if test x"$enable_pyvips8" = x"auto"; then - PKG_CHECK_EXISTS([pygobject-3.0 >= 3.13.0], - [enable_pyvips8=yes - ], - [AC_MSG_WARN([pygobject-3.0 not found; disabling vips8 python support]) - enable_pyvips8=no - ] - ) -fi - -if test x"$enable_pyvips8" = x"yes"; then - JD_PATH_PYTHON(2.7,, - [enable_pyvips8=no - AC_MSG_WARN([Python not found; disabling vips8 Python binding]) - ] - ) -fi - -if test x"$enable_pyvips8" = x"yes"; then - PKG_CHECK_MODULES(PYGOBJECT, [pygobject-3.0 >= 3.13.0]) -fi - -AM_CONDITIONAL(ENABLE_PYVIPS8, [test x"$enable_pyvips8" = x"yes"]) - # look for TIFF with pkg-config ... fall back to our tester # pkgconfig support for libtiff starts with libtiff-4 AC_ARG_WITH([tiff], @@ -1462,22 +1416,6 @@ image pyramid export: $with_gsf use libexif to load/save JPEG metadata: $with_libexif ]) -if test x"$found_introspection" = x"yes" -a "$VIPS_LIBDIR/girepository-1.0" != "$INTROSPECTION_TYPELIBDIR"; then - case "$VIPS_LIBDIR" in - /usr/local/Cellar/vips/*) - ;; # ignore for homebrew - *) - AC_MSG_RESULT([dnl -Vips-8.0.typelib will be installed to $VIPS_LIBDIR/girepository-1.0, but -your system repository seems to be $INTROSPECTION_TYPELIBDIR. -You may need to add this directory to your typelib path, for example: - - export GI_TYPELIB_PATH="$VIPS_LIBDIR/girepository-1.0" - ]) - ;; - esac -fi - if test x"$vips_os_win32" = x"yes"; then if test x"$have_g_win32_get_command_line" != x"yes"; then AC_MSG_RESULT([dnl diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am index 6169da14..edee8cbd 100644 --- a/fuzz/Makefile.am +++ b/fuzz/Makefile.am @@ -2,11 +2,14 @@ TESTS = \ test_fuzz.sh FUZZPROGS = \ + jpegsave_file_fuzzer \ jpegsave_buffer_fuzzer \ pngsave_buffer_fuzzer \ webpsave_buffer_fuzzer \ sharpen_fuzzer \ - thumbnail_fuzzer + thumbnail_fuzzer \ + smartcrop_fuzzer \ + mosaic_fuzzer AM_DEFAULT_SOURCE_EXT = .cc diff --git a/fuzz/jpegsave_buffer_fuzzer_corpus/.keep b/fuzz/common_fuzzer_corpus/.keep similarity index 100% rename from fuzz/jpegsave_buffer_fuzzer_corpus/.keep rename to fuzz/common_fuzzer_corpus/.keep diff --git a/fuzz/jpegsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5658586599915520 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5658586599915520 similarity index 100% rename from fuzz/jpegsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5658586599915520 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5658586599915520 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5673786296238080 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5673786296238080 new file mode 100644 index 00000000..447f0f65 Binary files /dev/null and b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5673786296238080 differ diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5759265708441600 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5759265708441600 new file mode 100644 index 00000000..5c4e7523 Binary files /dev/null and b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5759265708441600 differ diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_file_fuzzer-5662041322291200 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_file_fuzzer-5662041322291200 new file mode 100644 index 00000000..6e95e9ec Binary files /dev/null and b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_file_fuzzer-5662041322291200 differ diff --git a/fuzz/pngsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-pngsave_buffer_fuzzer-5078454764044288 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-pngsave_buffer_fuzzer-5078454764044288 similarity index 100% rename from fuzz/pngsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-pngsave_buffer_fuzzer-5078454764044288 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-pngsave_buffer_fuzzer-5078454764044288 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5203581631725568 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5203581631725568 new file mode 100644 index 00000000..af58a693 --- /dev/null +++ b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5203581631725568 @@ -0,0 +1 @@ +GIF8 =₯ \ No newline at end of file diff --git a/fuzz/sharpen_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5678720198639616 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5678720198639616 similarity index 100% rename from fuzz/sharpen_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5678720198639616 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5678720198639616 diff --git a/fuzz/sharpen_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5691855517253632 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5691855517253632 similarity index 100% rename from fuzz/sharpen_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5691855517253632 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5691855517253632 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-smartcrop_fuzzer-5687924892368896 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-smartcrop_fuzzer-5687924892368896 new file mode 100644 index 00000000..b4f3de62 Binary files /dev/null and b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-smartcrop_fuzzer-5687924892368896 differ diff --git a/fuzz/thumbnail_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5676300823429120 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5676300823429120 similarity index 100% rename from fuzz/thumbnail_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5676300823429120 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5676300823429120 diff --git a/fuzz/thumbnail_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5718717719117824 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5718717719117824 similarity index 100% rename from fuzz/thumbnail_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5718717719117824 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5718717719117824 diff --git a/fuzz/thumbnail_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5741423734816768 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5741423734816768 similarity index 100% rename from fuzz/thumbnail_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5741423734816768 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5741423734816768 diff --git a/fuzz/webpsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5207224829345792 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5207224829345792 similarity index 100% rename from fuzz/webpsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5207224829345792 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5207224829345792 diff --git a/fuzz/webpsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5674696418263040 b/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5674696418263040 similarity index 100% rename from fuzz/webpsave_buffer_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5674696418263040 rename to fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5674696418263040 diff --git a/fuzz/common_fuzzer_corpus/x-ascii.ppm b/fuzz/common_fuzzer_corpus/x-ascii.ppm new file mode 100644 index 00000000..aa81a14b --- /dev/null +++ b/fuzz/common_fuzzer_corpus/x-ascii.ppm @@ -0,0 +1,15 @@ +P2 +#vips2ppm - Fri Aug 23 12:48:07 2019 + +10 10 +255 +96 101 113 118 124 130 136 141 147 150 +81 87 98 101 107 112 117 123 130 135 +73 78 85 90 95 99 103 110 118 124 +46 51 60 67 73 81 87 94 103 109 +34 35 40 48 60 69 77 81 85 88 +28 26 31 36 45 54 59 64 69 72 +32 31 41 39 39 40 45 52 61 66 +38 38 47 42 38 36 38 43 49 53 +37 38 39 39 37 37 37 36 34 32 +36 36 38 36 35 34 35 32 28 25 diff --git a/fuzz/common_fuzzer_corpus/x.csv b/fuzz/common_fuzzer_corpus/x.csv new file mode 100644 index 00000000..640265ec --- /dev/null +++ b/fuzz/common_fuzzer_corpus/x.csv @@ -0,0 +1,10 @@ +96 101 113 118 124 130 136 141 147 150 +81 87 98 101 107 112 117 123 130 135 +73 78 85 90 95 99 103 110 118 124 +46 51 60 67 73 81 87 94 103 109 +34 35 40 48 60 69 77 81 85 88 +28 26 31 36 45 54 59 64 69 72 +32 31 41 39 39 40 45 52 61 66 +38 38 47 42 38 36 38 43 49 53 +37 38 39 39 37 37 37 36 34 32 +36 36 38 36 35 34 35 32 28 25 diff --git a/fuzz/common_fuzzer_corpus/x.mat b/fuzz/common_fuzzer_corpus/x.mat new file mode 100644 index 00000000..bed7279e --- /dev/null +++ b/fuzz/common_fuzzer_corpus/x.mat @@ -0,0 +1,11 @@ +10 10 +96 101 113 118 124 130 136 141 147 150 +81 87 98 101 107 112 117 123 130 135 +73 78 85 90 95 99 103 110 118 124 +46 51 60 67 73 81 87 94 103 109 +34 35 40 48 60 69 77 81 85 88 +28 26 31 36 45 54 59 64 69 72 +32 31 41 39 39 40 45 52 61 66 +38 38 47 42 38 36 38 43 49 53 +37 38 39 39 37 37 37 36 34 32 +36 36 38 36 35 34 35 32 28 25 diff --git a/fuzz/common_fuzzer_corpus/x.ppm b/fuzz/common_fuzzer_corpus/x.ppm new file mode 100644 index 00000000..cc0b6072 --- /dev/null +++ b/fuzz/common_fuzzer_corpus/x.ppm @@ -0,0 +1,6 @@ +P5 +#vips2ppm - Fri Aug 23 12:47:38 2019 + +10 10 +255 +`eqv|‚ˆ“–QWbekpu{‚‡INUZ_cgnv|.3LQDUWL]\SfcZolbzri|.(/)3#.9+8E7FM@QSHYVM`ZQf]Tg' %*$- *6(5=1?C6HG;OL@VOCW,!)!3$+2",0#-1#05(9>/B.""/##0$&/#'.!(.!*. --.+.)-," ,"!/##-!#,&+'-*+*'(#% \ No newline at end of file diff --git a/fuzz/jpegsave_file_fuzzer.cc b/fuzz/jpegsave_file_fuzzer.cc new file mode 100644 index 00000000..878dd9e6 --- /dev/null +++ b/fuzz/jpegsave_file_fuzzer.cc @@ -0,0 +1,62 @@ +#include + +extern "C" int +LLVMFuzzerInitialize( int *argc, char ***argv ) +{ + vips_concurrency_set( 1 ); + return( 0 ); +} + +static int +test_one_file( const char *name ) +{ + VipsImage *image; + void *buf; + size_t len; + + if( !(image = vips_image_new_from_file( name, + "access", VIPS_ACCESS_SEQUENTIAL, + NULL )) ) + return( 0 ); + + /* Skip big images. They are likely to timeout. + */ + if( image->Xsize > 1024 || + image->Ysize > 1024 || + image->Bands > 10 ) { + g_object_unref( image ); + return( 0 ); + } + + if( vips_jpegsave_buffer( image, &buf, &len, NULL ) ) { + g_object_unref( image ); + return( 0 ); + } + + g_free( buf ); + g_object_unref( image ); + + return( 0 ); +} + +extern "C" int +LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) +{ + char *name; + + if( !(name = vips__temp_name( "%s" )) ) + return( 0 ); + + if( !g_file_set_contents( name, (const char *) data, size, NULL ) || + test_one_file( name ) ) { + g_unlink( name ); + g_free( name ); + + return( 0 ); + } + + g_unlink( name ); + g_free( name ); + + return( 0 ); +} diff --git a/fuzz/mosaic_fuzzer.cc b/fuzz/mosaic_fuzzer.cc new file mode 100644 index 00000000..a7b7a12d --- /dev/null +++ b/fuzz/mosaic_fuzzer.cc @@ -0,0 +1,63 @@ +#include + +struct mosaic_opt { + guint8 dir : 1; + guint16 xref; + guint16 yref; + guint16 xsec; + guint16 ysec; +}; + +extern "C" int +LLVMFuzzerInitialize( int *argc, char ***argv ) +{ + vips_concurrency_set( 1 ); + return( 0 ); +} + +extern "C" int +LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) +{ + VipsImage *ref, *sec, *out; + struct mosaic_opt *opt; + double d; + + if( size < sizeof(struct mosaic_opt) ) + return( 0 ); + + if( !(ref = vips_image_new_from_buffer( data, size, "", NULL )) ) + return( 0 ); + + /* Skip big images. They are likely to timeout. + */ + if( ref->Xsize > 1024 || + ref->Ysize > 1024 || + ref->Bands > 10 ) { + g_object_unref( ref ); + return( 0 ); + } + + if( vips_rot180( ref, &sec, NULL ) ) { + g_object_unref( ref ); + return( 0 ); + } + + /* Extract some bytes from the tail to fuzz the arguments of the API. + */ + opt = (struct mosaic_opt *) (data + size - sizeof(struct mosaic_opt)); + + if( vips_mosaic( ref, sec, &out, (VipsDirection) opt->dir, + opt->xref, opt->yref, opt->xsec, opt->ysec, NULL ) ) { + g_object_unref( sec ); + g_object_unref( ref ); + return( 0 ); + } + + vips_max( out, &d, NULL ); + + g_object_unref( out ); + g_object_unref( sec ); + g_object_unref( ref ); + + return( 0 ); +} diff --git a/fuzz/pngsave_buffer_fuzzer_corpus/.keep b/fuzz/pngsave_buffer_fuzzer_corpus/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/fuzz/sharpen_fuzzer_corpus/.keep b/fuzz/sharpen_fuzzer_corpus/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/fuzz/smartcrop_fuzzer.cc b/fuzz/smartcrop_fuzzer.cc new file mode 100644 index 00000000..30e349d1 --- /dev/null +++ b/fuzz/smartcrop_fuzzer.cc @@ -0,0 +1,39 @@ +#include + +extern "C" int +LLVMFuzzerInitialize( int *argc, char ***argv ) +{ + vips_concurrency_set( 1 ); + return( 0 ); +} + +extern "C" int +LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) +{ + VipsImage *image, *out; + double d; + + if( !(image = vips_image_new_from_buffer( data, size, "", NULL )) ) + return( 0 ); + + /* Skip big images. They are likely to timeout. + */ + if( image->Xsize > 1024 || + image->Ysize > 1024 || + image->Bands > 10 ) { + g_object_unref( image ); + return( 0 ); + } + + if( vips_smartcrop( image, &out, 32, 32, NULL ) ) { + g_object_unref( image ); + return( 0 ); + } + + vips_min( out, &d, NULL ); + + g_object_unref( out ); + g_object_unref( image ); + + return( 0 ); +} diff --git a/fuzz/test_fuzz.sh b/fuzz/test_fuzz.sh index dd4decb5..e536e113 100755 --- a/fuzz/test_fuzz.sh +++ b/fuzz/test_fuzz.sh @@ -14,7 +14,7 @@ export VIPS_WARNING=0 ret=0 for fuzzer in *_fuzzer; do - find "${fuzzer}_corpus" -type f -not -empty -print0 \ + find "common_fuzzer_corpus" -type f -not -empty -print0 \ | xargs -0 -n1 "./$fuzzer" || ret=1 done diff --git a/fuzz/thumbnail_fuzzer_corpus/.keep b/fuzz/thumbnail_fuzzer_corpus/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/fuzz/webpsave_buffer_fuzzer_corpus/.keep b/fuzz/webpsave_buffer_fuzzer_corpus/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/libvips/arithmetic/boolean.c b/libvips/arithmetic/boolean.c index 1df7dab3..c4fcf18d 100644 --- a/libvips/arithmetic/boolean.c +++ b/libvips/arithmetic/boolean.c @@ -458,14 +458,11 @@ vips_boolean_const_build( VipsObject *object ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsUnary *unary = (VipsUnary *) object; - VipsUnaryConst *uconst = (VipsUnaryConst *) object; if( unary->in && vips_check_noncomplex( class->nickname, unary->in ) ) return( -1 ); - uconst->const_format = VIPS_FORMAT_INT; - if( VIPS_OBJECT_CLASS( vips_boolean_const_parent_class )-> build( object ) ) return( -1 ); @@ -474,9 +471,9 @@ vips_boolean_const_build( VipsObject *object ) } #define LOOPC( TYPE, OP ) { \ - TYPE *p = (TYPE *) in[0]; \ - TYPE *q = (TYPE *) out; \ - int *c = (int *) uconst->c_ready; \ + TYPE * restrict p = (TYPE *) in[0]; \ + TYPE * restrict q = (TYPE *) out; \ + int * restrict c = uconst->c_int; \ \ for( i = 0, x = 0; x < width; x++ ) \ for( b = 0; b < bands; b++, i++ ) \ @@ -484,9 +481,9 @@ vips_boolean_const_build( VipsObject *object ) } #define FLOOPC( TYPE, OP ) { \ - TYPE *p = (TYPE *) in[0]; \ - int *q = (int *) out; \ - int *c = (int *) uconst->c_ready; \ + TYPE * restrict p = (TYPE *) in[0]; \ + int * restrict q = (int *) out; \ + int * restrict c = uconst->c_int; \ \ for( i = 0, x = 0; x < width; x++ ) \ for( b = 0; b < bands; b++, i++ ) \ diff --git a/libvips/arithmetic/math2.c b/libvips/arithmetic/math2.c index 35ed16d0..cbcd2b99 100644 --- a/libvips/arithmetic/math2.c +++ b/libvips/arithmetic/math2.c @@ -338,14 +338,11 @@ vips_math2_const_build( VipsObject *object ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsUnary *unary = (VipsUnary *) object; - VipsUnaryConst *uconst = (VipsUnaryConst *) object; if( unary->in && vips_check_noncomplex( class->nickname, unary->in ) ) return( -1 ); - uconst->const_format = VIPS_FORMAT_DOUBLE; - if( VIPS_OBJECT_CLASS( vips_math2_const_parent_class )-> build( object ) ) return( -1 ); @@ -356,7 +353,7 @@ vips_math2_const_build( VipsObject *object ) #define LOOPC( IN, OUT, OP ) { \ IN * restrict p = (IN *) in[0]; \ OUT * restrict q = (OUT *) out; \ - double * restrict c = (double *) uconst->c_ready; \ + double * restrict c = uconst->c_double; \ \ for( i = 0, x = 0; x < width; x++ ) \ for( b = 0; b < bands; b++, i++ ) \ diff --git a/libvips/arithmetic/relational.c b/libvips/arithmetic/relational.c index c975f2af..f2f8f817 100644 --- a/libvips/arithmetic/relational.c +++ b/libvips/arithmetic/relational.c @@ -457,25 +457,18 @@ typedef VipsUnaryConstClass VipsRelationalConstClass; G_DEFINE_TYPE( VipsRelationalConst, vips_relational_const, VIPS_TYPE_UNARY_CONST ); -static int -vips_relational_const_build( VipsObject *object ) -{ - VipsUnary *unary = (VipsUnary *) object; - VipsUnaryConst *uconst = (VipsUnaryConst *) object; - - if( unary->in ) - uconst->const_format = unary->in->BandFmt; - - if( VIPS_OBJECT_CLASS( vips_relational_const_parent_class )-> - build( object ) ) - return( -1 ); - - return( 0 ); +#define RLOOPCI( TYPE, OP ) { \ + TYPE * restrict p = (TYPE *) in[0]; \ + int * restrict c = uconst->c_int; \ + \ + for( i = 0, x = 0; x < width; x++ ) \ + for( b = 0; b < bands; b++, i++ ) \ + out[i] = (p[i] OP c[b]) ? 255 : 0; \ } -#define RLOOPC( TYPE, OP ) { \ +#define RLOOPCF( TYPE, OP ) { \ TYPE * restrict p = (TYPE *) in[0]; \ - TYPE * restrict c = (TYPE *) uconst->c_ready; \ + double * restrict c = uconst->c_double; \ \ for( i = 0, x = 0; x < width; x++ ) \ for( b = 0; b < bands; b++, i++ ) \ @@ -486,7 +479,7 @@ vips_relational_const_build( VipsObject *object ) TYPE * restrict p = (TYPE *) in[0]; \ \ for( i = 0, x = 0; x < width; x++ ) { \ - TYPE * restrict c = (TYPE *) uconst->c_ready; \ + double * restrict c = uconst->c_double; \ \ for( b = 0; b < bands; b++, i++ ) { \ out[i] = OP( p[0], p[1], c[0], c[1]) ? 255 : 0; \ @@ -505,32 +498,64 @@ vips_relational_const_buffer( VipsArithmetic *arithmetic, VipsRelationalConst *rconst = (VipsRelationalConst *) arithmetic; VipsImage *im = arithmetic->ready[0]; int bands = im->Bands; + gboolean is_int = uconst->is_int && + vips_band_format_isint( im->BandFmt ); int i, x, b; switch( rconst->relational ) { - case VIPS_OPERATION_RELATIONAL_EQUAL: - SWITCH( RLOOPC, CLOOPC, ==, CEQUAL ); + case VIPS_OPERATION_RELATIONAL_EQUAL: + if( is_int ) { + SWITCH( RLOOPCI, CLOOPC, ==, CEQUAL ); + } + else { + SWITCH( RLOOPCF, CLOOPC, ==, CEQUAL ); + } break; case VIPS_OPERATION_RELATIONAL_NOTEQ: - SWITCH( RLOOPC, CLOOPC, !=, CNOTEQ ); + if( is_int ) { + SWITCH( RLOOPCI, CLOOPC, !=, CNOTEQ ); + } + else { + SWITCH( RLOOPCF, CLOOPC, !=, CNOTEQ ); + } break; - case VIPS_OPERATION_RELATIONAL_LESS: - SWITCH( RLOOPC, CLOOPC, <, CLESS ); + case VIPS_OPERATION_RELATIONAL_LESS: + if( is_int ) { + SWITCH( RLOOPCI, CLOOPC, <, CLESS ); + } + else { + SWITCH( RLOOPCF, CLOOPC, <, CLESS ); + } break; - case VIPS_OPERATION_RELATIONAL_LESSEQ: - SWITCH( RLOOPC, CLOOPC, <=, CLESSEQ ); + case VIPS_OPERATION_RELATIONAL_LESSEQ: + if( is_int ) { + SWITCH( RLOOPCI, CLOOPC, <=, CLESSEQ ); + } + else { + SWITCH( RLOOPCF, CLOOPC, <=, CLESSEQ ); + } break; - case VIPS_OPERATION_RELATIONAL_MORE: - SWITCH( RLOOPC, CLOOPC, >, CMORE ); + case VIPS_OPERATION_RELATIONAL_MORE: + if( is_int ) { + SWITCH( RLOOPCI, CLOOPC, >, CMORE ); + } + else { + SWITCH( RLOOPCF, CLOOPC, >, CMORE ); + } break; - case VIPS_OPERATION_RELATIONAL_MOREEQ: - SWITCH( RLOOPC, CLOOPC, >=, CMOREEQ ); + case VIPS_OPERATION_RELATIONAL_MOREEQ: + if( is_int ) { + SWITCH( RLOOPCI, CLOOPC, >=, CMOREEQ ); + } + else { + SWITCH( RLOOPCF, CLOOPC, >=, CMOREEQ ); + } break; default: @@ -551,7 +576,6 @@ vips_relational_const_class_init( VipsRelationalConstClass *class ) object_class->nickname = "relational_const"; object_class->description = _( "relational operations against a constant" ); - object_class->build = vips_relational_const_build; aclass->process_line = vips_relational_const_buffer; diff --git a/libvips/arithmetic/remainder.c b/libvips/arithmetic/remainder.c index b845b37e..75df8f4d 100644 --- a/libvips/arithmetic/remainder.c +++ b/libvips/arithmetic/remainder.c @@ -238,15 +238,11 @@ vips_remainder_const_build( VipsObject *object ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsUnary *unary = (VipsUnary *) object; - VipsUnaryConst *uconst = (VipsUnaryConst *) object; if( unary->in && vips_check_noncomplex( class->nickname, unary->in ) ) return( -1 ); - if( unary->in ) - uconst->const_format = unary->in->BandFmt; - if( VIPS_OBJECT_CLASS( vips_remainder_const_parent_class )-> build( object ) ) return( -1 ); @@ -259,7 +255,7 @@ vips_remainder_const_build( VipsObject *object ) #define IREMAINDERCONST( TYPE ) { \ TYPE * restrict p = (TYPE *) in[0]; \ TYPE * restrict q = (TYPE *) out; \ - TYPE * restrict c = (TYPE *) uconst->c_ready; \ + int * restrict c = uconst->c_int; \ \ for( i = 0, x = 0; x < width; x++ ) \ for( b = 0; b < bands; b++, i++ ) \ @@ -271,7 +267,7 @@ vips_remainder_const_build( VipsObject *object ) #define FREMAINDERCONST( TYPE ) { \ TYPE * restrict p = (TYPE *) in[0]; \ TYPE * restrict q = (TYPE *) out; \ - TYPE * restrict c = (TYPE *) uconst->c_ready; \ + int * restrict c = uconst->c_int; \ \ for( i = 0, x = 0; x < width; x++ ) \ for( b = 0; b < bands; b++, i++ ) { \ diff --git a/libvips/arithmetic/unaryconst.c b/libvips/arithmetic/unaryconst.c index b5cbf797..f098f169 100644 --- a/libvips/arithmetic/unaryconst.c +++ b/libvips/arithmetic/unaryconst.c @@ -2,6 +2,8 @@ * * 11/11/11 * - from arith_binary_const + * 21/8/19 + * - revise to fix out of range comparisons */ /* @@ -49,100 +51,6 @@ G_DEFINE_ABSTRACT_TYPE( VipsUnaryConst, vips_unary_const, VIPS_TYPE_UNARY ); -/* Cast a vector of double to a vector of TYPE, clipping to a range. - */ -#define CAST_CLIP( TYPE, N, X ) { \ - TYPE * restrict tq = (TYPE *) q; \ - \ - for( i = 0; i < m; i++ ) { \ - double v = p[VIPS_MIN( n - 1, i )]; \ - \ - tq[i] = (TYPE) VIPS_FCLIP( N, v, X ); \ - } \ -} - -/* Cast a vector of double to a vector of TYPE. - */ -#define CAST( TYPE ) { \ - TYPE * restrict tq = (TYPE *) q; \ - \ - for( i = 0; i < m; i++ ) \ - tq[i] = (TYPE) p[VIPS_MIN( n - 1, i )]; \ -} - -/* Cast a vector of double to a complex vector of TYPE. - */ -#define CASTC( TYPE ) { \ - TYPE * restrict tq = (TYPE *) q; \ - \ - for( i = 0; i < m; i++ ) { \ - tq[0] = (TYPE) p[VIPS_MIN( n - 1, i )]; \ - tq[1] = 0; \ - \ - tq += 2; \ - } \ -} - -/* Cast a n-band vector of double to a m-band vector in another format. - */ -static VipsPel * -make_pixel( VipsObject *obj, - int m, VipsBandFormat fmt, int n, double * restrict p ) -{ - VipsPel *q; - int i; - - if( !(q = VIPS_ARRAY( obj, m * vips_format_sizeof( fmt ), VipsPel )) ) - return( NULL ); - - switch( fmt ) { - case VIPS_FORMAT_CHAR: - CAST_CLIP( signed char, SCHAR_MIN, SCHAR_MAX ); - break; - - case VIPS_FORMAT_UCHAR: - CAST_CLIP( unsigned char, 0, UCHAR_MAX ); - break; - - case VIPS_FORMAT_SHORT: - CAST_CLIP( signed short, SCHAR_MIN, SCHAR_MAX ); - break; - - case VIPS_FORMAT_USHORT: - CAST_CLIP( unsigned short, 0, USHRT_MAX ); - break; - - case VIPS_FORMAT_INT: - CAST_CLIP( signed int, INT_MIN, INT_MAX ); - break; - - case VIPS_FORMAT_UINT: - CAST_CLIP( unsigned int, 0, UINT_MAX ); - break; - - case VIPS_FORMAT_FLOAT: - CAST( float ); - break; - - case VIPS_FORMAT_DOUBLE: - CAST( double ); - break; - - case VIPS_FORMAT_COMPLEX: - CASTC( float ); - break; - - case VIPS_FORMAT_DPCOMPLEX: - CASTC( double ); - break; - - default: - g_assert_not_reached(); - } - - return( q ); -} - static int vips_unary_const_build( VipsObject *object ) { @@ -161,27 +69,54 @@ vips_unary_const_build( VipsObject *object ) uconst->n = VIPS_MAX( uconst->n, unary->in->Bands ); arithmetic->base_bands = uconst->n; - if( unary->in && uconst->c ) { + if( unary->in && + uconst->c ) { if( vips_check_vector( class->nickname, uconst->c->n, unary->in ) ) return( -1 ); } - /* Some operations need the vector in the input type (eg. - * im_equal_vec() where the output type is always uchar and is useless - * for comparisons), some need it in the output type (eg. - * im_andimage_vec() where we want to get the double to an int so we - * can do bitwise-and without having to cast for each pixel), some - * need a fixed type (eg. im_powtra_vec(), where we want to keep it as - * double). + /* Some operations need int constants, for example boolean AND, SHIFT + * etc. * - * Therefore pass in the desired vector type as a param. + * Some can use int constants as an optimisation, for example (x < + * 12). It depends on the value though: obviously (x < 12.5) should + * not use the int form. + * + * For complex images, we double the vector length and set the + * imaginary part to 0. */ + if( uconst->c ) { + gboolean is_complex = + vips_band_format_iscomplex( unary->in->BandFmt ); + int step = is_complex ? 2 : 1; + int n = step * uconst->n; + double *c = (double *) uconst->c->data; - if( uconst->c ) - uconst->c_ready = make_pixel( (VipsObject *) uconst, - uconst->n, uconst->const_format, - uconst->c->n, (double *) uconst->c->data ); + int i; + + uconst->c_int = VIPS_ARRAY( object, n, int ); + uconst->c_double = VIPS_ARRAY( object, n, double ); + if( !uconst->c_int || + !uconst->c_double ) + return( -1 ); + memset( uconst->c_int, 0, n * sizeof( int ) ); + memset( uconst->c_double, 0, n * sizeof( double ) ); + + for( i = 0; i < n; i += step ) + uconst->c_double[i] = + c[VIPS_MIN( i / step, uconst->c->n - 1)]; + + for( i = 0; i < n; i += step ) + uconst->c_int[i] = uconst->c_double[i]; + + uconst->is_int = TRUE; + for( i = 0; i < n; i += step ) + if( uconst->c_int[i] != uconst->c_double[i] ) { + uconst->is_int = FALSE; + break; + } + } if( VIPS_OBJECT_CLASS( vips_unary_const_parent_class )-> build( object ) ) diff --git a/libvips/arithmetic/unaryconst.h b/libvips/arithmetic/unaryconst.h index 2d204426..b2c26135 100644 --- a/libvips/arithmetic/unaryconst.h +++ b/libvips/arithmetic/unaryconst.h @@ -59,16 +59,15 @@ typedef struct _VipsUnaryConst { */ VipsArea *c; - /* The format the constant should be cast to. Subclasses set this - * ready for unaryconst's build method. - */ - VipsBandFormat const_format; - - /* Our constant expanded to match arith->ready in size and - * const_format in type. + /* Our constant expanded to match arith->ready in size. We need int + * and double versions. + * + * is_int is TRUE if the two arrays are equal for every element. */ int n; - VipsPel *c_ready; + int *c_int; + double *c_double; + gboolean is_int; } VipsUnaryConst; diff --git a/libvips/colour/UCS2LCh.c b/libvips/colour/UCS2LCh.c index e6b46530..6fe2e6fe 100644 --- a/libvips/colour/UCS2LCh.c +++ b/libvips/colour/UCS2LCh.c @@ -127,7 +127,7 @@ make_hI( void ) for( i = 0; i < 361; i++ ) { int k; - for( k = 0; k < 360 && hl[j][k] <= i; k++ ) + for( k = 1; k < 360 && hl[j][k] <= i; k++ ) ; hI[j][i] = k - 1 + (i - hl[j][k - 1]) / diff --git a/libvips/colour/Yxy2XYZ.c b/libvips/colour/Yxy2XYZ.c index b15e24e3..04b2e135 100644 --- a/libvips/colour/Yxy2XYZ.c +++ b/libvips/colour/Yxy2XYZ.c @@ -7,7 +7,9 @@ * - gtkdoc * - cleanups * 20/9/12 - * redo as a class + * - redo as a class + * 29/8/19 + * - avoid /0 */ /* @@ -67,18 +69,26 @@ vips_Yxy2XYZ_line( VipsColour *colour, VipsPel *out, VipsPel **in, int width ) float x = p[1]; float y = p[2]; - double total; float X, Z; - p += 3; + if( x == 0.0 || + y == 0.0 ) { + X = 0.0; + Z = 0.0; + } + else { + double total; - total = Y / y; - X = x * total; - Z = (X - x * X - x * Y) / x; + total = Y / y; + X = x * total; + Z = (X - x * X - x * Y) / x; + } q[0] = X; q[1] = Y; q[2] = Z; + + p += 3; q += 3; } } diff --git a/libvips/colour/rad2float.c b/libvips/colour/rad2float.c index cecbb38c..7089d2d9 100644 --- a/libvips/colour/rad2float.c +++ b/libvips/colour/rad2float.c @@ -164,17 +164,10 @@ vips_rad2float_line( VipsColour *colour, VipsPel *out, VipsPel **in, int width ) COLR *inp = (COLR *) in[0]; COLOR *outbuf = (COLOR *) out; - colr_color(outbuf[0], inp[0]); - while (--width > 0) { - outbuf++; inp++; - if (inp[0][RED] == inp[-1][RED] && - inp[0][GRN] == inp[-1][GRN] && - inp[0][BLU] == inp[-1][BLU] && - inp[0][EXP] == inp[-1][EXP]) - copycolor(outbuf[0], outbuf[-1]); - else - colr_color(outbuf[0], inp[0]); - } + int i; + + for( i = 0; i < width; i++ ) + colr_color( outbuf[i], inp[i] ); } static void diff --git a/libvips/conversion/Makefile.am b/libvips/conversion/Makefile.am index 95b8d4fc..0f4dc365 100644 --- a/libvips/conversion/Makefile.am +++ b/libvips/conversion/Makefile.am @@ -1,6 +1,7 @@ noinst_LTLIBRARIES = libconversion.la libconversion_la_SOURCES = \ + switch.c \ transpose3d.c \ composite.cpp \ smartcrop.c \ @@ -44,4 +45,10 @@ libconversion_la_SOURCES = \ subsample.c \ zoom.c -AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ +# gcc annoyingly warns about clang pragmas, and does not support suppressing +# the warning with a gcc pragma +AM_CPPFLAGS = \ + -I${top_srcdir}/libvips/include \ + @VIPS_CFLAGS@ @VIPS_INCLUDES@ \ + -Wno-unknown-pragmas + diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 465c791d..51dcfb97 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -383,6 +383,7 @@ vips_conversion_operation_init( void ) extern GType vips_rot45_get_type( void ); extern GType vips_autorot_get_type( void ); extern GType vips_ifthenelse_get_type( void ); + extern GType vips_switch_get_type( void ); extern GType vips_recomb_get_type( void ); extern GType vips_bandmean_get_type( void ); extern GType vips_bandfold_get_type( void ); @@ -434,6 +435,7 @@ vips_conversion_operation_init( void ) vips_rot45_get_type(); vips_autorot_get_type(); vips_ifthenelse_get_type(); + vips_switch_get_type(); vips_recomb_get_type(); vips_bandmean_get_type(); vips_bandfold_get_type(); diff --git a/libvips/conversion/switch.c b/libvips/conversion/switch.c new file mode 100644 index 00000000..055b372b --- /dev/null +++ b/libvips/conversion/switch.c @@ -0,0 +1,260 @@ +/* switch between an array of images + * + * 28/7/19 + * - from maplut.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include + +typedef struct _VipsSwitch { + VipsOperation parent_instance; + + VipsArrayImage *tests; + VipsImage *out; + + int n; + +} VipsSwitch; + +typedef VipsOperationClass VipsSwitchClass; + +G_DEFINE_TYPE( VipsSwitch, vips_switch, VIPS_TYPE_OPERATION ); + +static int +vips_switch_gen( VipsRegion *or, void *seq, void *a, void *b, + gboolean *stop ) +{ + VipsRegion **ar = (VipsRegion **) seq; + VipsSwitch *swit = (VipsSwitch *) b; + VipsRect *r = &or->valid; + + int x, y, i; + VipsPel * restrict q; + size_t qls; + VipsPel * restrict p[256]; + size_t ls[256]; + + if( vips_reorder_prepare_many( or->im, ar, r ) ) + return( -1 ); + + g_assert( ar->im->BandFmt == VIPS_FORMAT_UCHAR ); + g_assert( ar->im->Bands == 1 ); + + for( i = 0; i < swit->n; i++ ) { + p[i] = VIPS_REGION_ADDR( ar[i], r->left, r->top ); + ls[i] = VIPS_REGION_LSKIP( ar[i] ); + } + + q = VIPS_REGION_ADDR( or, r->left, r->top ); + qls = VIPS_REGION_LSKIP( or ); + for( y = 0; y < r->height; y++ ) { + for( x = 0; x < r->width; x++ ) { + for( i = 0; i < swit->n; i++ ) + if( p[i][x] ) + break; + + q[x] = i; + } + + q += qls; + for( i = 0; i < swit->n; i++ ) + p[i] += ls[i]; + } + + return( 0 ); +} + +static int +vips_switch_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsSwitch *swit = (VipsSwitch *) object; + + VipsImage **tests; + VipsImage **decode; + VipsImage **format; + VipsImage **band; + VipsImage **size; + int i; + + g_object_set( object, "out", vips_image_new(), NULL ); + + if( VIPS_OBJECT_CLASS( vips_switch_parent_class )->build( object ) ) + return( -1 ); + + /* 255 rather than 256, since we want to reserve +1 as the no + * match value. + */ + tests = vips_area_get_data( &swit->tests->area, + NULL, &swit->n, NULL, NULL ); + if( swit->n > 255 || + swit->n < 1 ) { + vips_error( class->nickname, "%s", _( "bad number of tests" ) ); + return( -1 ); + } + + decode = (VipsImage **) vips_object_local_array( object, swit->n ); + format = (VipsImage **) vips_object_local_array( object, swit->n ); + band = (VipsImage **) vips_object_local_array( object, swit->n + 1 ); + size = (VipsImage **) vips_object_local_array( object, swit->n + 1 ); + + /* Decode RAD/LABQ etc. + */ + for( i = 0; i < swit->n; i++ ) + if( vips_image_decode( tests[i], &decode[i] ) ) + return( -1 ); + tests = decode; + + /* Must be uchar. + */ + for( i = 0; i < swit->n; i++ ) + if( vips_cast_uchar( tests[i], &format[i], NULL ) ) + return( -1 ); + tests = format; + + /* Images must match in size and bands. + */ + if( vips__bandalike_vec( class->nickname, tests, band, swit->n, 1 ) || + vips__sizealike_vec( band, size, swit->n ) ) + return( -1 ); + tests = size; + + if( tests[0]->Bands > 1 ) { + vips_error( class->nickname, + "%s", _( "test images not 1-band" ) ); + return( -1 ); + } + + if( vips_image_pipeline_array( swit->out, + VIPS_DEMAND_STYLE_THINSTRIP, tests ) ) + return( -1 ); + + if( vips_image_generate( swit->out, + vips_start_many, vips_switch_gen, vips_stop_many, + tests, swit ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_switch_class_init( VipsSwitchClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "switch"; + object_class->description = + _( "find the index of the first non-zero pixel in tests" ); + object_class->build = vips_switch_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_BOXED( class, "tests", 1, + _( "Tests" ), + _( "Table of images to test" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsSwitch, tests ), + VIPS_TYPE_ARRAY_IMAGE ); + + VIPS_ARG_IMAGE( class, "out", 2, + _( "Output" ), + _( "Output image" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsSwitch, out ) ); + +} + +static void +vips_switch_init( VipsSwitch *swit ) +{ +} + +static int +vips_switchv( VipsImage **tests, VipsImage **out, int n, va_list ap ) +{ + VipsArrayImage *tests_array; + int result; + + tests_array = vips_array_image_new( tests, n ); + result = vips_call_split( "switch", ap, tests_array, out ); + vips_area_unref( VIPS_AREA( tests_array ) ); + + return( result ); +} + +/** + * vips_switch: (method) + * @tests: (array length=n): test these images + * @out: (out): output index image + * @n: number of input images + * @...: %NULL-terminated list of optional named arguments + * + * The @tests images are evaluated and at each point the index of the first + * non-zero value is written to @out. If all @tests are false, the value + * (@n + 1) is written. + * + * Images in @tests must have one band. They are expanded to the + * bounding box of the set of images in @tests, and that size is used for + * @out. @tests can have up to 255 elements. + * + * Combine with vips_case() to make an efficient multi-way vips_ifthenelse(). + * + * See also: vips_maplut(), vips_case(), vips_ifthenelse(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_switch( VipsImage **tests, VipsImage **out, int n, ... ) +{ + va_list ap; + int result; + + va_start( ap, n ); + result = vips_switchv( tests, out, n, ap ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/convolution/sharpen.c b/libvips/convolution/sharpen.c index dc817575..795299a6 100644 --- a/libvips/convolution/sharpen.c +++ b/libvips/convolution/sharpen.c @@ -39,6 +39,9 @@ * - swap "radius" for "sigma", allows finer control * - allow a much greater range of parameters * - move to defaults suitable for screen output + * 28/8/19 + * - fix sigma 0.5 case (thanks 2h4dl) + * - restore input colourspace */ /* @@ -170,10 +173,11 @@ vips_sharpen_build( VipsObject *object ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsSharpen *sharpen = (VipsSharpen *) object; - VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 ); + VipsImage **t = (VipsImage **) vips_object_local_array( object, 8 ); VipsImage **args = (VipsImage **) vips_object_local_array( object, 2 ); VipsImage *in; + VipsInterpretation old_interpretation; int i; VIPS_GATE_START( "vips_sharpen_build: build" ); @@ -190,6 +194,7 @@ vips_sharpen_build( VipsObject *object ) in = sharpen->in; + old_interpretation = in->Type; if( vips_colourspace( in, &t[0], VIPS_INTERPRETATION_LABS, NULL ) ) return( -1 ); in = t[0]; @@ -199,10 +204,10 @@ vips_sharpen_build( VipsObject *object ) vips_check_format( class->nickname, in, VIPS_FORMAT_SHORT ) ) return( -1 ); - /* Stop at 20% of max ... a bit mean. We always sharpen a short, + /* Stop at 10% of max ... a bit mean. We always sharpen a short, * so there's no point using a float mask. */ - if( vips_gaussmat( &t[1], sharpen->sigma, 0.2, + if( vips_gaussmat( &t[1], sharpen->sigma, 0.1, "separable", TRUE, "precision", VIPS_PRECISION_INTEGER, NULL ) ) @@ -269,9 +274,6 @@ vips_sharpen_build( VipsObject *object ) NULL ) ) return( -1 ); - /* Set demand hints. FATSTRIP is good for us, as THINSTRIP will cause - * too many recalculations on overlaps. - */ t[5] = vips_image_new(); if( vips_image_pipeline_array( t[5], VIPS_DEMAND_STYLE_FATSTRIP, args ) ) @@ -287,7 +289,8 @@ vips_sharpen_build( VipsObject *object ) /* Reattach the rest. */ if( vips_bandjoin2( t[5], t[3], &t[6], NULL ) || - vips_image_write( t[6], sharpen->out ) ) + vips_colourspace( t[6], &t[7], old_interpretation, NULL ) || + vips_image_write( t[7], sharpen->out ) ) return( -1 ); VIPS_GATE_STOP( "vips_sharpen_build: build" ); diff --git a/libvips/foreign/exif.c b/libvips/foreign/exif.c index 759e0fdc..12b2d448 100644 --- a/libvips/foreign/exif.c +++ b/libvips/foreign/exif.c @@ -237,14 +237,25 @@ vips_exif_get_double( ExifData *ed, { ExifRational rv; ExifSRational srv; + double value; - if( !vips_exif_get_rational( ed, entry, component, &rv ) ) - *out = (double) rv.numerator / rv.denominator; - else if( !vips_exif_get_srational( ed, entry, component, &srv ) ) - *out = (double) srv.numerator / srv.denominator; + if( !vips_exif_get_rational( ed, entry, component, &rv ) ) { + if( rv.denominator == 0 ) + value = 0; + else + value = (double) rv.numerator / rv.denominator; + } + else if( !vips_exif_get_srational( ed, entry, component, &srv ) ) { + if( srv.denominator == 0 ) + value = 0; + else + value = (double) srv.numerator / srv.denominator; + } else return( -1 ); + *out = value; + return( 0 ); } @@ -681,7 +692,11 @@ vips_exif_set_double( ExifData *ed, ExifRational rv; rv = exif_get_rational( entry->data + offset, bo ); - old_value = (double) rv.numerator / rv.denominator; + if( rv.denominator == 0 ) + old_value = 0; + else + old_value = (double) rv.numerator / rv.denominator; + if( VIPS_FABS( old_value - value ) > 0.0001 ) { vips_exif_double_to_rational( value, &rv ); @@ -696,7 +711,11 @@ vips_exif_set_double( ExifData *ed, ExifSRational srv; srv = exif_get_srational( entry->data + offset, bo ); - old_value = (double) srv.numerator / srv.denominator; + if( srv.denominator == 0 ) + old_value = 0; + else + old_value = (double) srv.numerator / srv.denominator; + if( VIPS_FABS( old_value - value ) > 0.0001 ) { vips_exif_double_to_srational( value, &srv ); diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index a9b7cdc7..68ef31a2 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -27,6 +27,13 @@ * 24/7/19 * - close early on minimise * - close early on error + * 23/8/18 + * - allow GIF read errors during header scan + * - better feof() handling + * 27/8/19 + * - check image and frame bounds, since giflib does not + * 1/9/19 + * - improve early close again */ /* @@ -192,7 +199,6 @@ typedef struct _VipsForeignLoadGif { /* Params for DGifOpen(). Set by subclasses, called by base class in * _open(). */ - void *userPtr; InputFunc read_func; } VipsForeignLoadGif; @@ -203,6 +209,10 @@ typedef struct _VipsForeignLoadGifClass { /* Close and reopen gif->file. */ int (*open)( VipsForeignLoadGif *gif ); + + /* Close any underlying file resource. + */ + void (*close)( VipsForeignLoadGif *gif ); } VipsForeignLoadGifClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif, vips_foreign_load_gif, @@ -297,32 +307,14 @@ vips_foreign_load_gif_error( VipsForeignLoadGif *gif ) vips_foreign_load_gif_error_vips( gif, error ); } -static void -vips_foreign_load_gif_close( VipsForeignLoadGif *gif ) -{ -#ifdef HAVE_GIFLIB_5 - if( gif->file ) { - int error; - - if( DGifCloseFile( gif->file, &error ) == GIF_ERROR ) - vips_foreign_load_gif_error_vips( gif, error ); - gif->file = NULL; - } -#else - if( gif->file ) { - if( DGifCloseFile( gif->file ) == GIF_ERROR ) - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - gif->file = NULL; - } -#endif -} - static void vips_foreign_load_gif_dispose( GObject *gobject ) { VipsForeignLoadGif *gif = (VipsForeignLoadGif *) gobject; + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif ); - vips_foreign_load_gif_close( gif ); + class->close( gif ); VIPS_UNREF( gif->frame ); VIPS_UNREF( gif->previous ); @@ -373,6 +365,23 @@ vips_foreign_load_gif_is_a( const char *filename ) return( 0 ); } +/* Make sure delays is allocated and large enough. + */ +static void +vips_foreign_load_gif_allocate_delays( VipsForeignLoadGif *gif ) +{ + if( gif->n_pages >= gif->delays_length ) { + int old = gif->delays_length; + int i; + + gif->delays_length = gif->delays_length + gif->n_pages + 64; + gif->delays = (int *) g_realloc( gif->delays, + gif->delays_length * sizeof( int ) ); + for( i = old; i < gif->delays_length; i++ ) + gif->delays[i] = 40; + } +} + static int vips_foreign_load_gif_ext_next( VipsForeignLoadGif *gif, GifByteType **extension ) @@ -406,15 +415,19 @@ vips_foreign_load_gif_code_next( VipsForeignLoadGif *gif, /* Quickly scan an image record. */ static int -vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) +vips_foreign_load_gif_scan_image( VipsForeignLoadGif *gif ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); GifFileType *file = gif->file; - ColorMapObject *map = file->Image.ColorMap ? - file->Image.ColorMap : file->SColorMap; + ColorMapObject *map; GifByteType *extension; + if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + /* Check that the frame looks sane. Perhaps giflib checks * this for us. */ @@ -432,6 +445,7 @@ vips_foreign_load_gif_scan_image_record( VipsForeignLoadGif *gif ) /* Test for a non-greyscale colourmap for this frame. */ + map = file->Image.ColorMap ? file->Image.ColorMap : file->SColorMap; if( !gif->has_colour && map ) { int i; @@ -525,18 +539,6 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) gif->has_transparency = TRUE; } - if( gif->n_pages >= gif->delays_length ) { - int old = gif->delays_length; - int i; - - gif->delays_length = - gif->delays_length + gif->n_pages + 64; - gif->delays = (int *) g_realloc( gif->delays, - gif->delays_length * sizeof( int ) ); - for( i = old; i < gif->delays_length; i++ ) - gif->delays[i] = 40; - } - /* giflib uses centiseconds, we use ms. */ gif->delays[gif->n_pages] = @@ -610,49 +612,35 @@ vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image ) } /* Attempt to quickly scan a GIF and discover what we need for our header. We - * need to scan the whole file to get n_pages, transparency and colour. + * need to scan the whole file to get n_pages, transparency and colour. + * + * Don't flag errors during header scan. Many GIFs do not follow spec. */ static int -vips_foreign_load_gif_header( VipsForeignLoad *load ) +vips_foreign_load_gif_scan( VipsForeignLoadGif *gif ) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadGifClass *gif_class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); GifRecordType record; - if( gif_class->open( gif ) ) - return( -1 ); - gif->n_pages = 0; do { - if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } + if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) + continue; switch( record ) { case IMAGE_DESC_RECORD_TYPE: - if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - - if( vips_foreign_load_gif_scan_image_record( gif ) ) - return( -1 ); - + (void) vips_foreign_load_gif_scan_image( gif ); gif->n_pages += 1; - + vips_foreign_load_gif_allocate_delays( gif ); break; case EXTENSION_RECORD_TYPE: /* We need to fetch the extensions to check for * cmaps and transparency. */ - if( vips_foreign_load_gif_scan_extension( gif ) ) - return( -1 ); + (void) vips_foreign_load_gif_scan_extension( gif ); break; case TERMINATE_RECORD_TYPE: @@ -678,11 +666,34 @@ vips_foreign_load_gif_header( VipsForeignLoad *load ) return( -1 ); } - /* And set the output vips header from what we've learned. - */ - if( vips_foreign_load_gif_set_header( gif, load->out ) ) + return( 0 ); +} + +/* Scan the GIF and set the libvips header. We always close after scan, even + * on an error. + */ +static int +vips_foreign_load_gif_header( VipsForeignLoad *load ) +{ + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + + if( class->open( gif ) ) return( -1 ); + if( vips_foreign_load_gif_scan( gif ) ) { + class->close( gif ); + return( -1 ); + } + + if( vips_foreign_load_gif_set_header( gif, load->out ) ) { + class->close( gif ); + return( -1 ); + } + + class->close( gif ); + return( 0 ); } @@ -749,6 +760,11 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) { GifFileType *file = gif->file; + if( DGifGetImageDesc( file ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } + /* Update the colour map for this frame. */ vips_foreign_load_gif_build_cmap( gif ); @@ -774,7 +790,28 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), VIPS_IMAGE_SIZEOF_IMAGE( gif->frame ) ); - if( file->Image.Interlace ) { + /* giflib does not check that the Left / Top / Width / Height for this + * Image is inside the canvas. + * + * We could clip against the canvas, but for now, just ignore out of + * bounds frames. Watch for int overflow too. + */ + if( file->Image.Left < 0 || + file->Image.Left > VIPS_MAX_COORD || + file->Image.Width <= 0 || + file->Image.Width > VIPS_MAX_COORD || + file->Image.Left + file->Image.Width > file->SWidth || + file->Image.Top < 0 || + file->Image.Top > VIPS_MAX_COORD || + file->Image.Height <= 0 || + file->Image.Height > VIPS_MAX_COORD || + file->Image.Top + file->Image.Height > file->SHeight ) { + VIPS_DEBUG_MSG( "vips_foreign_load_gif_render: " + "out of bounds frame of %d x %d pixels at %d x %d\n", + file->Image.Width, file->Image.Height, + file->Image.Left, file->Image.Top ); + } + else if( file->Image.Interlace ) { int i; VIPS_DEBUG_MSG( "vips_foreign_load_gif_render: " @@ -888,11 +925,6 @@ vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif ) VIPS_DEBUG_MSG( "vips_foreign_load_gif_next_page: " "IMAGE_DESC_RECORD_TYPE\n" ); - if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); - return( -1 ); - } - if( vips_foreign_load_gif_render( gif ) ) return( -1 ); @@ -1013,7 +1045,10 @@ vips_foreign_load_gif_generate( VipsRegion *or, static void vips_foreign_load_gif_minimise( VipsObject *object, VipsForeignLoadGif *gif ) { - vips_foreign_load_gif_close( gif ); + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif ); + + class->close( gif ); } static int @@ -1025,8 +1060,6 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 4 ); - /* Rewind. - */ if( class->open( gif ) ) return( -1 ); @@ -1080,25 +1113,38 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) static int vips_foreign_load_gif_open( VipsForeignLoadGif *gif ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); + #ifdef HAVE_GIFLIB_5 { int error; - if( !(gif->file = DGifOpen( gif->userPtr, gif->read_func, &error )) ) { + if( !(gif->file = DGifOpen( gif, gif->read_func, &error )) ) { vips_foreign_load_gif_error_vips( gif, error ); return( -1 ); } } -#else - if( !(gif->file = DGifOpen( gif->userPtr, gif->read_func )) ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - return( -1 ); +#else + if( !(gif->file = DGifOpen( gif, gif->read_func )) ) { + vips_foreign_load_gif_error_vips( gif, GifLastError() ); + return( -1 ); } #endif gif->eof = FALSE; gif->current_page = 0; + /* giflib does no checking of image dimensions, not even for 0. + */ + if( gif->file->SWidth <= 0 || + gif->file->SWidth > VIPS_MAX_COORD || + gif->file->SHeight <= 0 || + gif->file->SHeight > VIPS_MAX_COORD ) { + vips_error( class->nickname, + "%s", _( "image size out of bounds" ) ); + return( -1 ); + } + /* Allocate a line buffer now that we have the GIF width. */ VIPS_FREE( gif->line ) @@ -1108,6 +1154,26 @@ vips_foreign_load_gif_open( VipsForeignLoadGif *gif ) return( 0 ); } +static void +vips_foreign_load_gif_close( VipsForeignLoadGif *gif ) +{ +#ifdef HAVE_GIFLIB_5 + if( gif->file ) { + int error; + + if( DGifCloseFile( gif->file, &error ) == GIF_ERROR ) + vips_foreign_load_gif_error_vips( gif, error ); + gif->file = NULL; + } +#else + if( gif->file ) { + if( DGifCloseFile( gif->file ) == GIF_ERROR ) + vips_foreign_load_gif_error_vips( gif, GifLastError() ); + gif->file = NULL; + } +#endif +} + static void vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) { @@ -1121,6 +1187,7 @@ vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) gobject_class->get_property = vips_object_get_property; gif_class->open = vips_foreign_load_gif_open; + gif_class->close = vips_foreign_load_gif_close; load_class->header = vips_foreign_load_gif_header; load_class->load = vips_foreign_load_gif_load; @@ -1157,6 +1224,8 @@ vips_foreign_load_gif_init( VipsForeignLoadGif *gif ) gif->loop = 0; gif->comment = NULL; gif->dispose = 0; + + vips_foreign_load_gif_allocate_delays( gif ); } typedef struct _VipsForeignLoadGifFile { @@ -1177,40 +1246,35 @@ typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass; G_DEFINE_TYPE( VipsForeignLoadGifFile, vips_foreign_load_gif_file, vips_foreign_load_gif_get_type() ); -static void -vips_foreign_load_gif_file_dispose( GObject *gobject ) -{ - VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gobject; - - VIPS_FREEF( fclose, file->fp ); - - G_OBJECT_CLASS( vips_foreign_load_gif_file_parent_class )-> - dispose( gobject ); -} - /* Our input function for file open. We can't use DGifOpenFileName(), since * that just calls open() and won't work with unicode on win32. We can't use * DGifOpenFileHandle() since that's an fd from open() and you can't pass those * across DLL boundaries on Windows. */ -static int -vips_giflib_file_read( GifFileType *file, GifByteType *buffer, int n ) +static int +vips_giflib_file_read( GifFileType *gfile, GifByteType *buffer, int n ) { - FILE *fp = (FILE *) file->UserData; + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) gfile->UserData; + VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; - return( (int) fread( (void *) buffer, 1, n, fp ) ); + if( feof( file->fp ) ) + gif->eof = TRUE; + + return( (int) fread( (void *) buffer, 1, n, file->fp ) ); } static int vips_foreign_load_gif_file_header( VipsForeignLoad *load ) { - VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) load; + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); if( VIPS_FOREIGN_LOAD_CLASS( vips_foreign_load_gif_file_parent_class )->header( load ) ) { /* Close early if header read fails in our base class. */ - VIPS_FREEF( fclose, file->fp ); + class->close( gif ); return( -1 ); } @@ -1219,32 +1283,41 @@ vips_foreign_load_gif_file_header( VipsForeignLoad *load ) /* We have to have _open() as a vfunc since our base class needs to be able to * make two scans of the gif (scan for header, then scan for pixels), so we - * must be able to close and reopen (or rewind). + * must be able to close and reopen. */ static int vips_foreign_load_gif_file_open( VipsForeignLoadGif *gif ) { VipsForeignLoad *load = (VipsForeignLoad *) gif; VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); - if( !file->fp ) { - if( !(file->fp = - vips__file_open_read( file->filename, NULL, FALSE )) ) - return( -1 ); + class->close( gif ); - VIPS_SETSTR( load->out->filename, file->filename ); - } - else - rewind( file->fp ); + if( !(file->fp = + vips__file_open_read( file->filename, NULL, FALSE )) ) + return( -1 ); + + VIPS_SETSTR( load->out->filename, file->filename ); - vips_foreign_load_gif_close( gif ); - gif->userPtr = file->fp; gif->read_func = vips_giflib_file_read; return( VIPS_FOREIGN_LOAD_GIF_CLASS( vips_foreign_load_gif_file_parent_class )->open( gif ) ); } +static void +vips_foreign_load_gif_file_close( VipsForeignLoadGif *gif ) +{ + VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; + + VIPS_FOREIGN_LOAD_GIF_CLASS( + vips_foreign_load_gif_file_parent_class )->close( gif ); + + VIPS_FREEF( fclose, file->fp ); +} + static const char *vips_foreign_gif_suffs[] = { ".gif", NULL @@ -1260,7 +1333,6 @@ vips_foreign_load_gif_file_class_init( VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) class; - gobject_class->dispose = vips_foreign_load_gif_file_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -1273,6 +1345,7 @@ vips_foreign_load_gif_file_class_init( load_class->header = vips_foreign_load_gif_file_header; gif_class->open = vips_foreign_load_gif_file_open; + gif_class->close = vips_foreign_load_gif_file_close; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), @@ -1314,26 +1387,31 @@ G_DEFINE_TYPE( VipsForeignLoadGifBuffer, vips_foreign_load_gif_buffer, static int vips_giflib_buffer_read( GifFileType *file, GifByteType *buf, int n ) { - VipsForeignLoadGifBuffer *buffer = - (VipsForeignLoadGifBuffer *) file->UserData; + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) file->UserData; + VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif; size_t will_read = VIPS_MIN( n, buffer->bytes_to_go ); memcpy( buf, buffer->p, will_read ); buffer->p += will_read; buffer->bytes_to_go -= will_read; - return( will_read ); + if( will_read == 0 ) + gif->eof = TRUE; + + return( will_read ); } static int vips_foreign_load_gif_buffer_open( VipsForeignLoadGif *gif ) { VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif; + VipsForeignLoadGifClass *class = + (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif ); + + class->close( gif ); - vips_foreign_load_gif_close( gif ); buffer->p = buffer->buf->data; buffer->bytes_to_go = buffer->buf->length; - gif->userPtr = gif; gif->read_func = vips_giflib_buffer_read;; return( VIPS_FOREIGN_LOAD_GIF_CLASS( diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index f468b1ed..2b8ed2f3 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -7,6 +7,8 @@ * 24/7/19 * - close early on minimise * - close early on error + * 1/9/19 [meyermarcel] + * - handle alpha */ /* @@ -86,6 +88,10 @@ typedef struct _VipsForeignLoadHeif { */ int n_top; + /* TRUE for RGBA ... otherwise, RGB. + */ + gboolean has_alpha; + /* Size of final output image. */ int width; @@ -260,7 +266,6 @@ vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif, static int vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) { - gboolean has_alpha; int bands; int i; /* Surely, 16 metadata items will be enough for anyone. @@ -275,10 +280,12 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) if( vips_foreign_load_heif_set_page( heif, heif->page, FALSE ) ) return( -1 ); - /* FIXME ... never seen this return TRUE on any image, strangely. - */ - has_alpha = heif_image_handle_has_alpha_channel( heif->handle ); - bands = has_alpha ? 4 : 3; + heif->has_alpha = heif_image_handle_has_alpha_channel( heif->handle ); +#ifdef DEBUG + printf( "heif_image_handle_has_alpha_channel() = %d\n", + heif->has_alpha ); +#endif /*DEBUG*/ + bands = heif->has_alpha ? 4 : 3; /* FIXME .. need to test XMP and IPCT. */ @@ -617,13 +624,11 @@ vips_foreign_load_heif_generate( VipsRegion *or, if( !heif->img ) { struct heif_error error; struct heif_decoding_options *options; + enum heif_chroma chroma = heif->has_alpha ? + heif_chroma_interleaved_RGBA : + heif_chroma_interleaved_RGB; - /* Decode the image to 24bit interleaved. - * - * FIXME What will this do for RGBA? Or is alpha always - * separate? - * - * Only disable transforms if we have been able to fetch the + /* Only disable transforms if we have been able to fetch the * untransformed dimensions. */ options = heif_decoding_options_alloc(); @@ -631,7 +636,7 @@ vips_foreign_load_heif_generate( VipsRegion *or, options->ignore_transformations = !heif->autorotate; #endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/ error = heif_decode_image( heif->handle, &heif->img, - heif_colorspace_RGB, heif_chroma_interleaved_RGB, + heif_colorspace_RGB, chroma, options ); heif_decoding_options_free( options ); if( error.code ) { diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index 05a7a516..be425d72 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -4,6 +4,8 @@ * - from niftisave.c * 3/7/19 [lovell] * - add "compression" option + * 1/9/19 [meyermarcel] + * - save alpha when necessary */ /* @@ -199,10 +201,8 @@ vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page ) #ifdef HAVE_HEIF_ENCODING_OPTIONS_ALLOC options = heif_encoding_options_alloc(); - /* FIXME .. should be an option, though I don't know of any way to - * test it - */ - options->save_alpha_channel = 1; + if( vips_image_hasalpha( save->ready ) ) + options->save_alpha_channel = 1; #else /*!HAVE_HEIF_ENCODING_OPTIONS_ALLOC*/ options = NULL; #endif /*HAVE_HEIF_ENCODING_OPTIONS_ALLOC*/ @@ -336,14 +336,19 @@ vips_foreign_save_heif_build( VipsObject *object ) * here and write a frame each time it fills. */ error = heif_image_create( heif->page_width, heif->page_height, - heif_colorspace_RGB, heif_chroma_interleaved_RGB, &heif->img ); + heif_colorspace_RGB, + vips_image_hasalpha( save->ready ) ? + heif_chroma_interleaved_RGBA : + heif_chroma_interleaved_RGB, + &heif->img ); if( error.code ) { vips__heif_error( &error ); return( -1 ); } error = heif_image_add_plane( heif->img, heif_channel_interleaved, - heif->page_width, heif->page_height, 24 ); + heif->page_width, heif->page_height, + vips_image_hasalpha( save->ready ) ? 32 : 24 ); if( error.code ) { vips__heif_error( &error ); return( -1 ); @@ -394,7 +399,7 @@ vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *class ) foreign_class->suffs = vips__heif_suffs; - save_class->saveable = VIPS_SAVEABLE_RGB; + save_class->saveable = VIPS_SAVEABLE_RGBA_ONLY; save_class->format_table = vips_heif_bandfmt; VIPS_ARG_INT( class, "Q", 10, diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index aacb6918..5cd5c295 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -313,7 +313,7 @@ find_chroma_subsample( struct jpeg_decompress_struct *cinfo ) } static int -attach_blob( VipsImage *im, const char *field, void *data, int data_length ) +attach_blob( VipsImage *im, const char *field, void *data, size_t data_length ) { /* Only use the first one. */ @@ -339,18 +339,21 @@ attach_blob( VipsImage *im, const char *field, void *data, int data_length ) * the real XMP. */ static int -attach_xmp_blob( VipsImage *im, void *data, int data_length ) +attach_xmp_blob( VipsImage *im, void *data, size_t data_length ) { char *p = (char *) data; int i; - if( !vips_isprefix( "http", p ) ) + if( data_length < 4 || + !vips_isprefix( "http", p ) ) return( 0 ); /* Search for a null char within the first few characters. 80 * should be plenty for a basic URL. + * + * -2 for the extra null. */ - for( i = 0; i < 80; i++ ) + for( i = 0; i < VIPS_MIN( 80, data_length - 2 ); i++ ) if( !p[i] ) break; if( p[i] ) @@ -499,7 +502,7 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) for( p = cinfo->marker_list; p; p = p->next ) { #ifdef DEBUG { - printf( "read_jpeg_header: seen %d bytes of APP%d\n", + printf( "read_jpeg_header: seen %u bytes of APP%d\n", p->data_length, p->marker - JPEG_APP0 ); @@ -596,7 +599,7 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) default: #ifdef DEBUG printf( "read_jpeg_header: " - "ignoring %d byte APP%d block\n", + "ignoring %u byte APP%d block\n", p->data_length, p->marker - JPEG_APP0 ); #endif /*DEBUG*/ break; diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index bf2dffde..888f9d50 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -323,7 +323,10 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load ) vips_foreign_load_pdf_set_image( pdf, load->out ); - /* Convert the background to the image format. + /* Convert the background to the image format. + * + * FIXME ... we probably should convert this to pre-multiplied BGRA + * to match the Cairo convention. See vips__cairo2rgba(). */ if( !(pdf->ink = vips__vector_to_ink( class->nickname, load->out, @@ -747,8 +750,8 @@ vips_foreign_load_pdf_is_a( const char *filename ) * you can scale the rendering from the default 1 point == 1 pixel by * setting @scale. * - * Use @background to set the background colour, including transparency. The - * default is 255 (solid white). + * Use @background to set the background RGBA colour. The default is 255 + * (solid white), use eg. 0 for a transparent background. * * The operation fills a number of header fields with metadata, for example * "pdf-author". They may be useful. diff --git a/libvips/foreign/vips2jpeg.c b/libvips/foreign/vips2jpeg.c index d941b961..03b97de5 100644 --- a/libvips/foreign/vips2jpeg.c +++ b/libvips/foreign/vips2jpeg.c @@ -776,9 +776,6 @@ empty_output_buffer( j_compress_ptr cinfo ) METHODDEF(void) init_destination( j_compress_ptr cinfo ) { - OutputBuffer *buf = (OutputBuffer *) cinfo->dest; - - vips_dbuf_init( &buf->dbuf ); empty_output_buffer( cinfo ); } @@ -845,6 +842,7 @@ buf_dest( j_compress_ptr cinfo, void **obuf, size_t *olen ) /* Save output parameters. */ + vips_dbuf_init( &buf->dbuf ); buf->obuf = obuf; buf->olen = olen; } diff --git a/libvips/foreign/webp2vips.c b/libvips/foreign/webp2vips.c index c12cdc46..90bf3a04 100644 --- a/libvips/foreign/webp2vips.c +++ b/libvips/foreign/webp2vips.c @@ -547,7 +547,9 @@ read_header( Read *read, VipsImage *out ) } if( read->width <= 0 || - read->height <= 0 ) { + read->height <= 0 || + read->width > 0x3FFF || + read->height > 0x3FFF ) { vips_error( "webp", "%s", _( "bad image dimensions" ) ); return( -1 ); } diff --git a/libvips/histogram/Makefile.am b/libvips/histogram/Makefile.am index bd903644..2e901388 100644 --- a/libvips/histogram/Makefile.am +++ b/libvips/histogram/Makefile.am @@ -4,6 +4,7 @@ libhistogram_la_SOURCES = \ histogram.c \ phistogram.h \ maplut.c \ + case.c \ hist_unary.c \ hist_unary.h \ hist_cum.c \ diff --git a/libvips/histogram/case.c b/libvips/histogram/case.c new file mode 100644 index 00000000..8ed97a97 --- /dev/null +++ b/libvips/histogram/case.c @@ -0,0 +1,313 @@ +/* use pixel values to pick cases from an array of images + * + * 28/7/19 + * - from maplut.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include + +typedef struct _VipsCase { + VipsOperation parent_instance; + + VipsImage *index; + VipsArrayImage *cases; + VipsImage *out; + int n; + +} VipsCase; + +typedef VipsOperationClass VipsCaseClass; + +G_DEFINE_TYPE( VipsCase, vips_case, VIPS_TYPE_OPERATION ); + +static int +vips_case_gen( VipsRegion *or, void *seq, void *a, void *b, + gboolean *stop ) +{ + VipsRegion **ar = (VipsRegion **) seq; + VipsCase *cas = (VipsCase *) b; + VipsRect *r = &or->valid; + VipsRegion *index = ar[cas->n]; + + int x, y, i; + VipsPel * restrict ip; + VipsPel * restrict q; + size_t ils; + size_t qls; + int hist[256]; + VipsPel * restrict p[256]; + size_t ls[256]; + size_t ps; + + if( vips_region_prepare( index, r ) ) + return( -1 ); + + g_assert( index->im->BandFmt == VIPS_FORMAT_UCHAR ); + g_assert( index->im->Bands == 1 ); + + /* Histogram of index region, so we know which of our inputs we will + * need to prepare. + */ + memset( hist, 0, cas->n * sizeof( int ) ); + ip = VIPS_REGION_ADDR( index, r->left, r->top ); + ils = VIPS_REGION_LSKIP( index ); + for( y = 0; y < r->height; y++ ) { + for( x = 0; x < r->width; x++ ) { + int v = VIPS_MIN( ip[x], cas->n - 1 ); + + hist[v] += 1; + } + + ip += ils; + } + + for( i = 0; i < cas->n; i++ ) + if( hist[i] ) { + if( vips_region_prepare( ar[i], r ) ) + return( -1 ); + p[i] = VIPS_REGION_ADDR( ar[i], r->left, r->top ); + ls[i] = VIPS_REGION_LSKIP( ar[i] ); + } + + ip = VIPS_REGION_ADDR( index, r->left, r->top ); + q = VIPS_REGION_ADDR( or, r->left, r->top ); + qls = VIPS_REGION_LSKIP( or ); + ps = VIPS_IMAGE_SIZEOF_PEL( or->im ); + for( y = 0; y < r->height; y++ ) { + int k; + + k = 0; + for( x = 0; x < r->width; x++ ) { + int v = VIPS_MIN( ip[x], cas->n - 1 ); + VipsPel * restrict pv = p[v]; + + int j; + + for( j = 0; j < ps; j++ ) { + q[k] = pv[k]; + k += 1; + } + } + + ip += ils; + q += qls; + for( i = 0; i < cas->n; i++ ) + if( hist[i] ) + p[i] += ls[i]; + } + + return( 0 ); +} + +static int +vips_case_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsCase *cas = (VipsCase *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 ); + + VipsImage *index; + VipsImage **cases; + VipsImage **decode; + VipsImage **format; + VipsImage **band; + VipsImage **size; + int i; + + g_object_set( object, "out", vips_image_new(), NULL ); + + if( VIPS_OBJECT_CLASS( vips_case_parent_class )->build( object ) ) + return( -1 ); + + index = cas->index; + cases = vips_area_get_data( &cas->cases->area, + NULL, &cas->n, NULL, NULL ); + if( cas->n > 256 || + cas->n < 1 ) { + vips_error( class->nickname, "%s", _( "bad number of cases" ) ); + return( -1 ); + } + if( index->Bands > 1 ) { + vips_error( class->nickname, + "%s", _( "index image not 1-band" ) ); + return( -1 ); + } + + /* Cast @index to u8 to make the index image. + */ + if( vips_cast( index, &t[0], VIPS_FORMAT_UCHAR, NULL ) ) + return( -1 ); + index = t[0]; + + decode = (VipsImage **) vips_object_local_array( object, cas->n ); + format = (VipsImage **) vips_object_local_array( object, cas->n ); + band = (VipsImage **) vips_object_local_array( object, cas->n + 1 ); + size = (VipsImage **) vips_object_local_array( object, cas->n + 1 ); + + /* Decode RAD/LABQ etc. + */ + for( i = 0; i < cas->n; i++ ) + if( vips_image_decode( cases[i], &decode[i] ) ) + return( -1 ); + cases = decode; + + /* case images must match in format, size and bands. + * + * We want everything sized up to the size of the index image, so add + * that to the end of the set of images for sizealike. + */ + band[cas->n] = index; + g_object_ref( index ); + if( vips__formatalike_vec( cases, format, cas->n ) || + vips__bandalike_vec( class->nickname, + format, band, cas->n, 1 ) || + vips__sizealike_vec( band, size, cas->n + 1 ) ) + return( -1 ); + cases = size; + + if( vips_image_pipeline_array( cas->out, + VIPS_DEMAND_STYLE_THINSTRIP, cases ) ) + return( -1 ); + + cas->out->BandFmt = cases[0]->BandFmt; + cas->out->Bands = cases[0]->Bands; + cas->out->Type = cases[0]->Type; + + if( vips_image_generate( cas->out, + vips_start_many, vips_case_gen, vips_stop_many, + cases, cas ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_case_class_init( VipsCaseClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "case"; + object_class->description = + _( "use pixel values to pick cases from an array of images" ); + object_class->build = vips_case_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_IMAGE( class, "index", 1, + _( "index" ), + _( "Index image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsCase, index ) ); + + VIPS_ARG_BOXED( class, "cases", 2, + _( "cases" ), + _( "Array of case images" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsCase, cases ), + VIPS_TYPE_ARRAY_IMAGE ); + + VIPS_ARG_IMAGE( class, "out", 3, + _( "Output" ), + _( "Output image" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsCase, out ) ); + +} + +static void +vips_case_init( VipsCase *cas ) +{ +} + +static int +vips_casev( VipsImage *index, VipsImage **cases, VipsImage **out, int n, + va_list ap ) +{ + VipsArrayImage *array; + int result; + + array = vips_array_image_new( cases, n ); + result = vips_call_split( "case", ap, index, array, out ); + vips_area_unref( VIPS_AREA( array ) ); + + return( result ); +} + +/** + * vips_case: (method) + * @index: index image + * @cases: (array length=n): array of case images + * @out: (out): output image + * @n: number of case images + * @...: %NULL-terminated list of optional named arguments + * + * Use values in @index to select pixels from @cases. + * + * @index must have one band. @cases can have up to 256 elements. Values in + * @index greater than or equal to @n use the final image in @cases. The + * images in @cases must have either one band or the same number of bands. + * The output image is the same size as @index. Images in @cases are + * expanded to the smallest common format and number of bands. + * + * Combine this with vips_switch() to make something like a case statement or + * a multi-way vips_ifthenelse(). + * + * See also: vips_maplut(), vips_switch(), vips_ifthenelse(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_case( VipsImage *index, VipsImage **cases, VipsImage **out, int n, ... ) +{ + va_list ap; + int result; + + va_start( ap, n ); + result = vips_casev( index, cases, out, n, ap ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/histogram/histogram.c b/libvips/histogram/histogram.c index 13ef1ce8..a7c51b15 100644 --- a/libvips/histogram/histogram.c +++ b/libvips/histogram/histogram.c @@ -251,6 +251,7 @@ void vips_histogram_operation_init( void ) { extern GType vips_maplut_get_type( void ); + extern GType vips_case_get_type( void ); extern GType vips_percent_get_type( void ); extern GType vips_hist_cum_get_type( void ); extern GType vips_hist_norm_get_type( void ); @@ -262,15 +263,16 @@ vips_histogram_operation_init( void ) extern GType vips_hist_entropy_get_type( void ); extern GType vips_stdif_get_type( void ); - vips_maplut_get_type(); - vips_percent_get_type(); - vips_stdif_get_type(); - vips_hist_cum_get_type(); - vips_hist_norm_get_type(); - vips_hist_equal_get_type(); - vips_hist_plot_get_type(); - vips_hist_match_get_type(); - vips_hist_local_get_type(); - vips_hist_ismonotonic_get_type(); + vips_maplut_get_type(); + vips_case_get_type(); + vips_percent_get_type(); + vips_stdif_get_type(); + vips_hist_cum_get_type(); + vips_hist_norm_get_type(); + vips_hist_equal_get_type(); + vips_hist_plot_get_type(); + vips_hist_match_get_type(); + vips_hist_local_get_type(); + vips_hist_ismonotonic_get_type(); vips_hist_entropy_get_type(); } diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index eaa7ff22..109b456e 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -262,6 +262,8 @@ int vips_recomb( VipsImage *in, VipsImage **out, VipsImage *m, ... ) int vips_ifthenelse( VipsImage *cond, VipsImage *in1, VipsImage *in2, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_switch( VipsImage **tests, VipsImage **out, int n, ... ) + __attribute__((sentinel)); int vips_flatten( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); diff --git a/libvips/include/vips/histogram.h b/libvips/include/vips/histogram.h index 53d46b15..039b9d94 100644 --- a/libvips/include/vips/histogram.h +++ b/libvips/include/vips/histogram.h @@ -40,6 +40,8 @@ extern "C" { int vips_maplut( VipsImage *in, VipsImage **out, VipsImage *lut, ... ) __attribute__((sentinel)); +int vips_mapimage( VipsImage *in, VipsImage **out, VipsImage **lut, int n, ... ) + __attribute__((sentinel)); int vips_percent( VipsImage *in, double percent, int *threshold, ... ) __attribute__((sentinel)); int vips_stdif( VipsImage *in, VipsImage **out, int width, int height, ... ) @@ -62,6 +64,10 @@ int vips_hist_ismonotonic( VipsImage *in, gboolean *out, ... ) int vips_hist_entropy( VipsImage *in, double *out, ... ) __attribute__((sentinel)); +int vips_case( VipsImage *index, VipsImage **cases, VipsImage **out, int n, + ... ) + __attribute__((sentinel)); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index cc23e674..79bf8b1a 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -1454,12 +1454,15 @@ vips_image_set_blob_copy( VipsImage *image, { void *data_copy; + /* Cap at 100mb for sanity. + */ if( !data || - length == 0 ) + length == 0 || + length > 100 * 1024 * 1024 ) return; /* We add an extra, secret null byte at the end, just in case this blob - * is read as a C string. The libtiff reader (for example) attaches + * is read as a C string. The libtiff reader attaches * XMP XML as a blob, for example. */ if( !(data_copy = vips_malloc( NULL, length + 1 )) ) diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index 83988c78..5b43f101 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -25,6 +25,8 @@ * - use O_TMPFILE, if available * 23/7/18 * - escape ASCII control characters in XML + * 29/8/19 + * - verify bands/format for coded images */ /* @@ -348,8 +350,8 @@ vips__read_header_bytes( VipsImage *im, unsigned char *from ) from += 4; if( im->magic != VIPS_MAGIC_INTEL && im->magic != VIPS_MAGIC_SPARC ) { - vips_error( "VipsImage", _( "\"%s\" is not a VIPS image" ), - im->filename ); + vips_error( "VipsImage", + _( "\"%s\" is not a VIPS image" ), im->filename ); return( -1 ); } @@ -383,10 +385,51 @@ vips__read_header_bytes( VipsImage *im, unsigned char *from ) 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 + /* Coding and Type have missing values, so we look up in the enum. + */ + im->Type = g_enum_get_value( + g_type_class_ref( VIPS_TYPE_INTERPRETATION ), + im->Type ) ? + im->Type : VIPS_INTERPRETATION_ERROR; + im->Coding = g_enum_get_value( + g_type_class_ref( VIPS_TYPE_CODING ), + im->Coding ) ? + im->Coding : VIPS_CODING_ERROR; + + /* Offset, Res, etc. don't affect vips file layout, just * pixel interpretation, don't clip them. */ + /* Coding values imply Bands and BandFmt settings --- make sure they + * are sane. + */ + switch( im->Coding ) { + case VIPS_CODING_NONE: + break; + + case VIPS_CODING_LABQ: + if( im->Bands != 4 || + im->BandFmt != VIPS_FORMAT_UCHAR ) { + vips_error( "VipsImage", + "%s", _( "malformed LABQ image" ) ); + return( -1 ); + } + break; + + case VIPS_CODING_RAD: + if( im->Bands != 4 || + im->BandFmt != VIPS_FORMAT_UCHAR ) { + vips_error( "VipsImage", + "%s", _( "malformed RAD image" ) ); + return( -1 ); + } + break; + + default: + g_assert_not_reached(); + break; + } + return( 0 ); } diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index 89fbf48a..c38be69b 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -22,6 +22,8 @@ * - don't force import CMYK, since colourspace knows about it now * 24/4/19 * - support multi-page (animated) images + * 27/8/19 kleisauke + * - prevent over-pre-shrink in thumbnail */ /* @@ -368,11 +370,20 @@ vips_thumbnail_calculate_common_shrink( VipsThumbnail *thumbnail, { double hshrink; double vshrink; + double shrink; vips_thumbnail_calculate_shrink( thumbnail, width, height, &hshrink, &vshrink ); - return( VIPS_MIN( hshrink, vshrink ) ); + shrink = VIPS_MIN( hshrink, vshrink ); + + /* We don't want to shrink so much that we send an axis to 0. + */ + if( shrink > thumbnail->input_width || + shrink > thumbnail->input_height ) + shrink = 1.0; + + return( shrink ); } /* Find the best jpeg preload shrink. @@ -464,37 +475,21 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) factor = 1.0; - if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) { + if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) factor = vips_thumbnail_find_jpegshrink( thumbnail, thumbnail->input_width, thumbnail->input_height ); - - g_info( "loading jpeg with factor %g pre-shrink", factor ); - } else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) || vips_isprefix( "VipsForeignLoadOpenslide", - thumbnail->loader ) ) { + thumbnail->loader ) ) factor = vips_thumbnail_find_pyrlevel( thumbnail, thumbnail->input_width, thumbnail->input_height ); - - g_info( "loading pyr level %g", factor ); - } - else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ) { - factor = 1.0 / - vips_thumbnail_calculate_common_shrink( thumbnail, - thumbnail->input_width, - thumbnail->page_height ); - - g_info( "loading PDF with factor %g pre-scale", factor ); - } - else if( vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) { - factor = 1.0 / - vips_thumbnail_calculate_common_shrink( thumbnail, - thumbnail->input_width, - thumbnail->input_height ); - - g_info( "loading SVG with factor %g pre-scale", factor ); - } - else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { + else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || + vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) || + vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) + factor = vips_thumbnail_calculate_common_shrink( thumbnail, + thumbnail->input_width, + thumbnail->page_height ); + if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { /* 'factor' is a gboolean which enables thumbnail load instead * of image load. * @@ -505,16 +500,9 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) factor = 1.0; else factor = 0.0; - } - else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) { - factor = 1.0 / - vips_thumbnail_calculate_common_shrink( thumbnail, - thumbnail->input_width, - thumbnail->page_height ); - g_info( "loading webp with factor %g pre-scale", factor ); - } + g_info( "loading with factor %g pre-shrink", factor ); if( !(im = class->open( thumbnail, factor )) ) return( NULL ); @@ -952,7 +940,7 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor ) vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) { return( vips_image_new_from_file( file->filename, "access", VIPS_ACCESS_SEQUENTIAL, - "scale", factor, + "scale", 1.0 / factor, NULL ) ); } else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { @@ -1150,7 +1138,7 @@ vips_thumbnail_buffer_open( VipsThumbnail *thumbnail, double factor ) buffer->buf->data, buffer->buf->length, buffer->option_string, "access", VIPS_ACCESS_SEQUENTIAL, - "scale", factor, + "scale", 1.0 / factor, NULL ) ); } else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { diff --git a/m4/python.m4 b/m4/python.m4 deleted file mode 100644 index 4c3210e2..00000000 --- a/m4/python.m4 +++ /dev/null @@ -1,239 +0,0 @@ -## Imported from pygobject at commit 5737a9ec4bf4d9d07a7e3994d91abf9077b342cc. -## Automake's built-in version has problems on multiarch systems. -## this one is commonly used with AM_PATH_PYTHONDIR ... -dnl AM_CHECK_PYMOD(MODNAME [,SYMBOL [,ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]]]) -dnl Check if a module containing a given symbol is visible to python. -AC_DEFUN([AM_CHECK_PYMOD], -[AC_REQUIRE([AM_PATH_PYTHON]) -py_mod_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'` -AC_MSG_CHECKING(for ifelse([$2],[],,[$2 in ])python module $1) -AC_CACHE_VAL(py_cv_mod_$py_mod_var, [ -ifelse([$2],[], [prog=" -import sys -try: - import $1 -except ImportError: - sys.exit(1) -except: - sys.exit(0) -sys.exit(0)"], [prog=" -import $1 -$1.$2"]) -if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC - then - eval "py_cv_mod_$py_mod_var=yes" - else - eval "py_cv_mod_$py_mod_var=no" - fi -]) -py_val=`eval "echo \`echo '$py_cv_mod_'$py_mod_var\`"` -if test "x$py_val" != xno; then - AC_MSG_RESULT(yes) - ifelse([$3], [],, [$3 -])dnl -else - AC_MSG_RESULT(no) - ifelse([$4], [],, [$4 -])dnl -fi -]) - -dnl a macro to check for ability to create python extensions -dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE]) -dnl function also defines PYTHON_INCLUDES -AC_DEFUN([AM_CHECK_PYTHON_HEADERS], -[AC_REQUIRE([AM_PATH_PYTHON]) -AC_MSG_CHECKING(for headers required to compile python extensions) -dnl deduce PYTHON_INCLUDES -if test "x$PYTHON_INCLUDES" = x; then - PYTHON_CONFIG=`which $PYTHON`-config - if test -x "$PYTHON_CONFIG"; then - PYTHON_INCLUDES=`$PYTHON_CONFIG --includes 2>/dev/null` - else - PYTHON_INCLUDES=`$PYTHON -c "import distutils.sysconfig, sys; sys.stdout.write(distutils.sysconfig.get_python_inc(True))"` - PYTHON_INCLUDES="-I$PYTHON_INCLUDES" - fi -fi -AC_SUBST(PYTHON_INCLUDES) -dnl check if the headers exist: -save_CPPFLAGS="$CPPFLAGS" -CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES" -AC_TRY_CPP([#include ],dnl -[AC_MSG_RESULT(found) -$1],dnl -[AC_MSG_RESULT(not found) -$2]) -CPPFLAGS="$save_CPPFLAGS" -]) - -dnl a macro to check for ability to embed python -dnl AM_CHECK_PYTHON_LIBS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE]) -dnl function also defines PYTHON_LIBS -AC_DEFUN([AM_CHECK_PYTHON_LIBS], -[AC_REQUIRE([AM_PATH_PYTHON]) -AC_MSG_CHECKING(for libraries required to embed python) -dnl deduce PYTHON_LIBS -py_prefix=`$PYTHON -c "import sys; sys.stdout.write(sys.prefix)"` -if test "x$PYTHON_LIBS" = x; then - PYTHON_CONFIG=`which $PYTHON`-config - if test -x "$PYTHON_CONFIG"; then - PYTHON_LIBS=`$PYTHON_CONFIG --ldflags 2>/dev/null` - else - PYTHON_LIBS="-L${py_prefix}/lib -lpython${PYTHON_VERSION}" - fi -fi -if test "x$PYTHON_LIB_LOC" = x; then - PYTHON_LIB_LOC="${py_prefix}/lib" -fi -AC_SUBST(PYTHON_LIBS) -AC_SUBST(PYTHON_LIB_LOC) -dnl check if the headers exist: -save_LIBS="$LIBS" -LIBS="$LIBS $PYTHON_LIBS" -AC_TRY_LINK_FUNC(Py_Initialize, dnl - [LIBS="$save_LIBS"; AC_MSG_RESULT(yes); $1], dnl - [LIBS="$save_LIBS"; AC_MSG_RESULT(no); $2]) - -]) - -# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 -# Free Software Foundation, Inc. -# -# This file is free software; the Free Software Foundation -# gives unlimited permission to copy and/or distribute it, -# with or without modifications, as long as this notice is preserved. - -# JD_PATH_PYTHON([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) -# --------------------------------------------------------------------------- -# Adds support for distributing Python modules and packages. To -# install modules, copy them to $(pythondir), using the python_PYTHON -# automake variable. To install a package with the same name as the -# automake package, install to $(pkgpythondir), or use the -# pkgpython_PYTHON automake variable. -# -# The variables $(pyexecdir) and $(pkgpyexecdir) are provided as -# locations to install python extension modules (shared libraries). -# Another macro is required to find the appropriate flags to compile -# extension modules. -# -# If your package is configured with a different prefix to python, -# users will have to add the install directory to the PYTHONPATH -# environment variable, or create a .pth file (see the python -# documentation for details). -# -# If the MINIMUM-VERSION argument is passed, AM_PATH_PYTHON will -# cause an error if the version of python installed on the system -# doesn't meet the requirement. MINIMUM-VERSION should consist of -# numbers and dots only. -AC_DEFUN([JD_PATH_PYTHON], - [ - dnl Find a Python interpreter. Python versions prior to 2.0 are not - dnl supported - m4_define_default([_AM_PYTHON_INTERPRETER_LIST], - [python python2 python2.7 python3 python3.5 python3.4 python3.3 python3.2 python3.1 python3.0]) - - m4_if([$1],[],[ - dnl No version check is needed. - # Find any Python interpreter. - if test -z "$PYTHON"; then - AC_PATH_PROGS([PYTHON], _AM_PYTHON_INTERPRETER_LIST, :) - fi - am_display_PYTHON=python - ], [ - dnl A version check is needed. - if test -n "$PYTHON"; then - # If the user set $PYTHON, use it and don't search something else. - AC_MSG_CHECKING([whether $PYTHON version >= $1]) - AM_PYTHON_CHECK_VERSION([$PYTHON], [$1], - [AC_MSG_RESULT(yes)], - [AC_MSG_ERROR(too old)]) - am_display_PYTHON=$PYTHON - else - # Otherwise, try each interpreter until we find one that satisfies - # VERSION. - AC_CACHE_CHECK([for a Python interpreter with version >= $1], - [am_cv_pathless_PYTHON],[ - for am_cv_pathless_PYTHON in _AM_PYTHON_INTERPRETER_LIST none; do - test "$am_cv_pathless_PYTHON" = none && break - AM_PYTHON_CHECK_VERSION([$am_cv_pathless_PYTHON], [$1], [break]) - done]) - # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON. - if test "$am_cv_pathless_PYTHON" = none; then - PYTHON=: - else - AC_PATH_PROG([PYTHON], [$am_cv_pathless_PYTHON]) - fi - am_display_PYTHON=$am_cv_pathless_PYTHON - fi - ]) - - if test "$PYTHON" = :; then - dnl Run any user-specified action, or abort. - m4_default([$3], [AC_MSG_ERROR([no suitable Python interpreter found])]) - else - - dnl Query Python for its version number. Getting [:3] seems to be - dnl the best way to do this; it's what "site.py" does in the standard - dnl library. - - AC_CACHE_CHECK([for $am_display_PYTHON version], [am_cv_python_version], - [am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[[:3]])"`]) - AC_SUBST([PYTHON_VERSION], [$am_cv_python_version]) - - dnl Use the values of $prefix and $exec_prefix for the corresponding - dnl values of PYTHON_PREFIX and PYTHON_EXEC_PREFIX. These are made - dnl distinct variables so they can be overridden if need be. However, - dnl general consensus is that you shouldn't need this ability. - - AC_SUBST([PYTHON_PREFIX], ['${prefix}']) - AC_SUBST([PYTHON_EXEC_PREFIX], ['${exec_prefix}']) - - dnl At times (like when building shared libraries) you may want - dnl to know which OS platform Python thinks this is. - - AC_CACHE_CHECK([for $am_display_PYTHON platform], [am_cv_python_platform], - [am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"`]) - AC_SUBST([PYTHON_PLATFORM], [$am_cv_python_platform]) - - - dnl Set up 4 directories: - - dnl pythondir -- where to install python scripts. This is the - dnl site-packages directory, not the python standard library - dnl directory like in previous automake betas. This behavior - dnl is more consistent with lispdir.m4 for example. - dnl Query distutils for this directory. distutils does not exist in - dnl Python 1.5, so we fall back to the hardcoded directory if it - dnl doesn't work. - AC_CACHE_CHECK([for $am_display_PYTHON script directory], - [am_cv_python_pythondir], - [am_cv_python_pythondir=`$PYTHON -c "from distutils import sysconfig; print(sysconfig.get_python_lib(0,0,prefix='$PYTHON_PREFIX'))" 2>/dev/null || - echo "$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages"`]) - AC_SUBST([pythondir], [$am_cv_python_pythondir]) - - dnl pkgpythondir -- $PACKAGE directory under pythondir. Was - dnl PYTHON_SITE_PACKAGE in previous betas, but this naming is - dnl more consistent with the rest of automake. - - AC_SUBST([pkgpythondir], [\${pythondir}/$PACKAGE]) - - dnl pyexecdir -- directory for installing python extension modules - dnl (shared libraries) - dnl Query distutils for this directory. distutils does not exist in - dnl Python 1.5, so we fall back to the hardcoded directory if it - dnl doesn't work. - AC_CACHE_CHECK([for $am_display_PYTHON extension module directory], - [am_cv_python_pyexecdir], - [am_cv_python_pyexecdir=`$PYTHON -c "from distutils import sysconfig; print(sysconfig.get_python_lib(1,0,prefix='$PYTHON_EXEC_PREFIX'))" 2>/dev/null || - echo "${PYTHON_EXEC_PREFIX}/lib/python${PYTHON_VERSION}/site-packages"`]) - AC_SUBST([pyexecdir], [$am_cv_python_pyexecdir]) - - dnl pkgpyexecdir -- $(pyexecdir)/$(PACKAGE) - - AC_SUBST([pkgpyexecdir], [\${pyexecdir}/$PACKAGE]) - - dnl Run any user-specified action. - $2 - fi - -]) diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index f254bd16..00d8caf6 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -1,5 +1,6 @@ # vim: set fileencoding=utf-8 : # test helpers + import os import tempfile import pytest @@ -174,7 +175,7 @@ def run_fn2(fn, x, y): # test a pair of things which can be lists for approx. equality def assert_almost_equal_objects(a, b, threshold=0.0001, msg=''): - # print 'assertAlmostEqualObjects %s = %s' % (a, b) + # print('assertAlmostEqualObjects %s = %s' % (a, b)) assert all([pytest.approx(x, abs=threshold) == y for x, y in zip_expand(a, b)]), msg diff --git a/test/test-suite/test_arithmetic.py b/test/test-suite/test_arithmetic.py index 8e6f3e46..b965991c 100644 --- a/test/test-suite/test_arithmetic.py +++ b/test/test-suite/test_arithmetic.py @@ -1,4 +1,5 @@ # vim: set fileencoding=utf-8 : + import math import pytest @@ -10,14 +11,16 @@ from helpers import unsigned_formats, float_formats, noncomplex_formats, \ class TestArithmetic: def run_arith(self, fn, fmt=all_formats): - [run_image2(fn.__name__ + ' image', x.cast(y), x.cast(z), fn) + [run_image2('%s image %s %s %s' % (fn.__name__, x, y, z), + x.cast(y), x.cast(z), fn) for x in self.all_images for y in fmt for z in fmt] def run_arith_const(self, fn, fmt=all_formats): - [run_const(fn.__name__ + ' scalar', fn, x.cast(y), 2) + [run_const('%s scalar %s %s' % (fn.__name__, x, y), + fn, x.cast(y), 2) for x in self.all_images for y in fmt] - [run_const(fn.__name__ + ' vector', fn, self.colour.cast(y), - [1, 2, 3]) + [run_const('%s vector %s' % (fn.__name__, y), + fn, self.colour.cast(y), [1, 2, 3]) for y in fmt] # run a function on an image, @@ -199,6 +202,13 @@ class TestArithmetic: self.run_arith_const(noteq) self.run_arith(noteq) + # comparisons against out of range values should always fail, and + # comparisons to fractional values should always fail + x = pyvips.Image.grey(256, 256, uchar=True) + assert (x == 1000).max() == 0 + assert (x == 12).max() == 255 + assert (x == 12.5).max() == 0 + def test_abs(self): def my_abs(x): return abs(x) diff --git a/test/test-suite/test_conversion.py b/test/test-suite/test_conversion.py index 73409ee3..0ee88574 100644 --- a/test/test-suite/test_conversion.py +++ b/test/test-suite/test_conversion.py @@ -539,6 +539,26 @@ class TestConversion: result = r(50, 50) assert_almost_equal_objects(result, [3.0, 4.9, 6.9], threshold=0.1) + def test_switch(self): + x = pyvips.Image.grey(256, 256, uchar=True) + + # slice into two at 128, we should get 50% of pixels in each half + index = pyvips.Image.switch([x < 128, x >= 128]) + assert index.avg() == 0.5 + + # slice into four + index = pyvips.Image.switch([ + x < 64, + x >= 64 and x < 128, + x >= 128 and x < 192, + x >= 192 + ]) + assert index.avg() == 1.5 + + # no match should return n + 1 + index = pyvips.Image.switch([x == 1000, x == 2000]) + assert index.avg() == 2 + def test_insert(self): for x in all_formats: for y in all_formats: diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 89bb8f34..6856418c 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -842,7 +842,9 @@ class TestForeign: def test_heifload(self): def heif_valid(im): a = im(10, 10) - assert_almost_equal_objects(a, [75.0, 86.0, 81.0]) + # different versions of HEIC decode have slightly different + # rounding + assert_almost_equal_objects(a, [75.0, 86.0, 81.0], threshold=2) assert im.width == 4032 assert im.height == 3024 assert im.bands == 3 diff --git a/test/test-suite/test_histogram.py b/test/test-suite/test_histogram.py index 765b33e1..a709dcfa 100644 --- a/test/test-suite/test_histogram.py +++ b/test/test-suite/test_histogram.py @@ -109,6 +109,25 @@ class TestHistogram: # new mean should be closer to target mean assert abs(im.avg() - 128) > abs(im2.avg() - 128) + def test_case(self): + # slice into two at 128, we should get 50% of pixels in each half + x = pyvips.Image.grey(256, 256, uchar=True) + index = pyvips.Image.switch([x < 128, x >= 128]) + + y = index.case([10, 20]) + assert y.avg() == 15 + + # slice into four + index = pyvips.Image.switch([ + x < 64, + x >= 64 and x < 128, + x >= 128 and x < 192, + x >= 192 + ]) + assert index.case([10, 20, 30, 40]).avg() == 25 + + # values over N should use the last value + assert index.case([10, 20, 30]).avg() == 22.5 if __name__ == '__main__': pytest.main()