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:
parent
a5bef08d4a
commit
f3326c8126
@ -24,6 +24,7 @@
|
|||||||
- added C++ arithmetic assignment overloads, += etc.
|
- added C++ arithmetic assignment overloads, += etc.
|
||||||
- VImage::ifthenelse() with double args was missing =0 on options
|
- VImage::ifthenelse() with double args was missing =0 on options
|
||||||
- better accuracy for reducev with smarter multiplication
|
- better accuracy for reducev with smarter multiplication
|
||||||
|
- better quality for vips_resize() with linear/cubic kernels
|
||||||
|
|
||||||
18/5/16 started 8.3.2
|
18/5/16 started 8.3.2
|
||||||
- more robust vips image reading
|
- more robust vips image reading
|
||||||
|
20
TODO
20
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
|
- add more webp tests to py suite
|
||||||
|
|
||||||
- try moving some more of the CLI tests to py
|
- try moving some more of the CLI tests to py
|
||||||
|
@ -559,11 +559,10 @@ vips_reduceh_init( VipsReduceh *reduceh )
|
|||||||
*
|
*
|
||||||
* Optional arguments:
|
* 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
|
* Reduce @in horizontally by a float factor. The pixels in @out are
|
||||||
* interpolated with a 1D mask. This operation will not work well for
|
* interpolated with a 1D mask.
|
||||||
* a reduction of more than a factor of two.
|
|
||||||
*
|
*
|
||||||
* This is a very low-level operation: see vips_resize() for a more
|
* This is a very low-level operation: see vips_resize() for a more
|
||||||
* convenient way to resize images.
|
* convenient way to resize images.
|
||||||
|
@ -41,6 +41,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
#define DEBUG_PIXELS
|
||||||
|
#define DEBUG_COMPILE
|
||||||
#define DEBUG
|
#define DEBUG
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -163,9 +165,9 @@ vips_reducev_compile_section( VipsReducev *reducev, Pass *pass, gboolean first )
|
|||||||
VipsVector *v;
|
VipsVector *v;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG_COMPILE
|
||||||
printf( "starting pass %d\n", pass->first );
|
printf( "starting pass %d\n", pass->first );
|
||||||
#endif /*DEBUG*/
|
#endif /*DEBUG_COMPILE*/
|
||||||
|
|
||||||
pass->vector = v = vips_vector_new( "reducev", 1 );
|
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 ) )
|
if( !vips_vector_compile( v ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG_COMPILE
|
||||||
printf( "done coeffs %d to %d\n", pass->first, pass->last );
|
printf( "done coeffs %d to %d\n", pass->first, pass->last );
|
||||||
vips_vector_print( v );
|
vips_vector_print( v );
|
||||||
#endif /*DEBUG*/
|
#endif /*DEBUG_COMPILE*/
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
@ -623,10 +625,10 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
|
|||||||
VipsExecutor executor[MAX_PASS];
|
VipsExecutor executor[MAX_PASS];
|
||||||
VipsRect s;
|
VipsRect s;
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG_PIXELS
|
||||||
printf( "vips_reducev_vector_gen: generating %d x %d at %d x %d\n",
|
printf( "vips_reducev_vector_gen: generating %d x %d at %d x %d\n",
|
||||||
r->width, r->height, r->left, r->top );
|
r->width, r->height, r->left, r->top );
|
||||||
#endif /*DEBUG*/
|
#endif /*DEBUG_PIXELS*/
|
||||||
|
|
||||||
s.left = r->left;
|
s.left = r->left;
|
||||||
s.top = r->top * reducev->yshrink;
|
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 ) )
|
if( vips_region_prepare( ir, &s ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG_PIXELS
|
||||||
printf( "vips_reducev_vector_gen: preparing %d x %d at %d x %d\n",
|
printf( "vips_reducev_vector_gen: preparing %d x %d at %d x %d\n",
|
||||||
s.width, s.height, s.left, s.top );
|
s.width, s.height, s.left, s.top );
|
||||||
#endif /*DEBUG*/
|
#endif /*DEBUG_PIXELS*/
|
||||||
|
|
||||||
for( int i = 0; i < reducev->n_pass; i++ )
|
for( int i = 0; i < reducev->n_pass; i++ )
|
||||||
vips_executor_set_program( &executor[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 ty = (siy + 1) >> 1;
|
||||||
const int *cyo = reducev->matrixo[ty];
|
const int *cyo = reducev->matrixo[ty];
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG_PIXELS
|
||||||
printf( "starting row %d\n", y + r->top );
|
printf( "starting row %d\n", y + r->top );
|
||||||
printf( "coefficients:\n" );
|
printf( "coefficients:\n" );
|
||||||
for( int i = 0; i < reducev->n_point; i++ )
|
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++ )
|
for( int i = 0; i < reducev->n_point; i++ )
|
||||||
printf( "\t%d - %d\n", i,
|
printf( "\t%d - %d\n", i,
|
||||||
*VIPS_REGION_ADDR( ir, r->left, r->top + y + 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.
|
/* 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 );
|
VIPS_SWAP( signed short *, seq->t1, seq->t2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG_PIXELS
|
||||||
printf( "pixel result:\n" );
|
printf( "pixel result:\n" );
|
||||||
printf( "\t%d\n", *q );
|
printf( "\t%d\n", *q );
|
||||||
#endif /*DEBUG*/
|
#endif /*DEBUG_PIXELS*/
|
||||||
}
|
}
|
||||||
|
|
||||||
VIPS_GATE_STOP( "vips_reducev_vector_gen: work" );
|
VIPS_GATE_STOP( "vips_reducev_vector_gen: work" );
|
||||||
@ -964,11 +966,10 @@ vips_reducev_init( VipsReducev *reducev )
|
|||||||
*
|
*
|
||||||
* Optional arguments:
|
* 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
|
* Reduce @in vertically by a float factor. The pixels in @out are
|
||||||
* interpolated with a 1D mask. This operation will not work well for
|
* interpolated with a 1D mask.
|
||||||
* a reduction of more than a factor of two.
|
|
||||||
*
|
*
|
||||||
* This is a very low-level operation: see vips_resize() for a more
|
* This is a very low-level operation: see vips_resize() for a more
|
||||||
* convenient way to resize images.
|
* convenient way to resize images.
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
* 1/5/16
|
* 1/5/16
|
||||||
* - allow >1 on one axis, <1 on the other
|
* - allow >1 on one axis, <1 on the other
|
||||||
* - expose @kernel setting
|
* - 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 );
|
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
|
static int
|
||||||
vips_resize_build( VipsObject *object )
|
vips_resize_build( VipsObject *object )
|
||||||
{
|
{
|
||||||
@ -114,25 +154,9 @@ vips_resize_build( VipsObject *object )
|
|||||||
else
|
else
|
||||||
target_height = in->Ysize * resize->scale;
|
target_height = in->Ysize * resize->scale;
|
||||||
|
|
||||||
/* If the factor is > 1.0, we need to zoom rather than shrink.
|
int_hshrink = vips_resize_int_shrink( resize, resize->scale );
|
||||||
* Just set the int part to 1 in this case.
|
if( vips_object_argument_isset( object, "vscale" ) )
|
||||||
*/
|
int_vshrink = vips_resize_int_shrink( resize, resize->vscale );
|
||||||
|
|
||||||
/* 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) );
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
int_vshrink = int_hshrink;
|
int_vshrink = int_hshrink;
|
||||||
|
|
||||||
@ -334,21 +358,21 @@ vips_resize_init( VipsResize *resize )
|
|||||||
*
|
*
|
||||||
* Optional arguments:
|
* Optional arguments:
|
||||||
*
|
*
|
||||||
* * @vscale: vertical scale factor
|
* * @vscale: %gdouble vertical scale factor
|
||||||
* * @kernel: #VipsKernel to reduce with
|
* * @kernel: #VipsKernel to reduce with
|
||||||
*
|
*
|
||||||
* Resize an image. When upsizing (@scale > 1), the image is simply block
|
* Resize an image. When upsizing (@scale > 1), the image is simply block
|
||||||
* upsized. When downsizing, the
|
* upsized. When downsizing, the
|
||||||
* image is block-shrunk with vips_shrink(),
|
* image is block-shrunk with vips_shrink(),
|
||||||
* then the image is shrunk again to the
|
* then the image is shrunk again to the
|
||||||
* target size with vips_reduce(). The operation will leave at least the final
|
* target size with vips_reduce(). How much is done by vips_shrink() vs.
|
||||||
* x2 to be done with vips_reduce().
|
* vips_reduce() varies with the @kernel setting.
|
||||||
*
|
*
|
||||||
* vips_resize() normally maintains the image apect ratio. If you set
|
* vips_resize() normally maintains the image apect ratio. If you set
|
||||||
* @vscale, that factor is used for the vertical scale and @scale for the
|
* @vscale, that factor is used for the vertical scale and @scale for the
|
||||||
* horizontal.
|
* 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.
|
* can change this with @kernel.
|
||||||
*
|
*
|
||||||
* This operation does not change xres or yres. The image resolution needs to
|
* This operation does not change xres or yres. The image resolution needs to
|
||||||
|
@ -435,10 +435,8 @@ vips_shrinkv_init( VipsShrinkv *shrink )
|
|||||||
* Shrink @in vertically by an integer factor. Each pixel in the output is
|
* 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 @yshrink pixels in the input.
|
||||||
*
|
*
|
||||||
* You will get aliasing for non-integer shrinks. In this case, shrink with
|
* This is a very low-level operation: see vips_resize() for a more
|
||||||
* this function to the nearest integer size above the target shrink, then
|
* convenient way to resize images.
|
||||||
* downsample to the exact size with vips_affine() and your choice of
|
|
||||||
* interpolator. See vips_resize() for a convenient way to do this.
|
|
||||||
*
|
*
|
||||||
* This operation does not change xres or yres. The image resolution needs to
|
* This operation does not change xres or yres. The image resolution needs to
|
||||||
* be updated by the application.
|
* be updated by the application.
|
||||||
|
Loading…
Reference in New Issue
Block a user