diff --git a/TODO b/TODO index b7a0392d..5f5b7803 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,15 @@ -- see +- strange - https://en.wikipedia.org/wiki/Lanczos_resampling + john@kiwi:~/pics$ vips gaussblur babe.jpg x.v 0.2 + gaussmat: mask too large + +- removed the cache from resize since we no longer sharpen, can we get + out-of-order reads? + +- vips_resize() needs to take a param for kernel rather than interpolate? + maybe just don't expose this + +- what demand hint are we setting for the reduce / shrink funcs? - try SEQ_UNBUFFERED on jpg source, get out of order error? diff --git a/libvips/create/gaussmat.c b/libvips/create/gaussmat.c index 89f27688..32529d36 100644 --- a/libvips/create/gaussmat.c +++ b/libvips/create/gaussmat.c @@ -18,6 +18,8 @@ * 16/12/14 * - default to int output to match vips_conv() * - use @precision, not @integer + * 10/3/16 + * - allow 1x1 masks */ /* @@ -84,6 +86,10 @@ typedef struct _VipsGaussmatClass { G_DEFINE_TYPE( VipsGaussmat, vips_gaussmat, VIPS_TYPE_CREATE ); +/* Don't allow mask radius to go over this. + */ +#define MASK_SANITY (5000) + static int vips_gaussmat_build( VipsObject *object ) { @@ -91,7 +97,7 @@ vips_gaussmat_build( VipsObject *object ) VipsCreate *create = VIPS_CREATE( object ); VipsGaussmat *gaussmat = (VipsGaussmat *) object; double sig2 = 2. * gaussmat->sigma * gaussmat->sigma; - int max_x = 8 * gaussmat->sigma > 5000 ? 5000 : 8 * gaussmat->sigma ; + int max_x = VIPS_CLIP( 0, 8 * gaussmat->sigma, MASK_SANITY ); int x, y; int width, height; @@ -114,7 +120,7 @@ vips_gaussmat_build( VipsObject *object ) return( -1 ); /* Find the size of the mask. Limit the mask size to 10k x 10k for - * sanity. + * sanity. We allow x == 0, meaning a 1x1 mask. */ for( x = 0; x < max_x; x++ ) { double v = exp( - ((double)(x * x)) / sig2 ); @@ -122,7 +128,7 @@ vips_gaussmat_build( VipsObject *object ) if( v < gaussmat->min_ampl ) break; } - if( x == max_x ) { + if( x >= MASK_SANITY ) { vips_error( class->nickname, "%s", _( "mask too large" ) ); return( -1 ); } diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index 821a9995..a29657e2 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -58,6 +58,11 @@ int vips_reduce( VipsImage *in, VipsImage **out, int vips_reduceh( VipsImage *in, VipsImage **out, double xshrink, ... ); int vips_reducev( VipsImage *in, VipsImage **out, double yshrink, ... ); +int vips_reducel3( VipsImage *in, VipsImage **out, + double xshrink, double yshrink, ... ); +int vips_reducehl3( VipsImage *in, VipsImage **out, double xshrink, ... ); +int vips_reducevl3( VipsImage *in, VipsImage **out, double yshrink, ... ); + int vips_similarity( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_affine( VipsImage *in, VipsImage **out, diff --git a/libvips/resample/Makefile.am b/libvips/resample/Makefile.am index a2d5b5fd..dd670d4f 100644 --- a/libvips/resample/Makefile.am +++ b/libvips/resample/Makefile.am @@ -10,6 +10,7 @@ libresample_la_SOURCES = \ shrinkh.c \ shrinkv.c \ reduce.c \ + reducel3.c \ reduceh.cpp \ reducehl3.cpp \ reducev.cpp \ diff --git a/libvips/resample/reducehl3.cpp b/libvips/resample/reducehl3.cpp index 24179fff..77ecbd6c 100644 --- a/libvips/resample/reducehl3.cpp +++ b/libvips/resample/reducehl3.cpp @@ -184,24 +184,30 @@ reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, * 11-point kernel for the vector version to be worthwhile. */ static void inline -reducehl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, +reducehl3_unsigned_uint8_6tab( VipsPel *out, const VipsPel *in, const int bands, const int *cx ) { const int b1 = bands; const int b2 = b1 + b1; const int b3 = b1 + b2; + const int b4 = b2 + b2; + const int b5 = b1 + b4; const int c0 = cx[0]; const int c1 = cx[1]; const int c2 = cx[2]; const int c3 = cx[3]; + const int c4 = cx[4]; + const int c5 = cx[5]; for( int z = 0; z < bands; z++ ) { int cubich = unsigned_fixed_round( c0 * in[0] + c1 * in[b1] + c2 * in[b2] + - c3 * in[b3] ); + c3 * in[b3] + + c4 * in[b4] + + c5 * in[b5] ); cubich = VIPS_CLIP( 0, cubich, 255 ); @@ -368,8 +374,8 @@ vips_reducehl3_gen( VipsRegion *out_region, void *seq, switch( in->BandFmt ) { case VIPS_FORMAT_UCHAR: - if( reducehl3->n_points == 4 ) - reducehl3_unsigned_uint8_4tab( + if( reducehl3->n_points == 6 ) + reducehl3_unsigned_uint8_6tab( q, p, bands, cxi ); else reducehl3_unsigned_int_tab @@ -560,14 +566,14 @@ vips_reducehl3_class_init( VipsReducehl3Class *reducehl3_class ) _( "Resampling kernel" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsReducehl3, kernel ), - VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); + VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); } static void vips_reducehl3_init( VipsReducehl3 *reducehl3 ) { - reducehl3->kernel = VIPS_KERNEL_CUBIC; + reducehl3->kernel = VIPS_KERNEL_LANCZOS3; } /** @@ -579,7 +585,7 @@ vips_reducehl3_init( VipsReducehl3 *reducehl3 ) * * Optional arguments: * - * @kernel: #VipsKernel to use to interpolate (default: cubic) + * @kernel: #VipsKernel to use to interpolate (default: lanczos3) * * Reduce @in horizontally by a float factor. The pixels in @out are * interpolated with a 1D mask. This operation will not work well for diff --git a/libvips/resample/reducel3.c b/libvips/resample/reducel3.c new file mode 100644 index 00000000..b59105f1 --- /dev/null +++ b/libvips/resample/reducel3.c @@ -0,0 +1,176 @@ +/* 2D reducel3 ... call reducel3h and reducel3v + * + * 27/1/16 + * - from shrink.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#include "presample.h" + +typedef struct _VipsReducel3 { + VipsResample parent_instance; + + double xshrink; /* Shrink factors */ + double yshrink; + + /* The thing we use to make the kernel. + */ + VipsKernel kernel; + +} VipsReducel3; + +typedef VipsResampleClass VipsReducel3Class; + +G_DEFINE_TYPE( VipsReducel3, vips_reducel3, VIPS_TYPE_RESAMPLE ); + +static int +vips_reducel3_build( VipsObject *object ) +{ + VipsResample *resample = VIPS_RESAMPLE( object ); + VipsReducel3 *reducel3 = (VipsReducel3 *) object; + VipsImage **t = (VipsImage **) + vips_object_local_array( object, 3 ); + + if( VIPS_OBJECT_CLASS( vips_reducel3_parent_class )->build( object ) ) + return( -1 ); + + if( vips_reducevl3( resample->in, &t[0], reducel3->yshrink, + "kernel", reducel3->kernel, + NULL ) || + vips_reducehl3( t[0], &t[1], reducel3->xshrink, + "kernel", reducel3->kernel, + NULL ) || + vips_image_write( t[1], resample->out ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_reducel3_class_init( VipsReducel3Class *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + VIPS_DEBUG_MSG( "vips_reducel3_class_init\n" ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "reducel3"; + vobject_class->description = _( "reducel3 an image" ); + vobject_class->build = vips_reducel3_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_DOUBLE( class, "xshrink", 8, + _( "Xshrink" ), + _( "Horizontal shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsReducel3, xshrink ), + 1.0, 1000000.0, 1.0 ); + + VIPS_ARG_DOUBLE( class, "yshrink", 9, + _( "Yshrink" ), + _( "Vertical shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsReducel3, yshrink ), + 1.0, 1000000.0, 1.0 ); + + VIPS_ARG_ENUM( class, "kernel", 3, + _( "Kernel" ), + _( "Resampling kernel" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsReducel3, kernel ), + VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); + +} + +static void +vips_reducel3_init( VipsReducel3 *reducel3 ) +{ + reducel3->kernel = VIPS_KERNEL_LANCZOS3; +} + +/** + * vips_reducel3: + * @in: input image + * @out: output image + * @xshrink: horizontal shrink + * @shrinke: vertical shrink + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * @kernel: #VipsKernel to use to interpolate (default: lanczos3) + * + * Reducel3 @in by a pair of factors with a pair of 1D interpolators. This + * will not work well for shrink factors greater than two. + * + * This is a very low-level operation: see vips_resize() for a more + * convenient way to resize images. + * + * This operation does not change xres or yres. The image resolution needs to + * be updated by the application. + * + * See also: vips_resize(), vips_affine(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_reducel3( VipsImage *in, VipsImage **out, + double xshrink, double yshrink, ... ) +{ + va_list ap; + int result; + + va_start( ap, yshrink ); + result = vips_call_split( "reducel3", ap, in, out, xshrink, yshrink ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/resample/reducevl3.cpp b/libvips/resample/reducevl3.cpp index 8fb7cba4..2f0ce6ed 100644 --- a/libvips/resample/reducevl3.cpp +++ b/libvips/resample/reducevl3.cpp @@ -115,24 +115,30 @@ reducevl3_unsigned_int_tab( VipsReducevl3 *reducevl3, /* An unrolled version of ^^ for the most common case. */ static void inline -reducevl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, +reducevl3_unsigned_uint8_6tab( VipsPel *out, const VipsPel *in, const int ne, const int lskip, const int *cy ) { const int l1 = lskip; const int l2 = l1 + l1; const int l3 = l1 + l2; + const int l4 = l2 + l2; + const int l5 = l4 + l1; const int c0 = cy[0]; const int c1 = cy[1]; const int c2 = cy[2]; const int c3 = cy[3]; + const int c4 = cy[4]; + const int c5 = cy[5]; for( int z = 0; z < ne; z++ ) { int sum = unsigned_fixed_round( c0 * in[0] + c1 * in[l1] + c2 * in[l2] + - c3 * in[l3] ); + c3 * in[l3] + + c4 * in[l4] + + c5 * in[l5] ); sum = VIPS_CLIP( 0, sum, 255 ); @@ -288,8 +294,8 @@ vips_reducevl3_gen( VipsRegion *out_region, void *seq, switch( in->BandFmt ) { case VIPS_FORMAT_UCHAR: - if( reducevl3->n_points == 4 ) - reducevl3_unsigned_uint8_4tab( + if( reducevl3->n_points == 6 ) + reducevl3_unsigned_uint8_6tab( q, p, ne, lskip, cyi ); else reducevl3_unsigned_int_tab @@ -474,14 +480,14 @@ vips_reducevl3_class_init( VipsReducevl3Class *reducevl3_class ) _( "Resampling kernel" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsReducevl3, kernel ), - VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); + VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); } static void vips_reducevl3_init( VipsReducevl3 *reducevl3 ) { - reducevl3->kernel = VIPS_KERNEL_CUBIC; + reducevl3->kernel = VIPS_KERNEL_LANCZOS3; } /** @@ -493,7 +499,7 @@ vips_reducevl3_init( VipsReducevl3 *reducevl3 ) * * Optional arguments: * - * @kernel: #VipsKernel to use to interpolate (default: cubic) + * @kernel: #VipsKernel to use to interpolate (default: lanczos3) * * Reduce @in vertically by a float factor. The pixels in @out are * interpolated with a 1D mask. This operation will not work well for diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index 1b45f328..d73c7b51 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -138,6 +138,7 @@ vips_resample_operation_init( void ) extern GType vips_shrinkh_get_type( void ); extern GType vips_shrinkv_get_type( void ); extern GType vips_reduce_get_type( void ); + extern GType vips_reducel3_get_type( void ); extern GType vips_reduceh_get_type( void ); extern GType vips_reducehl3_get_type( void ); extern GType vips_reducev_get_type( void ); @@ -156,6 +157,7 @@ vips_resample_operation_init( void ) vips_reducev_get_type(); vips_reducevl3_get_type(); vips_reduce_get_type(); + vips_reducel3_get_type(); vips_quadratic_get_type(); vips_affine_get_type(); vips_similarity_get_type(); diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 55262404..7e81200f 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -8,6 +8,8 @@ * - smarter cache sizing * 29/2/16 * - shrink more affine less, now we have better anti-alias settings + * 10/3/16 + * - revise again, using new vips_reduce() code */ /* @@ -65,6 +67,9 @@ typedef struct _VipsResize { double scale; double vscale; + + /* Deprecated. + */ VipsInterpolate *interpolate; double idx; double idy; @@ -82,11 +87,11 @@ vips_resize_build( VipsObject *object ) VipsResample *resample = VIPS_RESAMPLE( object ); VipsResize *resize = (VipsResize *) object; - VipsImage **t = (VipsImage **) - vips_object_local_array( object, 7 ); + VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 ); VipsImage *in; - int window_size; + int target_width; + int target_height; int int_hshrink; int int_vshrink; double hresidual; @@ -97,115 +102,45 @@ vips_resize_build( VipsObject *object ) if( VIPS_OBJECT_CLASS( vips_resize_parent_class )->build( object ) ) return( -1 ); - if( !vips_object_argument_isset( object, "interpolate" ) ) { - VipsInterpolate *interpolate; - char *nick; - - if( vips_type_find( "VipsInterpolate", "bicubic" ) ) - nick = "bicubic"; - else - nick = "bilinear"; - interpolate = vips_interpolate_new( nick ); - g_object_set( object, "interpolate", interpolate, NULL ); - VIPS_UNREF( interpolate ); - } - - /* Unset vscale means it's equal to hscale. - */ - if( !vips_object_argument_isset( object, "vscale" ) ) - resize->vscale = resize->scale; - in = resample->in; - window_size = resize->interpolate ? - vips_interpolate_get_window_size( resize->interpolate ) : 2; + /* The image size we are aiming for. + */ + target_width = in->Xsize * resize->scale; + if( vips_object_argument_isset( object, "vscale" ) ) + target_height = in->Ysize * resize->vscale; + else + target_height = in->Ysize * resize->scale; /* If the factor is > 1.0, we need to zoom rather than shrink. * Just set the int part to 1 in this case. */ int_hshrink = resize->scale > 1.0 ? 1 : VIPS_FLOOR( 1.0 / resize->scale ); - int_vshrink = resize->vscale > 1.0 ? - 1 : VIPS_FLOOR( 1.0 / resize->vscale ); + if( vips_object_argument_isset( object, "vscale" ) ) + int_vshrink = resize->vscale > 1.0 ? + 1 : VIPS_FLOOR( 1.0 / resize->vscale ); + else + int_vshrink = int_hshrink; - /* We want to shrink by less for interpolators with large windows. - */ - int_hshrink = VIPS_MAX( 1, - int_hshrink / VIPS_MAX( 1, window_size / 3 ) ); - int_vshrink = VIPS_MAX( 1, - int_vshrink / VIPS_MAX( 1, window_size / 3 ) ); - - /* Will this produce a residual scale of almost 1? shrink a bit less - * if it will. - */ - if( (in->Xsize * resize->scale) / (in->Xsize / int_hshrink) > 0.9 ) - int_hshrink = VIPS_MAX( 1, int_hshrink - 1 ); - if( (in->Ysize * resize->vscale) / (in->Ysize / int_vshrink) > 0.9 ) - int_vshrink = VIPS_MAX( 1, int_vshrink - 1 ); - - /* Residual scale factor is. - */ - hresidual = (in->Xsize * resize->scale) / (in->Xsize / int_hshrink); - vresidual = (in->Ysize * resize->vscale) / (in->Ysize / int_vshrink); - - /* A copy for enlarge resize. - */ - vips_info( class->nickname, "box shrink by %d x %d", - int_hshrink, int_vshrink ); - if( vips_shrink( in, &t[0], int_hshrink, int_vshrink, NULL ) ) - return( -1 ); - in = t[0]; - - /* We want to make sure we read the image sequentially. - * However, the convolution we may be doing later will force us - * into SMALLTILE or maybe FATSTRIP mode and that will break - * sequentiality. - * - * So ... read into a cache where tiles are scanlines, and make sure - * we keep enough scanlines. - * - * We use a threaded tilecache to avoid a deadlock: suppose thread1, - * evaluating the top block of the output, is delayed, and thread2, - * evaluating the second block, gets here first (this can happen on - * a heavily-loaded system). - * - * With an unthreaded tilecache, thread2 will get - * the cache lock and start evaling the second block of the shrink. - * When it reaches the png reader it will stall until the first block - * has been used ... but it never will, since thread1 will block on - * this cache lock. - * - * Cache sizing: we double-buffer writes, so threads can be up to one - * line of tiles behind. For example, one thread could be allocated - * tile (0,0) and then stall, the whole write system won't stall until - * it tries to allocate tile (0, 2). - * - * We affine down after this, which can be a scale of up to @residual, - * perhaps 0.5 or down as low as 0.3 depending on the interpolator. So - * the number of scanlines we need to keep for the worst case is - * 2 * @tile_height / @residual, plus a little extra. - */ - if( int_vshrink > 1 ) { - int tile_width; - int tile_height; - int n_lines; - - int need_lines; - - vips_get_tile_size( in, - &tile_width, &tile_height, &n_lines ); - need_lines = 1.2 * n_lines / vresidual; - if( vips_tilecache( in, &t[6], - "tile_width", in->Xsize, - "tile_height", 10, - "max_tiles", 1 + need_lines / 10, - "access", VIPS_ACCESS_SEQUENTIAL, - "threaded", TRUE, - NULL ) ) + if( int_hshrink > 1 || + int_vshrink > 1 ) { + vips_info( class->nickname, "box shrink by %d x %d", + int_hshrink, int_vshrink ); + if( vips_shrink( in, &t[0], int_hshrink, int_vshrink, NULL ) ) return( -1 ); - in = t[6]; + in = t[0]; } + /* Do we need a further size adjustment? It's the difference + * between our target size and the size we have after vips_shrink(). + */ + hresidual = (double) target_width / in->Xsize; + if( vips_object_argument_isset( object, "vscale" ) ) + vresidual = (double) target_height / in->Ysize; + else + vresidual = hresidual; + /* If the final affine will be doing a large downsample, we can get * nasty aliasing on hard edges. Blur before affine to smooth this out. * @@ -215,61 +150,34 @@ vips_resize_build( VipsObject *object ) * Don't try to be clever for non-rectangular shrinks. We just * consider the horizontal factor. */ - sigma = ((1.0 / hresidual) - 0.5) / 2.0; - anti_alias = hresidual < 1.0 && sigma > 0.1; + sigma = ((1.0 / hresidual) - 0.5) / 2.5; + anti_alias = hresidual < 0.9 && sigma > 0.1; if( anti_alias ) { vips_info( class->nickname, "anti-alias sigma %g", sigma ); - if( vips_gaussblur( in, &t[2], sigma, NULL ) ) + if( vips_gaussblur( in, &t[1], sigma, NULL ) ) + return( -1 ); + in = t[1]; + } + + if( hresidual < 1.0 || + vresidual < 1.0 ) { + vips_info( class->nickname, "residual reduce by %g x %g", + hresidual, vresidual ); + + if( vips_reducel3( in, &t[2], + 1.0 / hresidual, 1.0 / vresidual, NULL ) ) return( -1 ); in = t[2]; } - - vips_info( class->nickname, "residual affine %g x %g", - hresidual, vresidual ); - if( resize->interpolate ) { - const char *nickname = - VIPS_OBJECT_GET_CLASS( resize->interpolate )->nickname; - - vips_info( class->nickname, "%s interpolation", nickname ); - } - - /* We have a special path for bicubic, idx/idy == 0. - */ - if( resize->interpolate && - strcmp( VIPS_OBJECT_GET_CLASS( resize->interpolate )->nickname, - "bicubic" ) == 0 && - resize->idx == 0.0 && - resize->idy == 0.0 ) { - vips_info( class->nickname, "using fast path for residual" ); - if( vips_reduce( in, &t[3], - 1.0 / hresidual, 1.0 / vresidual, NULL ) ) - return( -1 ); - } - else { + if( hresidual > 1.0 || + vresidual > 1.0 ) { + vips_info( class->nickname, "residual scale %g x %g", + hresidual, vresidual ); if( vips_affine( in, &t[3], hresidual, 0, 0, vresidual, - "interpolate", resize->interpolate, - "idx", resize->idx, - "idy", resize->idy, + "interpolate", vips_interpolate_nearest_static(), NULL ) ) return( -1 ); - } - in = t[3]; - - /* If we are upsampling, don't sharpen. Also don't sharpen if we - * skipped the anti-alias filter. - */ - if( int_hshrink >= 1 && - anti_alias ) { - vips_info( class->nickname, "final sharpen" ); - t[5] = vips_image_new_matrixv( 3, 3, - -1.0, -1.0, -1.0, - -1.0, 32.0, -1.0, - -1.0, -1.0, -1.0 ); - vips_image_set_double( t[5], "scale", 24 ); - - if( vips_conv( in, &t[4], t[5], NULL ) ) - return( -1 ); - in = t[4]; + in = t[3]; } if( vips_image_write( in, resample->out ) ) @@ -310,25 +218,33 @@ vips_resize_class_init( VipsResizeClass *class ) G_STRUCT_OFFSET( VipsResize, vscale ), 0, 10000000, 0 ); - VIPS_ARG_INTERPOLATE( class, "interpolate", 2, - _( "Interpolate" ), - _( "Interpolate pixels with this" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsResize, interpolate ) ); - + /* We used to let people set the input offset so you could pick centre + * or corner interpolation, but it's not clear this was useful. + */ VIPS_ARG_DOUBLE( class, "idx", 115, _( "Input offset" ), _( "Horizontal input displacement" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsResize, idx ), -10000000, 10000000, 0 ); VIPS_ARG_DOUBLE( class, "idy", 116, _( "Input offset" ), _( "Vertical input displacement" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsResize, idy ), -10000000, 10000000, 0 ); + + /* We used to let people set the interpolator, but it's not clear this + * was useful. Anyway, vips_reduce() no longer has an interpolator + * param. + */ + VIPS_ARG_INTERPOLATE( class, "interpolate", 2, + _( "Interpolate" ), + _( "Interpolate pixels with this" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsResize, interpolate ) ); + } static void @@ -346,29 +262,21 @@ vips_resize_init( VipsResize *resize ) * Optional arguments: * * @vscale: vertical scale factor - * @interpolate: interpolate pixels with this - * @idx: input horizontal offset - * @idy: input vertical offset * - * Resize an image. When upsizing (@scale > 1), the image is simply resized - * with vips_affine() and the supplied @interpolate. When downsizing, the - * image is block-shrunk with vips_shrink() to roughly half the interpolator - * window size above the target size, then blurred with an anti-alias filter, - * then resampled with vips_affine() and the supplied interpolator, then - * sharpened. + * Resize an image. When upsizing (@scale > 1), the image is simply block + * upsized. When downsizing, the + * image is block-shrunk with vips_shrink(), then an anti-alias blur is + * applied with vips_gaussblur(), then the image is shrunk again to the + * target size with vips_reduce(). * * vips_resize() normally maintains the image apect ratio. If you set * @vscale, that factor is used for the vertical scale and @scale for the * horizontal. * - * @interpolate defaults to bicubic. - * - * @idx, @idy default to zero. Set them to 0.5 to get pixel-centre sampling. - * * This operation does not change xres or yres. The image resolution needs to * be updated by the application. * - * See also: vips_shrink(), vips_affine(), #VipsInterpolate. + * See also: vips_shrink(), vips_reduce(), vips_gaussblur(). * * Returns: 0 on success, -1 on error */ diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 4d36484a..b7ad7511 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -189,9 +189,11 @@ calculate_shrink( VipsImage *im ) * * In crop mode we aim to fill the bounding box, so we must use the * smaller axis. + * + * Take off a tiny amount to stop us rounding below the target. */ - double horizontal = (double) width / thumbnail_width; - double vertical = (double) height / thumbnail_height; + double horizontal = (double) width / thumbnail_width - 0.0000001; + double vertical = (double) height / thumbnail_height - 0.0000001; if( crop_image ) { if( horizontal < vertical ) @@ -518,7 +520,9 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, shrink = calculate_shrink( in ); - if( vips_resize( in, &t[4], 1.0 / shrink, + /* Add a tiny amount to stop rounding below the target. + */ + if( vips_resize( in, &t[4], 1.0 / shrink + 0.0000000001, "interpolate", interp, NULL ) ) return( NULL );