From f3326c812679c67a9a34c0215ecdc0e5deb6f243 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 16 Jun 2016 09:46:02 +0100 Subject: [PATCH] better vips_resize() for cubic/linear/nearest vips_resize() used to do most of a downsize with vips_shrink() and the final 200 - 300% with vips_reduce(). This was correct for lanczos2/3, but not right for linear/cubic, which need more shrink and less reduce to avoid aliasing. This patch makes vips_resize() leave the final 100 - 200% to vips_reduce() for linear/cubic, and leave everything to reduce for nearest. --- ChangeLog | 1 + TODO | 20 ----------- libvips/resample/reduceh.cpp | 5 ++- libvips/resample/reducev.cpp | 31 ++++++++-------- libvips/resample/resize.c | 70 ++++++++++++++++++++++++------------ libvips/resample/shrinkv.c | 6 ++-- 6 files changed, 68 insertions(+), 65 deletions(-) diff --git a/ChangeLog b/ChangeLog index 605714b1..03f0ae14 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,7 @@ - added C++ arithmetic assignment overloads, += etc. - VImage::ifthenelse() with double args was missing =0 on options - better accuracy for reducev with smarter multiplication +- better quality for vips_resize() with linear/cubic kernels 18/5/16 started 8.3.2 - more robust vips image reading diff --git a/TODO b/TODO index 05b5a63d..78ccb86f 100644 --- a/TODO +++ b/TODO @@ -1,23 +1,3 @@ -- try: - - $ vips black x.v 10 10 - $ vips reducev x.v x2.v 2 - $ vips avg x2.v - 1.000000 - - argh! - - reduceh seems OK - - $ vips reducev x.v x2.v 2 --vips-novector - $ vips avg x2.v - 0.000000 - - also OK ... just the vector path - -- add more reducev tests ... resize constant 255 and constant 0 image, with and - without vector - - add more webp tests to py suite - try moving some more of the CLI tests to py diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 8d1c3763..d887240f 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -559,11 +559,10 @@ vips_reduceh_init( VipsReduceh *reduceh ) * * Optional arguments: * - * @kernel: #VipsKernel to use to interpolate (default: lanczos3) + * * @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 - * a reduction of more than a factor of two. + * interpolated with a 1D mask. * * This is a very low-level operation: see vips_resize() for a more * convenient way to resize images. diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index 7a16c77e..760b3b0b 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -41,6 +41,8 @@ */ /* +#define DEBUG_PIXELS +#define DEBUG_COMPILE #define DEBUG */ @@ -163,9 +165,9 @@ vips_reducev_compile_section( VipsReducev *reducev, Pass *pass, gboolean first ) VipsVector *v; int i; -#ifdef DEBUG +#ifdef DEBUG_COMPILE printf( "starting pass %d\n", pass->first ); -#endif /*DEBUG*/ +#endif /*DEBUG_COMPILE*/ pass->vector = v = vips_vector_new( "reducev", 1 ); @@ -270,10 +272,10 @@ vips_reducev_compile_section( VipsReducev *reducev, Pass *pass, gboolean first ) if( !vips_vector_compile( v ) ) return( -1 ); -#ifdef DEBUG +#ifdef DEBUG_COMPILE printf( "done coeffs %d to %d\n", pass->first, pass->last ); vips_vector_print( v ); -#endif /*DEBUG*/ +#endif /*DEBUG_COMPILE*/ return( 0 ); } @@ -623,10 +625,10 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, VipsExecutor executor[MAX_PASS]; VipsRect s; -#ifdef DEBUG +#ifdef DEBUG_PIXELS printf( "vips_reducev_vector_gen: generating %d x %d at %d x %d\n", r->width, r->height, r->left, r->top ); -#endif /*DEBUG*/ +#endif /*DEBUG_PIXELS*/ s.left = r->left; s.top = r->top * reducev->yshrink; @@ -635,10 +637,10 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, if( vips_region_prepare( ir, &s ) ) return( -1 ); -#ifdef DEBUG +#ifdef DEBUG_PIXELS printf( "vips_reducev_vector_gen: preparing %d x %d at %d x %d\n", s.width, s.height, s.left, s.top ); -#endif /*DEBUG*/ +#endif /*DEBUG_PIXELS*/ for( int i = 0; i < reducev->n_pass; i++ ) vips_executor_set_program( &executor[i], @@ -656,7 +658,7 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, const int ty = (siy + 1) >> 1; const int *cyo = reducev->matrixo[ty]; -#ifdef DEBUG +#ifdef DEBUG_PIXELS printf( "starting row %d\n", y + r->top ); printf( "coefficients:\n" ); for( int i = 0; i < reducev->n_point; i++ ) @@ -665,7 +667,7 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, for( int i = 0; i < reducev->n_point; i++ ) printf( "\t%d - %d\n", i, *VIPS_REGION_ADDR( ir, r->left, r->top + y + i ) ); -#endif /*DEBUG*/ +#endif /*DEBUG_PIXELS*/ /* We run our n passes to generate this scanline. */ @@ -687,10 +689,10 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, VIPS_SWAP( signed short *, seq->t1, seq->t2 ); } -#ifdef DEBUG +#ifdef DEBUG_PIXELS printf( "pixel result:\n" ); printf( "\t%d\n", *q ); -#endif /*DEBUG*/ +#endif /*DEBUG_PIXELS*/ } VIPS_GATE_STOP( "vips_reducev_vector_gen: work" ); @@ -964,11 +966,10 @@ vips_reducev_init( VipsReducev *reducev ) * * Optional arguments: * - * @kernel: #VipsKernel to use to interpolate (default: lanczos3) + * * @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 - * a reduction of more than a factor of two. + * interpolated with a 1D mask. * * This is a very low-level operation: see vips_resize() for a more * convenient way to resize images. diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index c35b1ef3..ac743f80 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -13,6 +13,9 @@ * 1/5/16 * - allow >1 on one axis, <1 on the other * - expose @kernel setting + * 16/6/16 + * - better quality for linear/cubic kernels ... do more shrink and less + * reduce */ /* @@ -84,6 +87,43 @@ typedef VipsResampleClass VipsResizeClass; G_DEFINE_TYPE( VipsResize, vips_resize, VIPS_TYPE_RESAMPLE ); +/* How much of a scale should be by an integer shrink factor? + * + * This depends on the scale and the kernel we will use for residual resizing. + * For upsizing and nearest-neighbour downsize, we want no shrinking. + * + * Linear and cubic are fixed-size kernels and for a 0 offset are point + * samplers. We will get aliasing if we do more than a x2 shrink with them. + * + * Lanczos is adaptive: the size of the kernel changes with the shrink factor. + * We will get the best quality (but be the slowest) if we let reduce do all + * the work. Leave it the final 200 - 300% to do as a compromise for + * efficiency. + * + * FIXME: this is rather ugly. Kernel should be a class and this info should be + * stored in there. + */ +static int +vips_resize_int_shrink( VipsResize *resize, double scale ) +{ + if( scale > 1.0 ) + return( 1 ); + + switch( resize->kernel ) { + case VIPS_KERNEL_NEAREST: + return( 1 ); + + case VIPS_KERNEL_LINEAR: + case VIPS_KERNEL_CUBIC: + default: + return( VIPS_FLOOR( 1.0 / scale ) ); + + case VIPS_KERNEL_LANCZOS2: + case VIPS_KERNEL_LANCZOS3: + return( VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (resize->scale * 2) ) ) ); + } +} + static int vips_resize_build( VipsObject *object ) { @@ -114,25 +154,9 @@ vips_resize_build( VipsObject *object ) 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. - */ - - /* We want the int part of the shrink to leave a bit to do with - * blur/reduce/sharpen, or we'll see strange changes in aliasing on int - * shrink boundaries as we resize. - */ - - if( resize->scale > 1.0 ) - int_hshrink = 1; - else - int_hshrink = VIPS_FLOOR( 1.0 / (resize->scale * 2) ); - if( vips_object_argument_isset( object, "vscale" ) ) { - if( resize->vscale > 1.0 ) - int_vshrink = 1; - else - int_vshrink = VIPS_FLOOR( 1.0 / (resize->vscale * 2) ); - } + 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; @@ -334,21 +358,21 @@ vips_resize_init( VipsResize *resize ) * * Optional arguments: * - * * @vscale: vertical scale factor + * * @vscale: %gdouble vertical scale factor * * @kernel: #VipsKernel to reduce with * * Resize an image. When upsizing (@scale > 1), the image is simply block * upsized. When downsizing, the * image is block-shrunk with vips_shrink(), * then the image is shrunk again to the - * target size with vips_reduce(). The operation will leave at least the final - * x2 to be done with vips_reduce(). + * target size with vips_reduce(). How much is done by vips_shrink() vs. + * vips_reduce() varies with the @kernel setting. * * 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. * - * vips_resize() normally uses #VIPS_KERNEL_LANCZOS3 for thre final shrink, you + * vips_resize() normally uses #VIPS_KERNEL_LANCZOS3 for the final shrink, you * can change this with @kernel. * * This operation does not change xres or yres. The image resolution needs to diff --git a/libvips/resample/shrinkv.c b/libvips/resample/shrinkv.c index 0f6c1e37..9a6f87c6 100644 --- a/libvips/resample/shrinkv.c +++ b/libvips/resample/shrinkv.c @@ -435,10 +435,8 @@ vips_shrinkv_init( VipsShrinkv *shrink ) * 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. * - * You will get aliasing for non-integer shrinks. In this case, shrink with - * this function to the nearest integer size above the target shrink, then - * downsample to the exact size with vips_affine() and your choice of - * interpolator. See vips_resize() for a convenient way to do this. + * 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.