From 039997780b46893adeb8782030e59eb2d90f5432 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 5 Aug 2016 08:52:50 +0100 Subject: [PATCH 01/13] bump API version for new function thanks Benjamin --- configure.ac | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 929b9b53..8a6e60e6 100644 --- a/configure.ac +++ b/configure.ac @@ -37,9 +37,9 @@ VIPS_VERSION_STRING=$VIPS_VERSION-`date` # binary interface changes backwards compatible?: increment age # binary interface changes not backwards compatible?: reset age to 0 -LIBRARY_CURRENT=46 -LIBRARY_REVISION=3 -LIBRARY_AGE=4 +LIBRARY_CURRENT=47 +LIBRARY_REVISION=0 +LIBRARY_AGE=5 # patched into include/vips/version.h AC_SUBST(VIPS_VERSION) From ff819bfe632e8f5737f17cef24697bfc1cdc4b1a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 1 Aug 2016 12:04:15 +0100 Subject: [PATCH 02/13] small speedup for reduceh --- libvips/iofuncs/region.c | 2 +- libvips/resample/reduceh.cpp | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index ad7035e2..cd80b6ea 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -890,7 +890,7 @@ vips_region_fill( VipsRegion *reg, VipsRect *r, VipsRegionFillFn fn, void *a ) * the pixels we need. If it does, we could copy them and only * generate the new ones. * - * However, we usually have neighboring regions on different threads, + * However, we usually have neighbouring regions on different threads, * so from the point of view of this thread, we will get no overlaps * on successive prepare requests. */ diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 8d1c3763..9bfd537d 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -321,15 +321,30 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, VIPS_GATE_START( "vips_reduceh_gen: work" ); for( int y = 0; y < r->height; y ++ ) { + VipsPel *p0; VipsPel *q; + double X; q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); + X = r->left * reduceh->xshrink; + /* We want p0 to be the start (ie. x == 0) of the input + * scanline we are reading from. We can then calculate the p we + * need for each pixel with a single mul and avoid calling ADDR + * for each pixel. + * + * We can't get p0 directly with ADDR since it could be outside + * valid, so get the leftmost pixel in valid and subtract a + * bit. + */ + p0 = VIPS_REGION_ADDR( ir, ir->valid.left, r->top + y ) - + ir->valid.left * ps; + for( int x = 0; x < r->width; x++ ) { int ix = (int) X; - VipsPel *p = VIPS_REGION_ADDR( ir, ix, r->top + y ); + VipsPel *p = p0 + ix * ps; const int sx = X * VIPS_TRANSFORM_SCALE * 2; const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1); const int tx = (six + 1) >> 1; From 1f403a4add6aae4115cbce5daf7f7a2878b004b3 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 15 Aug 2016 15:45:26 +0100 Subject: [PATCH 03/13] first attempt seems to all work, test it a bit more see https://github.com/jcupitt/libvips/issues/491 --- ChangeLog | 2 + README.md | 1 - TODO | 11 +++++ libvips/include/vips/resample.h | 17 +++++--- libvips/iofuncs/operation.c | 6 ++- libvips/resample/reduce.c | 48 ++++++++++++++------- libvips/resample/reduceh.cpp | 47 +++++++++++++-------- libvips/resample/reducev.cpp | 53 +++++++++++++---------- libvips/resample/resize.c | 74 +++++++++++++++------------------ libvips/resample/shrink.c | 71 ++++++++++++++++++------------- libvips/resample/shrinkh.c | 67 +++++++++++++++++++---------- libvips/resample/shrinkv.c | 57 +++++++++++++++++-------- test/test_resample.py | 12 +++--- tools/vipsthumbnail.c | 9 ++-- 14 files changed, 294 insertions(+), 181 deletions(-) diff --git a/ChangeLog b/ChangeLog index e35c8d2d..cb2f0767 100644 --- a/ChangeLog +++ b/ChangeLog @@ -37,6 +37,8 @@ - support --strip for pngsave - add svgz support [Felix Bünemann] - rename boostrap.sh -> autogen.sh to help snapcraft +- resize/reduce/shrink now round output size to nearest rather than rounding + down, thanks ioquatix 30/7/16 started 8.3.3 - fix performance regression in 8.3.2, thanks Lovell diff --git a/README.md b/README.md index bbac874b..48b2e40e 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,6 @@ Debug build: Leak check: $ export G_DEBUG=gc-friendly - $ export G_SLICE=always-malloc $ valgrind --suppressions=libvips.supp \ --leak-check=yes \ vips ... > vips-vg.log 2>&1 diff --git a/TODO b/TODO index cdf1866b..742e920b 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,14 @@ +- strange: + + $ vips similarity --scale 0.33 k2.jpg x.v + $ vipsheader k2.jpg + k2.jpg: 1450x2048 uchar, 3 bands, srgb, jpegload + $ vipsheader x.v + x.v: 478x676 uchar, 3 bands, srgb, jpegload + + 1450 * 0.33 = 478.5 ... this was rounded down + 2048 * 0.33 = 675.84 ... this was rounded up + - add APPROX convsep test? - add more webp tests to py suite diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index 821a9995..5333d9a3 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -48,15 +48,20 @@ typedef enum { } VipsKernel; int vips_shrink( VipsImage *in, VipsImage **out, - double xshrink, double yshrink, ... ) + double hshrink, double vshrink, ... ) + __attribute__((sentinel)); +int vips_shrinkh( VipsImage *in, VipsImage **out, int hshrink, ... ) + __attribute__((sentinel)); +int vips_shrinkv( VipsImage *in, VipsImage **out, int vshrink, ... ) __attribute__((sentinel)); -int vips_shrinkh( VipsImage *in, VipsImage **out, int xshrink, ... ); -int vips_shrinkv( VipsImage *in, VipsImage **out, int yshrink, ... ); int vips_reduce( VipsImage *in, VipsImage **out, - double xshrink, double yshrink, ... ); -int vips_reduceh( VipsImage *in, VipsImage **out, double xshrink, ... ); -int vips_reducev( VipsImage *in, VipsImage **out, double yshrink, ... ); + double hshrink, double vshrink, ... ) + __attribute__((sentinel)); +int vips_reduceh( VipsImage *in, VipsImage **out, double hshrink, ... ) + __attribute__((sentinel)); +int vips_reducev( VipsImage *in, VipsImage **out, double vshrink, ... ) + __attribute__((sentinel)); int vips_similarity( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); diff --git a/libvips/iofuncs/operation.c b/libvips/iofuncs/operation.c index 2e4c01ca..6bd7fb85 100644 --- a/libvips/iofuncs/operation.c +++ b/libvips/iofuncs/operation.c @@ -699,7 +699,11 @@ vips_operation_set_valist_required( VipsOperation *operation, va_list ap ) g_assert( argument_instance ); - if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) ) { + /* We skip deprecated required args. There will be a new, + * renamed arg in the same place. + */ + if( (argument_class->flags & VIPS_ARGUMENT_REQUIRED) && + !(argument_class->flags & VIPS_ARGUMENT_DEPRECATED) ) { VIPS_ARGUMENT_COLLECT_SET( pspec, argument_class, ap ); #ifdef VIPS_DEBUG diff --git a/libvips/resample/reduce.c b/libvips/resample/reduce.c index 27e7661d..45960431 100644 --- a/libvips/resample/reduce.c +++ b/libvips/resample/reduce.c @@ -2,6 +2,8 @@ * * 27/1/16 * - from shrink.c + * 15/8/16 + * - rename xshrink -> hshrink for greater consistency */ /* @@ -66,8 +68,8 @@ typedef struct _VipsReduce { VipsResample parent_instance; - double xshrink; /* Shrink factors */ - double yshrink; + double hshrink; /* Shrink factors */ + double vshrink; /* The thing we use to make the kernel. */ @@ -90,10 +92,10 @@ vips_reduce_build( VipsObject *object ) if( VIPS_OBJECT_CLASS( vips_reduce_parent_class )->build( object ) ) return( -1 ); - if( vips_reducev( resample->in, &t[0], reduce->yshrink, + if( vips_reducev( resample->in, &t[0], reduce->vshrink, "kernel", reduce->kernel, NULL ) || - vips_reduceh( t[0], &t[1], reduce->xshrink, + vips_reduceh( t[0], &t[1], reduce->hshrink, "kernel", reduce->kernel, NULL ) || vips_image_write( t[1], resample->out ) ) @@ -120,18 +122,18 @@ vips_reduce_class_init( VipsReduceClass *class ) operation_class->flags = VIPS_OPERATION_SEQUENTIAL; - VIPS_ARG_DOUBLE( class, "xshrink", 8, - _( "Xshrink" ), + VIPS_ARG_DOUBLE( class, "hshrink", 8, + _( "Hshrink" ), _( "Horizontal shrink factor" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsReduce, xshrink ), + G_STRUCT_OFFSET( VipsReduce, hshrink ), 1.0, 1000000.0, 1.0 ); - VIPS_ARG_DOUBLE( class, "yshrink", 9, - _( "Yshrink" ), + VIPS_ARG_DOUBLE( class, "vshrink", 9, + _( "Vshrink" ), _( "Vertical shrink factor" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsReduce, yshrink ), + G_STRUCT_OFFSET( VipsReduce, vshrink ), 1.0, 1000000.0, 1.0 ); VIPS_ARG_ENUM( class, "kernel", 3, @@ -141,6 +143,22 @@ vips_reduce_class_init( VipsReduceClass *class ) G_STRUCT_OFFSET( VipsReduce, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); + /* The old names .. now use h and v everywhere. + */ + VIPS_ARG_DOUBLE( class, "xshrink", 8, + _( "Xshrink" ), + _( "Horizontal shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsReduce, hshrink ), + 1.0, 1000000.0, 1.0 ); + + VIPS_ARG_DOUBLE( class, "yshrink", 9, + _( "Yshrink" ), + _( "Vertical shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsReduce, vshrink ), + 1.0, 1000000.0, 1.0 ); + } static void @@ -153,8 +171,8 @@ vips_reduce_init( VipsReduce *reduce ) * vips_reduce: * @in: input image * @out: output image - * @xshrink: horizontal shrink - * @yshrink: vertical shrink + * @hshrink: horizontal shrink + * @vshrink: vertical shrink * @...: %NULL-terminated list of optional named arguments * * Optional arguments: @@ -176,13 +194,13 @@ vips_reduce_init( VipsReduce *reduce ) */ int vips_reduce( VipsImage *in, VipsImage **out, - double xshrink, double yshrink, ... ) + double hshrink, double vshrink, ... ) { va_list ap; int result; - va_start( ap, yshrink ); - result = vips_call_split( "reduce", ap, in, out, xshrink, yshrink ); + va_start( ap, vshrink ); + result = vips_call_split( "reduce", ap, in, out, hshrink, vshrink ); va_end( ap ); return( result ); diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 6ca45ab5..cf51d7f8 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -4,6 +4,8 @@ * - from shrinkh.c * 10/3/16 * - add other kernels + * 15/8/16 + * - rename xshrink as hshrink for consistency */ /* @@ -67,7 +69,7 @@ typedef struct _VipsReduceh { VipsResample parent_instance; - double xshrink; /* Reduce factor */ + double hshrink; /* Reduce factor */ /* The thing we use to make the kernel. */ @@ -276,7 +278,7 @@ reduceh_notab( VipsReduceh *reduceh, double cx[MAX_POINT]; - vips_reduce_make_mask( cx, reduceh->kernel, reduceh->xshrink, x ); + vips_reduce_make_mask( cx, reduceh->kernel, reduceh->hshrink, x ); for( int z = 0; z < bands; z++ ) { out[z] = reduce_sum( in, bands, cx, n ); @@ -311,9 +313,9 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, r->width, r->height, r->left, r->top ); #endif /*DEBUG*/ - s.left = r->left * reduceh->xshrink; + s.left = r->left * reduceh->hshrink; s.top = r->top; - s.width = r->width * reduceh->xshrink + reduceh->n_point; + s.width = r->width * reduceh->hshrink + reduceh->n_point; s.height = r->height; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -328,7 +330,7 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - X = r->left * reduceh->xshrink; + X = r->left * reduceh->hshrink; /* We want p0 to be the start (ie. x == 0) of the input * scanline we are reading from. We can then calculate the p we @@ -411,7 +413,7 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, break; } - X += reduceh->xshrink; + X += reduceh->hshrink; q += ps; } } @@ -439,19 +441,19 @@ vips_reduceh_build( VipsObject *object ) in = resample->in; - if( reduceh->xshrink < 1 ) { + if( reduceh->hshrink < 1 ) { vips_error( object_class->nickname, "%s", _( "reduce factors should be >= 1" ) ); return( -1 ); } - if( reduceh->xshrink == 1 ) + if( reduceh->hshrink == 1 ) return( vips_image_write( in, resample->out ) ); /* Build the tables of pre-computed coefficients. */ reduceh->n_point = - vips_reduce_get_points( reduceh->kernel, reduceh->xshrink ); + vips_reduce_get_points( reduceh->kernel, reduceh->hshrink ); vips_info( object_class->nickname, "%d point mask", reduceh->n_point ); if( reduceh->n_point > MAX_POINT ) { vips_error( object_class->nickname, @@ -468,7 +470,7 @@ vips_reduceh_build( VipsObject *object ) return( -1 ); vips_reduce_make_mask( reduceh->matrixf[x], - reduceh->kernel, reduceh->xshrink, + reduceh->kernel, reduceh->hshrink, (float) x / VIPS_TRANSFORM_SCALE ); for( int i = 0; i < reduceh->n_point; i++ ) @@ -503,7 +505,7 @@ vips_reduceh_build( VipsObject *object ) * fractional part), we just see the integer part here. */ resample->out->Xsize = VIPS_RINT( - (in->Xsize - reduceh->n_point + 1) / reduceh->xshrink ); + (in->Xsize - reduceh->n_point + 1) / reduceh->hshrink ); if( resample->out->Xsize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); @@ -543,11 +545,11 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class ) operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; - VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3, - _( "Xshrink" ), + VIPS_ARG_DOUBLE( reduceh_class, "hshrink", 3, + _( "Hshrink" ), _( "Horizontal shrink factor" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsReduceh, xshrink ), + G_STRUCT_OFFSET( VipsReduceh, hshrink ), 1, 1000000, 1 ); VIPS_ARG_ENUM( reduceh_class, "kernel", 3, @@ -557,6 +559,15 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class ) G_STRUCT_OFFSET( VipsReduceh, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); + /* Old name. + */ + VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3, + _( "Xshrink" ), + _( "Horizontal shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsReduceh, hshrink ), + 1, 1000000, 1 ); + } static void @@ -569,7 +580,7 @@ vips_reduceh_init( VipsReduceh *reduceh ) * vips_reduceh: * @in: input image * @out: output image - * @xshrink: horizontal reduce + * @hshrink: horizontal reduce * @...: %NULL-terminated list of optional named arguments * * Optional arguments: @@ -590,13 +601,13 @@ vips_reduceh_init( VipsReduceh *reduceh ) * Returns: 0 on success, -1 on error */ int -vips_reduceh( VipsImage *in, VipsImage **out, double xshrink, ... ) +vips_reduceh( VipsImage *in, VipsImage **out, double hshrink, ... ) { va_list ap; int result; - va_start( ap, xshrink ); - result = vips_call_split( "reduceh", ap, in, out, xshrink ); + va_start( ap, hshrink ); + result = vips_call_split( "reduceh", ap, in, out, hshrink ); va_end( ap ); return( result ); diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index 362ca861..c7913781 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -11,6 +11,8 @@ * equal to the target scale * 15/6/16 * - better accuracy with smarter multiplication + * 15/8/16 + * - rename yshrink as vshrink for consistency */ /* @@ -92,7 +94,7 @@ typedef struct { typedef struct _VipsReducev { VipsResample parent_instance; - double yshrink; /* Shrink factor */ + double vshrink; /* Shrink factor */ /* The thing we use to make the kernel. */ @@ -491,7 +493,7 @@ reducev_notab( VipsReducev *reducev, double cy[MAX_POINT]; - vips_reduce_make_mask( cy, reducev->kernel, reducev->yshrink, y ); + vips_reduce_make_mask( cy, reducev->kernel, reducev->vshrink, y ); for( int z = 0; z < ne; z++ ) out[z] = reduce_sum( in + z, l1, cy, n ); @@ -521,9 +523,9 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq, #endif /*DEBUG*/ s.left = r->left; - s.top = r->top * reducev->yshrink; + s.top = r->top * reducev->vshrink; s.width = r->width; - s.height = r->height * reducev->yshrink + reducev->n_point; + s.height = r->height * reducev->vshrink + reducev->n_point; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -532,7 +534,7 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq, for( int y = 0; y < r->height; y ++ ) { VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - const double Y = (r->top + y) * reducev->yshrink; + const double Y = (r->top + y) * reducev->vshrink; VipsPel *p = VIPS_REGION_ADDR( ir, r->left, (int) Y ); const int sy = Y * VIPS_TRANSFORM_SCALE * 2; const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); @@ -631,9 +633,9 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, #endif /*DEBUG_PIXELS*/ s.left = r->left; - s.top = r->top * reducev->yshrink; + s.top = r->top * reducev->vshrink; s.width = r->width; - s.height = r->height * reducev->yshrink + reducev->n_point; + s.height = r->height * reducev->vshrink + reducev->n_point; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -651,7 +653,7 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, for( int y = 0; y < r->height; y ++ ) { VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - const double Y = (r->top + y) * reducev->yshrink; + const double Y = (r->top + y) * reducev->vshrink; const int py = (int) Y; const int sy = Y * VIPS_TRANSFORM_SCALE * 2; const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); @@ -719,7 +721,7 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in ) return( -1 ); vips_reduce_make_mask( reducev->matrixf[y], - reducev->kernel, reducev->yshrink, + reducev->kernel, reducev->vshrink, (float) y / VIPS_TRANSFORM_SCALE ); #ifdef DEBUG @@ -780,7 +782,7 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in ) * fractional part), we just see the integer part here. */ resample->out->Ysize = VIPS_RINT( - (in->Ysize - reducev->n_point + 1) / reducev->yshrink ); + (in->Ysize - reducev->n_point + 1) / reducev->vshrink ); if( resample->out->Ysize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); @@ -816,17 +818,17 @@ vips_reducev_build( VipsObject *object ) in = resample->in; - if( reducev->yshrink < 1 ) { + if( reducev->vshrink < 1 ) { vips_error( object_class->nickname, "%s", _( "reduce factor should be >= 1" ) ); return( -1 ); } - if( reducev->yshrink == 1 ) + if( reducev->vshrink == 1 ) return( vips_image_write( in, resample->out ) ); reducev->n_point = - vips_reduce_get_points( reducev->kernel, reducev->yshrink ); + vips_reduce_get_points( reducev->kernel, reducev->vshrink ); if( reducev->n_point > MAX_POINT ) { vips_error( object_class->nickname, "%s", _( "reduce factor too large" ) ); @@ -876,20 +878,29 @@ vips_reducev_class_init( VipsReducevClass *reducev_class ) operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; - VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3, - _( "Xshrink" ), + VIPS_ARG_DOUBLE( reducev_class, "vshrink", 3, + _( "Vshrink" ), _( "Vertical shrink factor" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsReducev, yshrink ), + G_STRUCT_OFFSET( VipsReducev, vshrink ), 1, 1000000, 1 ); - VIPS_ARG_ENUM( reducev_class, "kernel", 3, + VIPS_ARG_ENUM( reducev_class, "kernel", 4, _( "Kernel" ), _( "Resampling kernel" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsReducev, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); + /* Old name. + */ + VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3, + _( "Yshrink" ), + _( "Vertical shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsReducev, vshrink ), + 1, 1000000, 1 ); + } static void @@ -902,7 +913,7 @@ vips_reducev_init( VipsReducev *reducev ) * vips_reducev: * @in: input image * @out: output image - * @yshrink: horizontal reduce + * @vshrink: horizontal reduce * @...: %NULL-terminated list of optional named arguments * * Optional arguments: @@ -923,13 +934,13 @@ vips_reducev_init( VipsReducev *reducev ) * Returns: 0 on success, -1 on error */ int -vips_reducev( VipsImage *in, VipsImage **out, double yshrink, ... ) +vips_reducev( VipsImage *in, VipsImage **out, double vshrink, ... ) { va_list ap; int result; - va_start( ap, yshrink ); - result = vips_call_split( "reducev", ap, in, out, yshrink ); + va_start( ap, vshrink ); + result = vips_call_split( "reducev", ap, in, out, vshrink ); va_end( ap ); return( result ); diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index b57b3f40..1a46125a 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -18,6 +18,8 @@ * reduce * 22/6/16 * - faster and better upsizing + * 15/8/16 + * - more accurate resizing */ /* @@ -157,37 +159,36 @@ vips_resize_build( VipsObject *object ) VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 ); VipsImage *in; - int target_width; - int target_height; + double hscale; + double vscale; int int_hshrink; int int_vshrink; - double hresidual; - double vresidual; if( VIPS_OBJECT_CLASS( vips_resize_parent_class )->build( object ) ) return( -1 ); in = resample->in; - /* The image size we are aiming for. + /* Updated below when we do the int part of our shrink. */ - target_width = in->Xsize * resize->scale; + hscale = resize->scale; if( vips_object_argument_isset( object, "vscale" ) ) - target_height = in->Ysize * resize->vscale; + vscale = resize->vscale; else - target_height = in->Ysize * resize->scale; + vscale = resize->scale; - int_hshrink = vips_resize_int_shrink( resize, resize->scale ); - if( vips_object_argument_isset( object, "vscale" ) ) - int_vshrink = vips_resize_int_shrink( resize, resize->vscale ); - else - int_vshrink = int_hshrink; + /* The int part of our scale. + */ + int_hshrink = vips_resize_int_shrink( resize, hscale ); + int_vshrink = vips_resize_int_shrink( resize, vscale ); if( int_vshrink > 1 ) { vips_info( class->nickname, "shrinkv by %d", int_vshrink ); if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) ) return( -1 ); in = t[0]; + + vscale *= int_vshrink; } if( int_hshrink > 1 ) { @@ -195,16 +196,9 @@ vips_resize_build( VipsObject *object ) if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) ) return( -1 ); in = t[1]; - } - /* Do we need a further size adjustment? It's the difference - * between our target size and the size we have after vips_shrink(). - * - * This can break the aspect ratio slightly :/ but hopefully no one - * will notice. - */ - hresidual = (double) target_width / in->Xsize; - vresidual = (double) target_height / in->Ysize; + hscale *= int_hshrink; + } /* We will get overcomputation on vips_shrink() from the vips_reduce() * coming later, so read into a cache where tiles are scanlines, and @@ -239,7 +233,7 @@ vips_resize_build( VipsObject *object ) vips_get_tile_size( in, &tile_width, &tile_height, &n_lines ); - need_lines = 1.2 * n_lines / vresidual; + need_lines = 1.2 * n_lines / vscale; if( vips_tilecache( in, &t[6], "tile_width", in->Xsize, "tile_height", 10, @@ -253,20 +247,20 @@ vips_resize_build( VipsObject *object ) /* Any residual downsizing. */ - if( vresidual < 1.0 ) { + if( vscale < 1.0 ) { vips_info( class->nickname, "residual reducev by %g", - vresidual ); - if( vips_reducev( in, &t[2], 1.0 / vresidual, + vscale ); + if( vips_reducev( in, &t[2], 1.0 / vscale, "kernel", resize->kernel, NULL ) ) return( -1 ); in = t[2]; } - if( hresidual < 1.0 ) { + if( hscale < 1.0 ) { vips_info( class->nickname, "residual reduceh by %g", - hresidual ); - if( vips_reduceh( in, &t[3], 1.0 / hresidual, + hscale ); + if( vips_reduceh( in, &t[3], 1.0 / hscale, "kernel", resize->kernel, NULL ) ) return( -1 ); @@ -275,8 +269,8 @@ vips_resize_build( VipsObject *object ) /* Any upsizing. */ - if( hresidual > 1.0 || - vresidual > 1.0 ) { + if( hscale > 1.0 || + vscale > 1.0 ) { const char *nickname = vips_resize_interpolate( resize->kernel ); VipsInterpolate *interpolate; @@ -284,21 +278,21 @@ vips_resize_build( VipsObject *object ) return( -1 ); vips_object_local( object, interpolate ); - if( hresidual > 1.0 && - vresidual > 1.0 ) { + if( hscale > 1.0 && + vscale > 1.0 ) { vips_info( class->nickname, - "residual scale %g x %g", hresidual, vresidual ); + "residual scale %g x %g", hscale, vscale ); if( vips_affine( in, &t[4], - hresidual, 0.0, 0.0, vresidual, + hscale, 0.0, 0.0, vscale, "interpolate", interpolate, NULL ) ) return( -1 ); in = t[4]; } - else if( hresidual > 1.0 ) { + else if( hscale > 1.0 ) { vips_info( class->nickname, - "residual scaleh %g", hresidual ); - if( vips_affine( in, &t[4], hresidual, 0.0, 0.0, 1.0, + "residual scale %g", hscale ); + if( vips_affine( in, &t[4], hscale, 0.0, 0.0, 1.0, "interpolate", interpolate, NULL ) ) return( -1 ); @@ -306,8 +300,8 @@ vips_resize_build( VipsObject *object ) } else { vips_info( class->nickname, - "residual scalev %g", vresidual ); - if( vips_affine( in, &t[4], 1.0, 0.0, 0.0, vresidual, + "residual scale %g", vscale ); + if( vips_affine( in, &t[4], 1.0, 0.0, 0.0, vscale, "interpolate", interpolate, NULL ) ) return( -1 ); diff --git a/libvips/resample/shrink.c b/libvips/resample/shrink.c index 59efc483..19eff243 100644 --- a/libvips/resample/shrink.c +++ b/libvips/resample/shrink.c @@ -4,6 +4,9 @@ * - from shrink.c (now renamed as shrink2.c) * - split to h and v shrinks for a large memory saving * - now handles complex + * 15/8/16 + * - more accurate resize + * - rename xshrink -> hshrink for greater consistency */ /* @@ -55,8 +58,8 @@ typedef struct _VipsShrink { VipsResample parent_instance; - double xshrink; /* Shrink factors */ - double yshrink; + double hshrink; /* Shrink factors */ + double vshrink; } VipsShrink; @@ -72,40 +75,34 @@ vips_shrink_build( VipsObject *object ) VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 ); - int xshrink_int; - int yshrink_int; + int hshrink_int; + int vshrink_int; if( VIPS_OBJECT_CLASS( vips_shrink_parent_class )->build( object ) ) return( -1 ); - xshrink_int = (int) shrink->xshrink; - yshrink_int = (int) shrink->yshrink; + hshrink_int = (int) shrink->hshrink; + vshrink_int = (int) shrink->vshrink; - if( xshrink_int != shrink->xshrink || - yshrink_int != shrink->yshrink ) { + if( hshrink_int != shrink->hshrink || + vshrink_int != shrink->vshrink ) { /* Shrink by int factors, affine to final size. */ - int target_width = resample->in->Xsize / shrink->xshrink; - int target_height = resample->in->Ysize / shrink->yshrink; + double xresidual = hshrink_int / shrink->hshrink; + double yresidual = vshrink_int / shrink->vshrink; - double xresidual; - double yresidual; - - if( vips_shrinkv( resample->in, &t[0], yshrink_int, NULL ) || - vips_shrinkh( t[0], &t[1], xshrink_int, NULL ) ) + if( vips_shrinkv( resample->in, &t[0], vshrink_int, NULL ) || + vips_shrinkh( t[0], &t[1], hshrink_int, NULL ) ) return( -1 ); - xresidual = (double) target_width / t[1]->Xsize; - yresidual = (double) target_height / t[1]->Ysize; - if( vips_affine( t[1], &t[2], xresidual, 0.0, 0.0, yresidual, NULL ) || vips_image_write( t[2], resample->out ) ) return( -1 ); } else { - if( vips_shrinkv( resample->in, &t[0], shrink->yshrink, NULL ) || - vips_shrinkh( t[0], &t[1], shrink->xshrink, NULL ) || + if( vips_shrinkv( resample->in, &t[0], shrink->vshrink, NULL ) || + vips_shrinkh( t[0], &t[1], shrink->hshrink, NULL ) || vips_image_write( t[1], resample->out ) ) return( -1 ); } @@ -131,18 +128,34 @@ vips_shrink_class_init( VipsShrinkClass *class ) operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; + VIPS_ARG_DOUBLE( class, "vshrink", 9, + _( "Vshrink" ), + _( "Vertical shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsShrink, vshrink ), + 1.0, 1000000.0, 1.0 ); + + VIPS_ARG_DOUBLE( class, "hshrink", 8, + _( "Hshrink" ), + _( "Horizontal shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsShrink, hshrink ), + 1.0, 1000000.0, 1.0 ); + + /* The old names .. now use h and v everywhere. + */ VIPS_ARG_DOUBLE( class, "xshrink", 8, _( "Xshrink" ), _( "Horizontal shrink factor" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsShrink, xshrink ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsShrink, hshrink ), 1.0, 1000000.0, 1.0 ); VIPS_ARG_DOUBLE( class, "yshrink", 9, _( "Yshrink" ), _( "Vertical shrink factor" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsShrink, yshrink ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsShrink, vshrink ), 1.0, 1000000.0, 1.0 ); } @@ -156,8 +169,8 @@ vips_shrink_init( VipsShrink *shrink ) * vips_shrink: * @in: input image * @out: output image - * @xshrink: horizontal shrink - * @yshrink: vertical shrink + * @hshrink: horizontal shrink + * @vshrink: vertical shrink * @...: %NULL-terminated list of optional named arguments * * Shrink @in by a pair of factors with a simple box filter. For non-integer @@ -177,13 +190,13 @@ vips_shrink_init( VipsShrink *shrink ) */ int vips_shrink( VipsImage *in, VipsImage **out, - double xshrink, double yshrink, ... ) + double hshrink, double vshrink, ... ) { va_list ap; int result; - va_start( ap, yshrink ); - result = vips_call_split( "shrink", ap, in, out, xshrink, yshrink ); + va_start( ap, vshrink ); + result = vips_call_split( "shrink", ap, in, out, hshrink, vshrink ); va_end( ap ); return( result ); diff --git a/libvips/resample/shrinkh.c b/libvips/resample/shrinkh.c index 68f1583f..0a9b66ef 100644 --- a/libvips/resample/shrinkh.c +++ b/libvips/resample/shrinkh.c @@ -4,6 +4,8 @@ * - from shrink.c * 22/1/16 * - reorganise loops, 30% faster, vectorisable + * 15/8/16 + * - rename xshrink -> hshrink for greater consistency */ /* @@ -55,7 +57,7 @@ typedef struct _VipsShrinkh { VipsResample parent_instance; - int xshrink; /* Shrink factor */ + int hshrink; /* Shrink factor */ } VipsShrinkh; @@ -79,9 +81,9 @@ G_DEFINE_TYPE( VipsShrinkh, vips_shrinkh, VIPS_TYPE_RESAMPLE ); \ sum = 0; \ x1 = b; \ - VIPS_UNROLL( shrink->xshrink, INNER( BANDS ) ); \ - q[b] = (sum + shrink->xshrink / 2) / \ - shrink->xshrink; \ + VIPS_UNROLL( shrink->hshrink, INNER( BANDS ) ); \ + q[b] = (sum + shrink->hshrink / 2) / \ + shrink->hshrink; \ } \ p += ne; \ q += BANDS; \ @@ -100,8 +102,8 @@ G_DEFINE_TYPE( VipsShrinkh, vips_shrinkh, VIPS_TYPE_RESAMPLE ); \ sum = 0.0; \ x1 = b; \ - VIPS_UNROLL( shrink->xshrink, INNER( bands ) ); \ - q[b] = sum / shrink->xshrink; \ + VIPS_UNROLL( shrink->hshrink, INNER( bands ) ); \ + q[b] = sum / shrink->hshrink; \ } \ p += ne; \ q += bands; \ @@ -118,9 +120,9 @@ vips_shrinkh_gen2( VipsShrinkh *shrink, VipsRegion *or, VipsRegion *ir, const int bands = resample->in->Bands * (vips_band_format_iscomplex( resample->in->BandFmt ) ? 2 : 1); - const int ne = shrink->xshrink * bands; + const int ne = shrink->hshrink * bands; VipsPel *out = VIPS_REGION_ADDR( or, left, top ); - VipsPel *in = VIPS_REGION_ADDR( ir, left * shrink->xshrink, top ); + VipsPel *in = VIPS_REGION_ADDR( ir, left * shrink->hshrink, top ); int x; int x1, b; @@ -200,9 +202,9 @@ vips_shrinkh_gen( VipsRegion *or, void *seq, for( y = 0; y < r->height; y ++ ) { VipsRect s; - s.left = r->left * shrink->xshrink; + s.left = r->left * shrink->hshrink; s.top = r->top + y; - s.width = r->width * shrink->xshrink; + s.width = r->width * shrink->hshrink; s.height = 1; #ifdef DEBUG printf( "shrinkh_gen: requesting line %d\n", s.top ); @@ -230,7 +232,7 @@ vips_shrinkh_build( VipsObject *object ) VipsResample *resample = VIPS_RESAMPLE( object ); VipsShrinkh *shrink = (VipsShrinkh *) object; VipsImage **t = (VipsImage **) - vips_object_local_array( object, 1 ); + vips_object_local_array( object, 2 ); VipsImage *in; @@ -239,13 +241,13 @@ vips_shrinkh_build( VipsObject *object ) in = resample->in; - if( shrink->xshrink < 1 ) { + if( shrink->hshrink < 1 ) { vips_error( class->nickname, "%s", _( "shrink factors should be >= 1" ) ); return( -1 ); } - if( shrink->xshrink == 1 ) + if( shrink->hshrink == 1 ) return( vips_image_write( in, resample->out ) ); /* Unpack for processing. @@ -254,6 +256,17 @@ vips_shrinkh_build( VipsObject *object ) return( -1 ); in = t[0]; + /* We need new pixels at the right so that we don't have small chunks + * to average down the right edge. + */ + if( vips_embed( in, &t[1], + 0, 0, + in->Xsize + shrink->hshrink, in->Ysize, + "extend", VIPS_EXTEND_COPY, + NULL ) ) + return( -1 ); + in = t[1]; + /* THINSTRIP will work, anything else will break seq mode. If you * combine shrink with conv you'll need to use a line cache to maintain * sequentiality. @@ -262,13 +275,14 @@ vips_shrinkh_build( VipsObject *object ) VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) return( -1 ); - /* Size output. Note: we round the output width down! + /* Size output. We need to rint() from the size of the original image. * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true shrink factor (including the * fractional part), we just see the integer part here. */ - resample->out->Xsize = in->Xsize / shrink->xshrink; + resample->out->Xsize = VIPS_RINT( + resample->in->Xsize / shrink->hshrink ); if( resample->out->Xsize <= 0 ) { vips_error( class->nickname, "%s", _( "image has shrunk to nothing" ) ); @@ -307,11 +321,20 @@ vips_shrinkh_class_init( VipsShrinkhClass *class ) operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; + VIPS_ARG_INT( class, "hshrink", 8, + _( "Hshrink" ), + _( "Horizontal shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsShrinkh, hshrink ), + 1, 1000000, 1 ); + + /* The old name .. now use h and v everywhere. + */ VIPS_ARG_INT( class, "xshrink", 8, _( "Xshrink" ), _( "Horizontal shrink factor" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsShrinkh, xshrink ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsShrinkh, hshrink ), 1, 1000000, 1 ); } @@ -325,11 +348,11 @@ vips_shrinkh_init( VipsShrinkh *shrink ) * vips_shrinkh: * @in: input image * @out: output image - * @xshrink: horizontal shrink + * @hshrink: horizontal shrink * @...: %NULL-terminated list of optional named arguments * * Shrink @in horizontally by an integer factor. Each pixel in the output is - * the average of the corresponding line of @xshrink pixels in the input. + * the average of the corresponding line of @hshrink pixels in the input. * * This is a very low-level operation: see vips_resize() for a more * convenient way to resize images. @@ -342,13 +365,13 @@ vips_shrinkh_init( VipsShrinkh *shrink ) * Returns: 0 on success, -1 on error */ int -vips_shrinkh( VipsImage *in, VipsImage **out, int xshrink, ... ) +vips_shrinkh( VipsImage *in, VipsImage **out, int hshrink, ... ) { va_list ap; int result; - va_start( ap, xshrink ); - result = vips_call_split( "shrinkh", ap, in, out, xshrink ); + va_start( ap, hshrink ); + result = vips_call_split( "shrinkh", ap, in, out, hshrink ); va_end( ap ); return( result ); diff --git a/libvips/resample/shrinkv.c b/libvips/resample/shrinkv.c index 9a6f87c6..001e4842 100644 --- a/libvips/resample/shrinkv.c +++ b/libvips/resample/shrinkv.c @@ -41,6 +41,8 @@ * 6/6/13 * - don't chunk horizontally, fixes seq problems with large shrink * factors + * 15/8/16 + * - rename yshrink -> vshrink for greater consistency */ /* @@ -93,7 +95,7 @@ typedef struct _VipsShrinkv { VipsResample parent_instance; - int yshrink; + int vshrink; size_t sizeof_line_buffer; } VipsShrinkv; @@ -200,7 +202,7 @@ vips_shrinkv_add_line( VipsShrinkv *shrink, VipsShrinkvSequence *seq, TYPE * restrict q = (TYPE *) out; \ \ for( x = 0; x < sz; x++ ) \ - q[x] = (sum[x] + shrink->yshrink / 2) / shrink->yshrink; \ + q[x] = (sum[x] + shrink->vshrink / 2) / shrink->vshrink; \ } /* Float average. @@ -210,7 +212,7 @@ vips_shrinkv_add_line( VipsShrinkv *shrink, VipsShrinkvSequence *seq, TYPE * restrict q = (TYPE *) out; \ \ for( x = 0; x < sz; x++ ) \ - q[x] = sum[x] / shrink->yshrink; \ + q[x] = sum[x] / shrink->vshrink; \ } /* Average the line of sums to out. @@ -287,11 +289,11 @@ vips_shrinkv_gen( VipsRegion *or, void *vseq, for( y = 0; y < r->height; y++ ) { memset( seq->sum, 0, shrink->sizeof_line_buffer ); - for( y1 = 0; y1 < shrink->yshrink; y1++ ) { + for( y1 = 0; y1 < shrink->vshrink; y1++ ) { VipsRect s; s.left = r->left; - s.top = y1 + (y + r->top) * shrink->yshrink; + s.top = y1 + (y + r->top) * shrink->vshrink; s.width = r->width; s.height = 1; #ifdef DEBUG @@ -328,7 +330,7 @@ vips_shrinkv_build( VipsObject *object ) VipsResample *resample = VIPS_RESAMPLE( object ); VipsShrinkv *shrink = (VipsShrinkv *) object; VipsImage **t = (VipsImage **) - vips_object_local_array( object, 1 ); + vips_object_local_array( object, 2 ); VipsImage *in; @@ -337,13 +339,13 @@ vips_shrinkv_build( VipsObject *object ) in = resample->in; - if( shrink->yshrink < 1 ) { + if( shrink->vshrink < 1 ) { vips_error( class->nickname, "%s", _( "shrink factors should be >= 1" ) ); return( -1 ); } - if( shrink->yshrink == 1 ) + if( shrink->vshrink == 1 ) return( vips_image_write( in, resample->out ) ); /* Unpack for processing. @@ -352,6 +354,17 @@ vips_shrinkv_build( VipsObject *object ) return( -1 ); in = t[0]; + /* We need new pixels along the bottom so that we don't have small chunks + * to average along the bottom edge. + */ + if( vips_embed( in, &t[1], + 0, 0, + in->Xsize, in->Ysize + shrink->vshrink, + "extend", VIPS_EXTEND_COPY, + NULL ) ) + return( -1 ); + in = t[1]; + /* We have to keep a line buffer as we sum columns. */ shrink->sizeof_line_buffer = @@ -366,13 +379,14 @@ vips_shrinkv_build( VipsObject *object ) VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) return( -1 ); - /* Size output. Note: we round the output width down! + /* Size output. We need to rint() from the size of the original image. * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true shrink factor (including the * fractional part), we just see the integer part here. */ - resample->out->Ysize = in->Ysize / shrink->yshrink; + resample->out->Ysize = VIPS_RINT( + resample->in->Ysize / shrink->vshrink ); if( resample->out->Ysize <= 0 ) { vips_error( class->nickname, "%s", _( "image has shrunk to nothing" ) ); @@ -411,11 +425,20 @@ vips_shrinkv_class_init( VipsShrinkvClass *class ) operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; + VIPS_ARG_INT( class, "vshrink", 9, + _( "Vshrink" ), + _( "Vertical shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsShrinkv, vshrink ), + 1, 1000000, 1 ); + + /* The old name .. now use h and v everywhere. + */ VIPS_ARG_INT( class, "yshrink", 9, _( "Yshrink" ), _( "Vertical shrink factor" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsShrinkv, yshrink ), + VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsShrinkv, vshrink ), 1, 1000000, 1 ); } @@ -429,11 +452,11 @@ vips_shrinkv_init( VipsShrinkv *shrink ) * vips_shrinkv: * @in: input image * @out: output image - * @yshrink: vertical shrink + * @vshrink: vertical shrink * @...: %NULL-terminated list of optional named arguments * * Shrink @in vertically by an integer factor. Each pixel in the output is - * the average of the corresponding column of @yshrink pixels in the input. + * the average of the corresponding column of @vshrink pixels in the input. * * This is a very low-level operation: see vips_resize() for a more * convenient way to resize images. @@ -446,13 +469,13 @@ vips_shrinkv_init( VipsShrinkv *shrink ) * Returns: 0 on success, -1 on error */ int -vips_shrinkv( VipsImage *in, VipsImage **out, int yshrink, ... ) +vips_shrinkv( VipsImage *in, VipsImage **out, int vshrink, ... ) { va_list ap; int result; - va_start( ap, yshrink ); - result = vips_call_split( "shrinkv", ap, in, out, yshrink ); + va_start( ap, vshrink ); + result = vips_call_split( "shrinkv", ap, in, out, vshrink ); va_end( ap ); return( result ); diff --git a/test/test_resample.py b/test/test_resample.py index 2819879e..ce9fb5ed 100755 --- a/test/test_resample.py +++ b/test/test_resample.py @@ -167,19 +167,19 @@ class TestResample(unittest.TestCase): def test_resize(self): im = Vips.Image.new_from_file("images/IMG_4618.jpg") im2 = im.resize(0.25) - self.assertEqual(im2.width, im.width // 4) - self.assertEqual(im2.height, im.height // 4) + self.assertEqual(im2.width, round(im.width / 4.0)) + self.assertEqual(im2.height, round(im.height / 4.0)) def test_shrink(self): im = Vips.Image.new_from_file("images/IMG_4618.jpg") im2 = im.shrink(4, 4) - self.assertEqual(im2.width, im.width // 4) - self.assertEqual(im2.height, im.height // 4) + self.assertEqual(im2.width, round(im.width / 4.0)) + self.assertEqual(im2.height, round(im.height / 4.0)) self.assertTrue(abs(im.avg() - im2.avg()) < 1) im2 = im.shrink(2.5, 2.5) - self.assertEqual(im2.width, im.width // 2.5) - self.assertEqual(im2.height, im.height // 2.5) + self.assertEqual(im2.width, round(im.width / 2.5)) + self.assertEqual(im2.height, round(im.height / 2.5)) self.assertLess(abs(im.avg() - im2.avg()), 1) def test_similarity(self): diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index b6199d87..bad3b164 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -83,6 +83,8 @@ * - no need to guess max-alpha now premultiply does this for us * 1/8/16 * - use scRGB as the working space in linear mode + * 15/8/16 + * - can now remove 0.1 rounding adjustment */ #ifdef HAVE_CONFIG_H @@ -192,12 +194,9 @@ calculate_shrink( VipsImage *im ) * * In crop mode, we aim to fill the bounding box, so we must use the * smaller axis. - * - * Add a small amount so when vips_resize() later rounds down, we - * don't round below target. */ - double horizontal = (double) width / (thumbnail_width + 0.1); - double vertical = (double) height / (thumbnail_height + 0.1); + double horizontal = (double) width / thumbnail_width; + double vertical = (double) height / thumbnail_height; if( crop_image ) { if( horizontal < vertical ) From 476f2157b849f6bb5d2b4597a2a2111127bf0b29 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 16 Aug 2016 09:21:54 +0100 Subject: [PATCH 04/13] add a .gitattributes stops git on Windows adding CRLF line endings to various files see https://github.com/jcupitt/libvips/issues/500#issuecomment-239968255 --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..edf14592 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Shell scripts and Automake sources can't have CRLF line endings +configure.ac eol=lf +Makefile.am eol=lf +*.m4 eol=lf +*.sh eol=lf From 8503065faaec24920f421756ef9a56ad240f658b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 18 Aug 2016 16:03:33 +0100 Subject: [PATCH 05/13] regen cpp binding need to update for the xshrink -> hshrink change --- cplusplus/include/vips/vips-operators.h | 20 ++++-- cplusplus/vips-operators.cpp | 84 ++++++++++++++++++++----- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/cplusplus/include/vips/vips-operators.h b/cplusplus/include/vips/vips-operators.h index 620465a8..61da4ae2 100644 --- a/cplusplus/include/vips/vips-operators.h +++ b/cplusplus/include/vips/vips-operators.h @@ -1,3 +1,7 @@ +// headers for vips operations +// Thu 18 Aug 16:02:54 BST 2016 +// this file is generated automatically, do not edit! + static void system( char * cmd_format , VOption *options = 0 ); VImage add( VImage right , VOption *options = 0 ); VImage subtract( VImage right , VOption *options = 0 ); @@ -148,12 +152,12 @@ VipsBlob * webpsave_buffer( VOption *options = 0 ); void tiffsave( char * filename , VOption *options = 0 ); void fitssave( char * filename , VOption *options = 0 ); VImage mapim( VImage index , VOption *options = 0 ); -VImage shrink( double xshrink , double yshrink , VOption *options = 0 ); -VImage shrinkh( int xshrink , VOption *options = 0 ); -VImage shrinkv( int yshrink , VOption *options = 0 ); -VImage reduceh( double xshrink , VOption *options = 0 ); -VImage reducev( double yshrink , VOption *options = 0 ); -VImage reduce( double xshrink , double yshrink , VOption *options = 0 ); +VImage shrink( double hshrink , double vshrink , VOption *options = 0 ); +VImage shrinkh( int hshrink , VOption *options = 0 ); +VImage shrinkv( int vshrink , VOption *options = 0 ); +VImage reduceh( double hshrink , VOption *options = 0 ); +VImage reducev( double vshrink , VOption *options = 0 ); +VImage reduce( double hshrink , double vshrink , VOption *options = 0 ); VImage quadratic( VImage coeff , VOption *options = 0 ); VImage affine( std::vector matrix , VOption *options = 0 ); VImage similarity( VOption *options = 0 ); @@ -201,8 +205,12 @@ VImage hist_local( int width , int height , VOption *options = 0 ); bool hist_ismonotonic( VOption *options = 0 ); double hist_entropy( VOption *options = 0 ); VImage conv( VImage mask , VOption *options = 0 ); +VImage conva( VImage mask , VOption *options = 0 ); +VImage convf( VImage mask , VOption *options = 0 ); +VImage convi( VImage mask , VOption *options = 0 ); VImage compass( VImage mask , VOption *options = 0 ); VImage convsep( VImage mask , VOption *options = 0 ); +VImage convasep( VImage mask , VOption *options = 0 ); VImage fastcor( VImage ref , VOption *options = 0 ); VImage spcor( VImage ref , VOption *options = 0 ); VImage sharpen( VOption *options = 0 ); diff --git a/cplusplus/vips-operators.cpp b/cplusplus/vips-operators.cpp index 1cb98e9f..7a0721b2 100644 --- a/cplusplus/vips-operators.cpp +++ b/cplusplus/vips-operators.cpp @@ -1,3 +1,7 @@ +// bodies for vips operations +// Thu 18 Aug 16:01:57 BST 2016 +// this file is generated automatically, do not edit! + void VImage::system( char * cmd_format , VOption *options ) { call( "system" , @@ -1849,7 +1853,7 @@ VImage VImage::mapim( VImage index , VOption *options ) return( out ); } -VImage VImage::shrink( double xshrink , double yshrink , VOption *options ) +VImage VImage::shrink( double hshrink , double vshrink , VOption *options ) { VImage out; @@ -1857,13 +1861,13 @@ VImage VImage::shrink( double xshrink , double yshrink , VOption *options ) (options ? options : VImage::option()) -> set( "in", *this ) -> set( "out", &out ) -> - set( "xshrink", xshrink ) -> - set( "yshrink", yshrink ) ); + set( "hshrink", hshrink ) -> + set( "vshrink", vshrink ) ); return( out ); } -VImage VImage::shrinkh( int xshrink , VOption *options ) +VImage VImage::shrinkh( int hshrink , VOption *options ) { VImage out; @@ -1871,12 +1875,12 @@ VImage VImage::shrinkh( int xshrink , VOption *options ) (options ? options : VImage::option()) -> set( "in", *this ) -> set( "out", &out ) -> - set( "xshrink", xshrink ) ); + set( "hshrink", hshrink ) ); return( out ); } -VImage VImage::shrinkv( int yshrink , VOption *options ) +VImage VImage::shrinkv( int vshrink , VOption *options ) { VImage out; @@ -1884,12 +1888,12 @@ VImage VImage::shrinkv( int yshrink , VOption *options ) (options ? options : VImage::option()) -> set( "in", *this ) -> set( "out", &out ) -> - set( "yshrink", yshrink ) ); + set( "vshrink", vshrink ) ); return( out ); } -VImage VImage::reduceh( double xshrink , VOption *options ) +VImage VImage::reduceh( double hshrink , VOption *options ) { VImage out; @@ -1897,12 +1901,12 @@ VImage VImage::reduceh( double xshrink , VOption *options ) (options ? options : VImage::option()) -> set( "in", *this ) -> set( "out", &out ) -> - set( "xshrink", xshrink ) ); + set( "hshrink", hshrink ) ); return( out ); } -VImage VImage::reducev( double yshrink , VOption *options ) +VImage VImage::reducev( double vshrink , VOption *options ) { VImage out; @@ -1910,12 +1914,12 @@ VImage VImage::reducev( double yshrink , VOption *options ) (options ? options : VImage::option()) -> set( "in", *this ) -> set( "out", &out ) -> - set( "yshrink", yshrink ) ); + set( "vshrink", vshrink ) ); return( out ); } -VImage VImage::reduce( double xshrink , double yshrink , VOption *options ) +VImage VImage::reduce( double hshrink , double vshrink , VOption *options ) { VImage out; @@ -1923,8 +1927,8 @@ VImage VImage::reduce( double xshrink , double yshrink , VOption *options ) (options ? options : VImage::option()) -> set( "in", *this ) -> set( "out", &out ) -> - set( "xshrink", xshrink ) -> - set( "yshrink", yshrink ) ); + set( "hshrink", hshrink ) -> + set( "vshrink", vshrink ) ); return( out ); } @@ -2509,6 +2513,45 @@ VImage VImage::conv( VImage mask , VOption *options ) return( out ); } +VImage VImage::conva( VImage mask , VOption *options ) +{ + VImage out; + + call( "conva" , + (options ? options : VImage::option()) -> + set( "in", *this ) -> + set( "out", &out ) -> + set( "mask", mask ) ); + + return( out ); +} + +VImage VImage::convf( VImage mask , VOption *options ) +{ + VImage out; + + call( "convf" , + (options ? options : VImage::option()) -> + set( "in", *this ) -> + set( "out", &out ) -> + set( "mask", mask ) ); + + return( out ); +} + +VImage VImage::convi( VImage mask , VOption *options ) +{ + VImage out; + + call( "convi" , + (options ? options : VImage::option()) -> + set( "in", *this ) -> + set( "out", &out ) -> + set( "mask", mask ) ); + + return( out ); +} + VImage VImage::compass( VImage mask , VOption *options ) { VImage out; @@ -2535,6 +2578,19 @@ VImage VImage::convsep( VImage mask , VOption *options ) return( out ); } +VImage VImage::convasep( VImage mask , VOption *options ) +{ + VImage out; + + call( "convasep" , + (options ? options : VImage::option()) -> + set( "in", *this ) -> + set( "out", &out ) -> + set( "mask", mask ) ); + + return( out ); +} + VImage VImage::fastcor( VImage ref , VOption *options ) { VImage out; From 314042ea9df377d01f52aa4407804b5107d85939 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 19 Aug 2016 16:11:45 +0100 Subject: [PATCH 06/13] better gif transparency handling we were testing (x == 1), we should have tested (x & 0x1), thanks diegocsandrim see https://github.com/jcupitt/libvips/issues/502 --- ChangeLog | 3 +++ configure.ac | 6 +++--- libvips/foreign/gifload.c | 10 ++++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 18931555..7cf77f53 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +19/8/16 started 8.3.4 +- better transparency handling in gifload, thanks diegocsandrim + 30/7/16 started 8.3.3 - fix performance regression in 8.3.2, thanks Lovell - yet more robust vips file reading diff --git a/configure.ac b/configure.ac index 8a6e60e6..1be7a291 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # also update the version number in the m4 macros below -AC_INIT([vips], [8.3.3], [vipsip@jiscmail.ac.uk]) +AC_INIT([vips], [8.3.4], [vipsip@jiscmail.ac.uk]) # required for gobject-introspection AC_PREREQ(2.62) @@ -18,7 +18,7 @@ AC_CONFIG_MACRO_DIR([m4]) # user-visible library versioning m4_define([vips_major_version], [8]) m4_define([vips_minor_version], [3]) -m4_define([vips_micro_version], [3]) +m4_define([vips_micro_version], [4]) m4_define([vips_version], [vips_major_version.vips_minor_version.vips_micro_version]) @@ -38,7 +38,7 @@ VIPS_VERSION_STRING=$VIPS_VERSION-`date` # binary interface changes not backwards compatible?: reset age to 0 LIBRARY_CURRENT=47 -LIBRARY_REVISION=0 +LIBRARY_REVISION=1 LIBRARY_AGE=5 # patched into include/vips/version.h diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 5fc317db..eeb52172 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -4,6 +4,11 @@ * - from svgload.c * 25/4/16 * - add giflib5 support + * 26/7/16 + * - transparency was wrong if there was no EXTENSION_RECORD + * - write 1, 2, 3, or 4 bands depending on file contents + * 19/8/16 + * - better transparency detection, thanks diegocsandrim */ /* @@ -490,9 +495,10 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) if( ext_code == GRAPHICS_EXT_FUNC_CODE && extension && extension[0] == 4 && - extension[1] == 1 ) { + (extension[1] & 0x1) ) { /* Bytes are 4, 1, delay low, delay high, - * transparency. + * transparency. Bit 1 means transparency + * is being set. */ gif->transparency = extension[4]; From 271d8656e919d5ab7823c5e226e52e0b1740dc9e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 20 Aug 2016 12:59:41 +0100 Subject: [PATCH 07/13] use round() rather than rint() where appropriate rint() rounds to nearest even, rather than nearest ... in some cases, like geometry transforms, we want strict nearest --- ChangeLog | 5 +++-- libvips/colour/Lab2LabQ.c | 6 ++---- libvips/conversion/arrayjoin.c | 10 +--------- libvips/conversion/zoom.c | 24 ++++++++---------------- libvips/create/perlin.c | 14 ++++---------- libvips/create/worley.c | 14 ++++---------- libvips/foreign/dzsave.c | 14 ++++---------- libvips/foreign/vips2tiff.c | 10 +--------- libvips/include/vips/util.h | 22 +++++++++++++++++++--- libvips/iofuncs/threadpool.c | 10 +--------- libvips/resample/reduceh.cpp | 5 +++-- libvips/resample/reducev.cpp | 5 +++-- libvips/resample/shrinkh.c | 5 +++-- libvips/resample/shrinkv.c | 5 +++-- libvips/resample/transform.c | 8 ++++---- 15 files changed, 63 insertions(+), 94 deletions(-) diff --git a/ChangeLog b/ChangeLog index cb2f0767..71963b98 100644 --- a/ChangeLog +++ b/ChangeLog @@ -37,8 +37,9 @@ - support --strip for pngsave - add svgz support [Felix Bünemann] - rename boostrap.sh -> autogen.sh to help snapcraft -- resize/reduce/shrink now round output size to nearest rather than rounding - down, thanks ioquatix +- added VIPS_ROUND as well as VIPS_RINT +- resize/reduce*/shrink*/affine now round output size to nearest rather than + rounding down, thanks ioquatix 30/7/16 started 8.3.3 - fix performance regression in 8.3.2, thanks Lovell diff --git a/libvips/colour/Lab2LabQ.c b/libvips/colour/Lab2LabQ.c index 01d250c4..f8d3df26 100644 --- a/libvips/colour/Lab2LabQ.c +++ b/libvips/colour/Lab2LabQ.c @@ -95,11 +95,9 @@ vips_Lab2LabQ_line( VipsColour *colour, VipsPel *out, VipsPel **in, int width ) int lsbs; int intv; - /* Scale L up to 10 bits. Add 0.5 rather than call VIPS_RINT - * for speed. This will not round negatives correctly! But - * this does not matter, since L is >0. L*=100.0 -> 1023. + /* Scale L up to 10 bits. */ - intv = 10.23 * p[0] + 0.5; /* scale L up to 10 bits */ + intv = VIPS_ROUND_UINT( 10.23 * p[0] ); intv = VIPS_CLIP( 0, intv, 1023 ); lsbs = (intv & 0x3) << 6; /* 00000011 -> 11000000 */ q[0] = intv >> 2; /* drop bot 2 bits and store */ diff --git a/libvips/conversion/arrayjoin.c b/libvips/conversion/arrayjoin.c index a4536a43..35370c7d 100644 --- a/libvips/conversion/arrayjoin.c +++ b/libvips/conversion/arrayjoin.c @@ -50,14 +50,6 @@ #include "pconversion.h" -/* Round N down to P boundary. - */ -#define ROUND_DOWN( N, P ) ((N) - ((N) % P)) - -/* Round N up to P boundary. - */ -#define ROUND_UP( N, P ) (ROUND_DOWN( (N) + (P) - 1, (P) )) - typedef struct _VipsArrayjoin { VipsConversion parent_instance; @@ -179,7 +171,7 @@ vips_arrayjoin_build( VipsObject *object ) /* How many images down the grid? */ - join->down = ROUND_UP( n, join->across ) / join->across; + join->down = VIPS_ROUND_UP( n, join->across ) / join->across; /* The output size. */ diff --git a/libvips/conversion/zoom.c b/libvips/conversion/zoom.c index c41d4e05..8421b091 100644 --- a/libvips/conversion/zoom.c +++ b/libvips/conversion/zoom.c @@ -98,14 +98,6 @@ typedef VipsConversionClass VipsZoomClass; G_DEFINE_TYPE( VipsZoom, vips_zoom, VIPS_TYPE_CONVERSION ); -/* Round N down to P boundary. - */ -#define ROUND_DOWN( N, P ) ((N) - ((N) % P)) - -/* Round N up to P boundary. - */ -#define ROUND_UP( N, P ) (ROUND_DOWN( (N) + (P) - 1, (P) )) - /* Paint the part of the region containing only whole pels. */ static void @@ -265,10 +257,10 @@ vips_zoom_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) /* Area of input we need. We have to round out, as we may have * part-pixels all around the edges. */ - left = ROUND_DOWN( r->left, zoom->xfac ); - right = ROUND_UP( ri, zoom->xfac ); - top = ROUND_DOWN( r->top, zoom->yfac ); - bottom = ROUND_UP( bo, zoom->yfac ); + left = VIPS_ROUND_DOWN( r->left, zoom->xfac ); + right = VIPS_ROUND_UP( ri, zoom->xfac ); + top = VIPS_ROUND_DOWN( r->top, zoom->yfac ); + bottom = VIPS_ROUND_UP( bo, zoom->yfac ); width = right - left; height = bottom - top; s.left = left / zoom->xfac; @@ -280,10 +272,10 @@ vips_zoom_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) /* Find the part of the output (if any) which uses only whole pels. */ - left = ROUND_UP( r->left, zoom->xfac ); - right = ROUND_DOWN( ri, zoom->xfac ); - top = ROUND_UP( r->top, zoom->yfac ); - bottom = ROUND_DOWN( bo, zoom->yfac ); + left = VIPS_ROUND_UP( r->left, zoom->xfac ); + right = VIPS_ROUND_DOWN( ri, zoom->xfac ); + top = VIPS_ROUND_UP( r->top, zoom->yfac ); + bottom = VIPS_ROUND_DOWN( bo, zoom->yfac ); width = right - left; height = bottom - top; diff --git a/libvips/create/perlin.c b/libvips/create/perlin.c index e006a5a6..7d535794 100644 --- a/libvips/create/perlin.c +++ b/libvips/create/perlin.c @@ -71,14 +71,6 @@ typedef struct _VipsPerlinClass { G_DEFINE_TYPE( VipsPerlin, vips_perlin, VIPS_TYPE_CREATE ); -/* Round N down to P boundary. - */ -#define ROUND_DOWN( N, P ) ((N) - ((N) % P)) - -/* Round N up to P boundary. - */ -#define ROUND_UP( N, P ) (ROUND_DOWN( (N) + (P) - 1, (P) )) - /* cos and sin from an angle in 0 - 255. */ float vips_perlin_cos[256]; @@ -261,9 +253,11 @@ vips_perlin_build( VipsObject *object ) /* Be careful if width is a multiple of cell_size. */ - perlin->cells_across = ROUND_UP( perlin->width, perlin->cell_size ) / + perlin->cells_across = + VIPS_ROUND_UP( perlin->width, perlin->cell_size ) / perlin->cell_size; - perlin->cells_down = ROUND_UP( perlin->height, perlin->cell_size ) / + perlin->cells_down = + VIPS_ROUND_UP( perlin->height, perlin->cell_size ) / perlin->cell_size; perlin->seed = g_random_double() * 0xffffffffu; diff --git a/libvips/create/worley.c b/libvips/create/worley.c index 514b7258..bdd30fbd 100644 --- a/libvips/create/worley.c +++ b/libvips/create/worley.c @@ -75,14 +75,6 @@ G_DEFINE_TYPE( VipsWorley, vips_worley, VIPS_TYPE_CREATE ); #define MAX_FEATURES (10) -/* Round N down to P boundary. - */ -#define ROUND_DOWN( N, P ) ((N) - ((N) % P)) - -/* Round N up to P boundary. - */ -#define ROUND_UP( N, P ) (ROUND_DOWN( (N) + (P) - 1, (P) )) - typedef struct _Cell { /* Cell position, in number of cells. Scale by cell_size to get * absolute image cods. @@ -294,9 +286,11 @@ vips_worley_build( VipsObject *object ) /* Be careful if width is a multiple of cell_size. */ - worley->cells_across = ROUND_UP( worley->width, worley->cell_size ) / + worley->cells_across = + VIPS_ROUND_UP( worley->width, worley->cell_size ) / worley->cell_size; - worley->cells_down = ROUND_UP( worley->height, worley->cell_size ) / + worley->cells_down = + VIPS_ROUND_UP( worley->height, worley->cell_size ) / worley->cell_size; worley->seed = g_random_double() * 0xffffffffu; diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 8370de20..c51a8086 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -285,14 +285,6 @@ vips__make_xml_metadata( const char *domain, VipsImage *image ) #include -/* Round N down to P boundary. - */ -#define ROUND_DOWN( N, P ) ((N) - ((N) % P)) - -/* Round N up to P boundary. - */ -#define ROUND_UP( N, P ) (ROUND_DOWN( (N) + (P) - 1, (P) )) - /* Simple wrapper around libgsf. * * We need to be able to do scattered writes to structured files. So while @@ -672,8 +664,10 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, layer->width = width; layer->height = height; - layer->tiles_across = ROUND_UP( width, dz->tile_size ) / dz->tile_size; - layer->tiles_down = ROUND_UP( height, dz->tile_size ) / dz->tile_size; + layer->tiles_across = VIPS_ROUND_UP( width, dz->tile_size ) / + dz->tile_size; + layer->tiles_down = VIPS_ROUND_UP( height, dz->tile_size ) / + dz->tile_size; layer->real_pixels = *real_pixels; diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 6b022ec5..9b882294 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -322,14 +322,6 @@ tiff_openin( const char *name ) return( tif ); } -/* Round N down to P boundary. - */ -#define ROUND_DOWN(N,P) ((N) - ((N) % P)) - -/* Round N up to P boundary. - */ -#define ROUND_UP(N,P) (ROUND_DOWN( (N) + (P) - 1, (P) )) - static Layer * pyramid_new( Write *write, Layer *above, int width, int height ) { @@ -975,7 +967,7 @@ write_new( VipsImage *im, const char *filename, if( im->Coding == VIPS_CODING_LABQ ) write->tls = write->tilew * 3; else if( write->onebit ) - write->tls = ROUND_UP( write->tilew, 8 ) / 8; + write->tls = VIPS_ROUND_UP( write->tilew, 8 ) / 8; else write->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * write->tilew; diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index 01cd8091..a6cc888e 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -53,11 +53,14 @@ extern "C" { #define VIPS_MAX( A, B ) ((A) > (B) ? (A) : (B)) #define VIPS_MIN( A, B ) ((A) < (B) ? (A) : (B)) -#define VIPS_ABS( X ) (((X) >= 0) ? (X) : -(X)) #define VIPS_CLIP( A, V, B ) VIPS_MAX( (A), VIPS_MIN( (B), (V) ) ) +#define VIPS_FCLIP( A, V, B ) VIPS_FMAX( (A), VIPS_FMIN( (B), (V) ) ) + #define VIPS_NUMBER( R ) ((int) (sizeof(R) / sizeof(R[0]))) +#define VIPS_ABS( X ) (((X) >= 0) ? (X) : -(X)) + /* The built-in isnan and isinf functions provided by gcc 4+ and clang are * up to 7x faster than their libc equivalent included from . */ @@ -67,6 +70,7 @@ extern "C" { #define VIPS_FLOOR( V ) __builtin_floor( V ) #define VIPS_CEIL( V ) __builtin_ceil( V ) #define VIPS_RINT( V ) __builtin_rint( V ) +#define VIPS_ROUND( V ) __builtin_round( V ) #define VIPS_FABS( V ) __builtin_fabs( V ) #define VIPS_FMAX( A, B ) __builtin_fmax( A, B ) #define VIPS_FMIN( A, B ) __builtin_fmin( A, B ) @@ -75,13 +79,25 @@ extern "C" { #define VIPS_ISINF( V ) isinf( V ) #define VIPS_FLOOR( V ) floor( V ) #define VIPS_CEIL( V ) ceil( V ) -#define VIPS_RINT( R ) ((int) ((R) > 0 ? ((R) + 0.5) : ((R) - 0.5))) +#define VIPS_RINT( R ) rint( V ) +#define VIPS_ROUND( V ) round( V ) #define VIPS_FABS( V ) VIPS_ABS( V ) #define VIPS_FMAX( A, B ) VIPS_MAX( A, B ) #define VIPS_FMIN( A, B ) VIPS_MIN( A, B ) #endif -#define VIPS_FCLIP( A, V, B ) VIPS_FMAX( (A), VIPS_FMIN( (B), (V) ) ) +/* VIPS_RINT() does "bankers rounding", it rounds to the nerarest even integer. + * For things like image geometry, we want strict nearest int. + * + * If you know it's unsigned, _UINT is a little faster. + */ +#define VIPS_ROUND_INT( R ) ((int) ((R) > 0 ? ((R) + 0.5) : ((R) - 0.5))) +#define VIPS_ROUND_UINT( R ) ((int) ((R) + 0.5)) + +/* Round N down and up to the nearest multiple of P. + */ +#define VIPS_ROUND_DOWN( N, P ) ((N) - ((N) % (P))) +#define VIPS_ROUND_UP( N, P ) (VIPS_ROUND_DOWN( N, P ) + (P)) #define VIPS_SWAP( TYPE, A, B ) \ G_STMT_START { \ diff --git a/libvips/iofuncs/threadpool.c b/libvips/iofuncs/threadpool.c index c70ec47d..559feee8 100644 --- a/libvips/iofuncs/threadpool.c +++ b/libvips/iofuncs/threadpool.c @@ -922,14 +922,6 @@ vips_threadpool_run( VipsImage *im, return( result ); } -/* Round N down to P boundary. - */ -#define ROUND_DOWN(N,P) ((N) - ((N) % P)) - -/* Round N up to P boundary. - */ -#define ROUND_UP(N,P) (ROUND_DOWN( (N) + (P) - 1, (P) )) - /** * vips_get_tile_size: * @im: image to guess for @@ -986,7 +978,7 @@ vips_get_tile_size( VipsImage *im, (1 + nthr / VIPS_MAX( 1, im->Xsize / vips__tile_width )) * 2; *n_lines = VIPS_MAX( *n_lines, vips__fatstrip_height * nthr * 2 ); *n_lines = VIPS_MAX( *n_lines, vips__thinstrip_height * nthr * 2 ); - *n_lines = ROUND_UP( *n_lines, *tile_height ); + *n_lines = VIPS_ROUND_UP( *n_lines, *tile_height ); /* We make this assumption in several places. */ diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index cf51d7f8..bad39044 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -498,13 +498,14 @@ vips_reduceh_build( VipsObject *object ) VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) return( -1 ); - /* Size output. Note: we round to nearest to hide rounding errors. + /* Size output. We need to always round to nearest, so round(), not + * rint(). * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true reduce factor (including the * fractional part), we just see the integer part here. */ - resample->out->Xsize = VIPS_RINT( + resample->out->Xsize = VIPS_ROUND_UINT( (in->Xsize - reduceh->n_point + 1) / reduceh->hshrink ); if( resample->out->Xsize <= 0 ) { vips_error( object_class->nickname, diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index c7913781..16ac50e8 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -775,13 +775,14 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in ) VIPS_DEMAND_STYLE_FATSTRIP, in, NULL ) ) return( -1 ); - /* Size output. Note: we round to nearest to hide rounding errors. + /* Size output. We need to always round to nearest, so round(), not + * rint(). * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true reduce factor (including the * fractional part), we just see the integer part here. */ - resample->out->Ysize = VIPS_RINT( + resample->out->Ysize = VIPS_ROUND_UINT( (in->Ysize - reducev->n_point + 1) / reducev->vshrink ); if( resample->out->Ysize <= 0 ) { vips_error( object_class->nickname, diff --git a/libvips/resample/shrinkh.c b/libvips/resample/shrinkh.c index 0a9b66ef..695b039c 100644 --- a/libvips/resample/shrinkh.c +++ b/libvips/resample/shrinkh.c @@ -275,13 +275,14 @@ vips_shrinkh_build( VipsObject *object ) VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) return( -1 ); - /* Size output. We need to rint() from the size of the original image. + /* Size output. We need to always round to nearest, so round(), not + * rint(). * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true shrink factor (including the * fractional part), we just see the integer part here. */ - resample->out->Xsize = VIPS_RINT( + resample->out->Xsize = VIPS_ROUND_UINT( resample->in->Xsize / shrink->hshrink ); if( resample->out->Xsize <= 0 ) { vips_error( class->nickname, diff --git a/libvips/resample/shrinkv.c b/libvips/resample/shrinkv.c index 001e4842..9e061566 100644 --- a/libvips/resample/shrinkv.c +++ b/libvips/resample/shrinkv.c @@ -379,13 +379,14 @@ vips_shrinkv_build( VipsObject *object ) VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) return( -1 ); - /* Size output. We need to rint() from the size of the original image. + /* Size output. We need to always round to nearest, so round(), not + * rint(). * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true shrink factor (including the * fractional part), we just see the integer part here. */ - resample->out->Ysize = VIPS_RINT( + resample->out->Ysize = VIPS_ROUND_UINT( resample->in->Ysize / shrink->vshrink ); if( resample->out->Ysize <= 0 ) { vips_error( class->nickname, diff --git a/libvips/resample/transform.c b/libvips/resample/transform.c index 116a6090..30b275ab 100644 --- a/libvips/resample/transform.c +++ b/libvips/resample/transform.c @@ -215,10 +215,10 @@ transform_rect( const VipsTransformation *trn, transform_fn transform, top = VIPS_MIN( y1, VIPS_MIN( y2, VIPS_MIN( y3, y4 ) ) ); bottom = VIPS_MAX( y1, VIPS_MAX( y2, VIPS_MAX( y3, y4 ) ) ); - out->left = VIPS_RINT( left ); - out->top = VIPS_RINT( top ); - out->width = VIPS_RINT( right - left ); - out->height = VIPS_RINT( bottom - top ); + out->left = VIPS_ROUND_INT( left ); + out->top = VIPS_ROUND_INT( top ); + out->width = VIPS_ROUND_INT( right - left ); + out->height = VIPS_ROUND_INT( bottom - top ); } /* Given an area in the input image, calculate the bounding box for those From c4a1ac631081cb8d775bbe594a9c903cac2da8b4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 20 Aug 2016 13:18:25 +0100 Subject: [PATCH 08/13] fix ROUND_UP, add a test for felix's corner case --- libvips/include/vips/util.h | 2 +- test/test_resample.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index a6cc888e..5a8ecc77 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -97,7 +97,7 @@ extern "C" { /* Round N down and up to the nearest multiple of P. */ #define VIPS_ROUND_DOWN( N, P ) ((N) - ((N) % (P))) -#define VIPS_ROUND_UP( N, P ) (VIPS_ROUND_DOWN( N, P ) + (P)) +#define VIPS_ROUND_UP( N, P ) (VIPS_ROUND_DOWN( (N) + (P) - 1, (P) )) #define VIPS_SWAP( TYPE, A, B ) \ G_STMT_START { \ diff --git a/test/test_resample.py b/test/test_resample.py index ce9fb5ed..02941666 100755 --- a/test/test_resample.py +++ b/test/test_resample.py @@ -170,6 +170,12 @@ class TestResample(unittest.TestCase): self.assertEqual(im2.width, round(im.width / 4.0)) self.assertEqual(im2.height, round(im.height / 4.0)) + # test geometry rounding corner case + im = Vips.Image.black(100, 1); + x = im.resize(0.5) + self.assertEqual(x.width, 50) + self.assertEqual(x.height, 1) + def test_shrink(self): im = Vips.Image.new_from_file("images/IMG_4618.jpg") im2 = im.shrink(4, 4) From 0c2d7c955a0655de0885abef85ea18d700b19470 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 1 Sep 2016 22:40:36 +0100 Subject: [PATCH 09/13] tiny doc fixes --- libvips/foreign/gifload.c | 4 ++-- libvips/foreign/tiffload.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 96f39ae8..0cbdeaf8 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -829,7 +829,7 @@ vips_foreign_load_gif_buffer_init( VipsForeignLoadGifBuffer *buffer ) * * Optional arguments: * - * * @page: %ginit, page (frame) to read + * * @page: %gint, page (frame) to read * * Read a GIF file into a VIPS image. Rendering uses the giflib library. * @@ -864,7 +864,7 @@ vips_gifload( const char *filename, VipsImage **out, ... ) * * Optional arguments: * - * * @page: %ginit, page (frame) to read + * * @page: %gint, page (frame) to read * * Read a GIF-formatted memory block into a VIPS image. Exactly as * vips_gifload(), but read from a memory buffer. diff --git a/libvips/foreign/tiffload.c b/libvips/foreign/tiffload.c index a58ee412..97b5109a 100644 --- a/libvips/foreign/tiffload.c +++ b/libvips/foreign/tiffload.c @@ -301,7 +301,7 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * * Optional arguments: * - * * @page: int, load this page + * * @page: %gint, load this page * * @autorotate: %gboolean, use orientation tag to rotate the image * during load * From dac05244f81594f989cc6fde4033db6ac43d994a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 4 Sep 2016 05:39:44 +0100 Subject: [PATCH 10/13] fix crash with empty blob metadata a blob metadata item with length 0 triggered a segv --- libvips/iofuncs/base64.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libvips/iofuncs/base64.c b/libvips/iofuncs/base64.c index dc072f64..cfdf21c1 100644 --- a/libvips/iofuncs/base64.c +++ b/libvips/iofuncs/base64.c @@ -228,9 +228,10 @@ vips__b64_decode( const char *buffer, size_t *data_length ) { const size_t buffer_length = strlen( buffer ); - /* Worst case. + /* Worst case. Add one, since we don't want to return NULL for an empty + * input string, it would look like an error return. */ - const size_t output_data_length = buffer_length * 3 / 4; + const size_t output_data_length = 1 + buffer_length * 3 / 4; unsigned char *data; unsigned char *p; From 89509337d114d9cec5f1dfc592966ad34929b7bf Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 4 Sep 2016 06:01:11 +0100 Subject: [PATCH 11/13] fix segv for length 0 blob print "vipsheader -f icc-profile-data" could segv for zero-length blob --- libvips/iofuncs/base64.c | 4 ---- libvips/iofuncs/init.c | 5 +++++ libvips/iofuncs/type.c | 10 ++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/libvips/iofuncs/base64.c b/libvips/iofuncs/base64.c index cfdf21c1..6808c616 100644 --- a/libvips/iofuncs/base64.c +++ b/libvips/iofuncs/base64.c @@ -168,10 +168,6 @@ vips__b64_encode( const unsigned char *data, size_t data_length ) int i; int cursor; - if( data_length == 0 ) { - vips_error( "vips__b64_encode", "%s", _( "too little data" ) ); - return( NULL ); - } if( output_data_length > 1024 * 1024 ) { /* We shouldn't really be used for large amounts of data, plus * we are using int offsets. diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 11aafcf3..7398374e 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -467,8 +467,13 @@ vips_leak( void ) vips_buf_append_size( &buf, vips_tracked_get_mem_highwater() ); vips_buf_appends( &buf, "\n" ); + if( strlen( vips_error_buffer() ) > 0 ) + vips_buf_appendf( &buf, "error buffer: %s", + vips_error_buffer() ); + fprintf( stderr, "%s", vips_buf_all( &buf ) ); + #ifdef DEBUG vips_buffer_dump_all(); #endif /*DEBUG*/ diff --git a/libvips/iofuncs/type.c b/libvips/iofuncs/type.c index 23ca5919..0c7d26b8 100644 --- a/libvips/iofuncs/type.c +++ b/libvips/iofuncs/type.c @@ -670,6 +670,11 @@ transform_blob_save_string( const GValue *src_value, GValue *dest_value ) vips_value_set_save_string( dest_value, b64 ); vips_free( b64 ); } + else + /* No error return from transform, but we should set it to + * something. + */ + vips_value_set_save_string( dest_value, "" ); } static void @@ -683,6 +688,11 @@ transform_save_string_blob( const GValue *src_value, GValue *dest_value ) if( (blob = vips__b64_decode( b64, &blob_length )) ) vips_value_set_blob( dest_value, (VipsCallbackFn) vips_free, blob, blob_length ); + else + /* No error return from transform, but we should set it to + * something. + */ + vips_value_set_save_string( dest_value, "" ); } GType From 881f34061067c54b6adec913540bc66bdd3de600 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 4 Sep 2016 06:12:35 +0100 Subject: [PATCH 12/13] increase size limit for b64 encoded-data we had 1 mb limit, but some large cmyk profiles can bust through that, up it to 10mb --- libvips/iofuncs/base64.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libvips/iofuncs/base64.c b/libvips/iofuncs/base64.c index 6808c616..c972d3f3 100644 --- a/libvips/iofuncs/base64.c +++ b/libvips/iofuncs/base64.c @@ -168,9 +168,11 @@ vips__b64_encode( const unsigned char *data, size_t data_length ) int i; int cursor; - if( output_data_length > 1024 * 1024 ) { + if( output_data_length > 10 * 1024 * 1024 ) { /* We shouldn't really be used for large amounts of data, plus * we are using int offsets. + * + * A large ICC profile can be 1MB, so allow 10MB of b64. */ vips_error( "vips__b64_encode", "%s", _( "too much data" ) ); return( NULL ); From e971a5070098e0941186b9f39d88e410e228f262 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 4 Sep 2016 06:13:54 +0100 Subject: [PATCH 13/13] improve docs for icc_* note use of _get_typeof() to spot embedded profiles, see https://github.com/jcupitt/libvips/issues/511 --- libvips/colour/icc_transform.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index 92c29785..43c4c891 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -1265,7 +1265,11 @@ vips_icc_ac2rc( VipsImage *in, VipsImage **out, const char *profile_filename ) * * If @embedded is set, the input profile is taken from the input image * metadata. If there is no embedded profile, - * @input_profile_filename is used as a fall-back. + * @input_profile_filename is used as a fall-back. + * You can test for the + * presence of an embedded profile with + * vips_image_get_typeof() with #VIPS_META_ICC_NAME as an argument. This will + * return %GType 0 if there is no profile. * * If @embedded is not set, the input profile is taken from * @input_profile. If @input_profile is not supplied, the @@ -1342,11 +1346,18 @@ vips_icc_export( VipsImage *in, VipsImage **out, ... ) * If @embedded is set, the input profile is taken from the input image * metadata, if present. If there is no embedded profile, * @input_profile is used as a fall-back. + * You can test for the + * presence of an embedded profile with + * vips_image_get_typeof() with #VIPS_META_ICC_NAME as an argument. This will + * return %GType 0 if there is no profile. * * If @embedded is not set, the input profile is taken from * @input_profile. If @input_profile is not supplied, the * metadata profile, if any, is used as a fall-back. * + * The output image has the output profile attached to the #VIPS_META_ICC_NAME + * field. + * * Use vips_icc_import() and vips_icc_export() to do either the first or * second half of this operation in isolation. *