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.
This commit is contained in:
John Cupitt 2016-06-16 09:46:02 +01:00
parent a5bef08d4a
commit f3326c8126
6 changed files with 68 additions and 65 deletions

View File

@ -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

20
TODO
View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.