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 )