Fix the pixel shift within reduce (#703)

This commit is contained in:
Kleis Auke Wolthuizen 2020-06-06 14:01:34 +02:00
parent ba0dea001d
commit ac358ff4b8
5 changed files with 124 additions and 86 deletions

View File

@ -6,6 +6,8 @@
* - rename xshrink -> hshrink for greater consistency * - rename xshrink -> hshrink for greater consistency
* 9/9/16 * 9/9/16
* - add @centre option * - add @centre option
* 6/6/20 kleisauke
* - deprecate @centre option, it's now always on
*/ */
/* /*
@ -78,14 +80,10 @@
* Optional arguments: * Optional arguments:
* *
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3) * * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
* * @centre: %gboolean use centre rather than corner sampling convention
* *
* 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 generated by @kernel. * interpolated with a 1D mask generated by @kernel.
* *
* Set @centre to use centre rather than corner sampling convention. Centre
* convention can be useful to match the behaviour of other systems.
*
* 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.
* *
@ -107,14 +105,10 @@
* Optional arguments: * Optional arguments:
* *
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3) * * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
* * @centre: %gboolean use centre rather than corner sampling convention
* *
* 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 generated by @kernel. * interpolated with a 1D mask generated by @kernel.
* *
* Set @centre to use centre rather than corner sampling convention. Centre
* convention can be useful to match the behaviour of other systems.
*
* 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.
* *
@ -136,7 +130,7 @@ typedef struct _VipsReduce {
*/ */
VipsKernel kernel; VipsKernel kernel;
/* Use centre rather than corner sampling convention. /* Deprecated.
*/ */
gboolean centre; gboolean centre;
@ -152,18 +146,16 @@ vips_reduce_build( VipsObject *object )
VipsResample *resample = VIPS_RESAMPLE( object ); VipsResample *resample = VIPS_RESAMPLE( object );
VipsReduce *reduce = (VipsReduce *) object; VipsReduce *reduce = (VipsReduce *) object;
VipsImage **t = (VipsImage **) VipsImage **t = (VipsImage **)
vips_object_local_array( object, 3 ); vips_object_local_array( object, 2 );
if( VIPS_OBJECT_CLASS( vips_reduce_parent_class )->build( object ) ) if( VIPS_OBJECT_CLASS( vips_reduce_parent_class )->build( object ) )
return( -1 ); return( -1 );
if( vips_reducev( resample->in, &t[0], reduce->vshrink, if( vips_reducev( resample->in, &t[0], reduce->vshrink,
"kernel", reduce->kernel, "kernel", reduce->kernel,
"centre", reduce->centre,
NULL ) || NULL ) ||
vips_reduceh( t[0], &t[1], reduce->hshrink, vips_reduceh( t[0], &t[1], reduce->hshrink,
"kernel", reduce->kernel, "kernel", reduce->kernel,
"centre", reduce->centre,
NULL ) || NULL ) ||
vips_image_write( t[1], resample->out ) ) vips_image_write( t[1], resample->out ) )
return( -1 ); return( -1 );
@ -210,13 +202,6 @@ vips_reduce_class_init( VipsReduceClass *class )
G_STRUCT_OFFSET( VipsReduce, kernel ), G_STRUCT_OFFSET( VipsReduce, kernel ),
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
VIPS_ARG_BOOL( class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReduce, centre ),
FALSE );
/* The old names .. now use h and v everywhere. /* The old names .. now use h and v everywhere.
*/ */
VIPS_ARG_DOUBLE( class, "xshrink", 8, VIPS_ARG_DOUBLE( class, "xshrink", 8,
@ -233,6 +218,15 @@ vips_reduce_class_init( VipsReduceClass *class )
G_STRUCT_OFFSET( VipsReduce, vshrink ), G_STRUCT_OFFSET( VipsReduce, vshrink ),
1.0, 1000000.0, 1.0 ); 1.0, 1000000.0, 1.0 );
/* We used to let people pick centre or corner, but it's automatic now.
*/
VIPS_ARG_BOOL( class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsReduce, centre ),
FALSE );
} }
static void static void
@ -252,14 +246,10 @@ vips_reduce_init( VipsReduce *reduce )
* Optional arguments: * Optional arguments:
* *
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3) * * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
* * @centre: %gboolean use centre rather than corner sampling convention
* *
* Reduce @in by a pair of factors with a pair of 1D kernels. This * Reduce @in by a pair of factors with a pair of 1D kernels. This
* will not work well for shrink factors greater than three. * will not work well for shrink factors greater than three.
* *
* Set @centre to use centre rather than corner sampling convention. Centre
* convention can be useful to match the behaviour of other systems.
*
* 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.
* *

View File

@ -8,6 +8,9 @@
* - rename xshrink as hshrink for consistency * - rename xshrink as hshrink for consistency
* 9/9/16 * 9/9/16
* - add @centre option * - add @centre option
* 6/6/20 kleisauke
* - deprecate @centre option, it's now always on
* - fix pixel shift
*/ */
/* /*
@ -66,14 +69,14 @@ typedef struct _VipsReduceh {
*/ */
VipsKernel kernel; VipsKernel kernel;
/* Use centre rather than corner sampling convention.
*/
gboolean centre;
/* Number of points in kernel. /* Number of points in kernel.
*/ */
int n_point; int n_point;
/* Horizontal displacement.
*/
double hoffset;
/* Precalculated interpolation matrices. int (used for pel /* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others). We go to * sizes up to short), and double (for all others). We go to
* scale + 1 so we can round-to-nearest safely. * scale + 1 so we can round-to-nearest safely.
@ -81,6 +84,10 @@ typedef struct _VipsReduceh {
int *matrixi[VIPS_TRANSFORM_SCALE + 1]; int *matrixi[VIPS_TRANSFORM_SCALE + 1];
double *matrixf[VIPS_TRANSFORM_SCALE + 1]; double *matrixf[VIPS_TRANSFORM_SCALE + 1];
/* Deprecated.
*/
gboolean centre;
} VipsReduceh; } VipsReduceh;
typedef VipsResampleClass VipsReducehClass; typedef VipsResampleClass VipsReducehClass;
@ -315,12 +322,10 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq,
r->width, r->height, r->left, r->top ); r->width, r->height, r->left, r->top );
#endif /*DEBUG*/ #endif /*DEBUG*/
s.left = r->left * reduceh->hshrink; s.left = r->left * reduceh->hshrink - reduceh->hoffset;
s.top = r->top; s.top = r->top;
s.width = r->width * reduceh->hshrink + reduceh->n_point; s.width = r->width * reduceh->hshrink + reduceh->n_point;
s.height = r->height; s.height = r->height;
if( reduceh->centre )
s.width += 1;
if( vips_region_prepare( ir, &s ) ) if( vips_region_prepare( ir, &s ) )
return( -1 ); return( -1 );
@ -334,9 +339,8 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq,
q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); q = VIPS_REGION_ADDR( out_region, r->left, r->top + y );
X = r->left * reduceh->hshrink; X = (r->left + 0.5) * reduceh->hshrink - 0.5 -
if( reduceh->centre ) reduceh->hoffset;
X += 0.5;
/* We want p0 to be the start (ie. x == 0) of the input /* 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 * scanline we are reading from. We can then calculate the p we
@ -351,7 +355,7 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq,
ir->valid.left * ps; ir->valid.left * ps;
for( int x = 0; x < r->width; x++ ) { for( int x = 0; x < r->width; x++ ) {
int ix = (int) X; const int ix = (int) X;
VipsPel *p = p0 + ix * ps; VipsPel *p = p0 + ix * ps;
const int sx = X * VIPS_TRANSFORM_SCALE * 2; const int sx = X * VIPS_TRANSFORM_SCALE * 2;
const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1); const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1);
@ -441,7 +445,7 @@ vips_reduceh_build( VipsObject *object )
vips_object_local_array( object, 2 ); vips_object_local_array( object, 2 );
VipsImage *in; VipsImage *in;
int width; double width, extra_pixels;
if( VIPS_OBJECT_CLASS( vips_reduceh_parent_class )->build( object ) ) if( VIPS_OBJECT_CLASS( vips_reduceh_parent_class )->build( object ) )
return( -1 ); return( -1 );
@ -457,8 +461,6 @@ vips_reduceh_build( VipsObject *object )
if( reduceh->hshrink == 1 ) if( reduceh->hshrink == 1 )
return( vips_image_write( in, resample->out ) ); return( vips_image_write( in, resample->out ) );
/* Build the tables of pre-computed coefficients.
*/
reduceh->n_point = reduceh->n_point =
vips_reduce_get_points( reduceh->kernel, reduceh->hshrink ); vips_reduce_get_points( reduceh->kernel, reduceh->hshrink );
g_info( "reduceh: %d point mask", reduceh->n_point ); g_info( "reduceh: %d point mask", reduceh->n_point );
@ -467,6 +469,28 @@ vips_reduceh_build( VipsObject *object )
"%s", _( "reduce factor too large" ) ); "%s", _( "reduce factor too large" ) );
return( -1 ); return( -1 );
} }
/* Output size. We need to always round to nearest, so round(), not
* rint().
*/
width = VIPS_ROUND_UINT(
(double) resample->in->Xsize / reduceh->hshrink );
/* How many pixels we are inventing in the input, -ve for
* discarding.
*/
extra_pixels =
width * reduceh->hshrink - resample->in->Xsize;
/* If we are rounding down, we are not using some input
* pixels. We need to move the origin *inside* the input image
* by half that distance so that we discard pixels equally
* from left and right.
*/
reduceh->hoffset = (1 + extra_pixels) / 2.0;
/* Build the tables of pre-computed coefficients.
*/
for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) { for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) {
reduceh->matrixf[x] = reduceh->matrixf[x] =
VIPS_ARRAY( object, reduceh->n_point, double ); VIPS_ARRAY( object, reduceh->n_point, double );
@ -499,15 +523,10 @@ vips_reduceh_build( VipsObject *object )
in = t[0]; in = t[0];
/* Add new pixels around the input so we can interpolate at the edges. /* Add new pixels around the input so we can interpolate at the edges.
* In centre mode, we read 0.5 pixels more to the right, so we must
* enlarge a little further.
*/ */
width = in->Xsize + reduceh->n_point - 1;
if( reduceh->centre )
width += 1;
if( vips_embed( in, &t[1], if( vips_embed( in, &t[1],
reduceh->n_point / 2 - 1, 0, reduceh->n_point / 2 - 1, 0,
width, in->Ysize, in->Xsize + reduceh->n_point, in->Ysize,
"extend", VIPS_EXTEND_COPY, "extend", VIPS_EXTEND_COPY,
(void *) NULL ) ) (void *) NULL ) )
return( -1 ); return( -1 );
@ -524,8 +543,7 @@ vips_reduceh_build( VipsObject *object )
* example, vipsthumbnail knows the true reduce factor (including the * example, vipsthumbnail knows the true reduce factor (including the
* fractional part), we just see the integer part here. * fractional part), we just see the integer part here.
*/ */
resample->out->Xsize = VIPS_ROUND_UINT( resample->out->Xsize = width;
resample->in->Xsize / reduceh->hshrink );
if( resample->out->Xsize <= 0 ) { if( resample->out->Xsize <= 0 ) {
vips_error( object_class->nickname, vips_error( object_class->nickname,
"%s", _( "image has shrunk to nothing" ) ); "%s", _( "image has shrunk to nothing" ) );
@ -574,20 +592,13 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
G_STRUCT_OFFSET( VipsReduceh, hshrink ), G_STRUCT_OFFSET( VipsReduceh, hshrink ),
1, 1000000, 1 ); 1, 1000000, 1 );
VIPS_ARG_ENUM( reduceh_class, "kernel", 3, VIPS_ARG_ENUM( reduceh_class, "kernel", 4,
_( "Kernel" ), _( "Kernel" ),
_( "Resampling kernel" ), _( "Resampling kernel" ),
VIPS_ARGUMENT_OPTIONAL_INPUT, VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReduceh, kernel ), G_STRUCT_OFFSET( VipsReduceh, kernel ),
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
VIPS_ARG_BOOL( reduceh_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReduceh, centre ),
FALSE );
/* Old name. /* Old name.
*/ */
VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3, VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3,
@ -597,6 +608,15 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
G_STRUCT_OFFSET( VipsReduceh, hshrink ), G_STRUCT_OFFSET( VipsReduceh, hshrink ),
1, 1000000, 1 ); 1, 1000000, 1 );
/* We used to let people pick centre or corner, but it's automatic now.
*/
VIPS_ARG_BOOL( reduceh_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsReduceh, centre ),
FALSE );
} }
static void static void

View File

@ -17,6 +17,9 @@
* - add @centre option * - add @centre option
* 7/3/17 * 7/3/17
* - add a seq line cache * - add a seq line cache
* 6/6/20 kleisauke
* - deprecate @centre option, it's now always on
* - fix pixel shift
*/ */
/* /*
@ -104,14 +107,14 @@ typedef struct _VipsReducev {
*/ */
VipsKernel kernel; VipsKernel kernel;
/* Use centre rather than corner sampling convention.
*/
gboolean centre;
/* Number of points in kernel. /* Number of points in kernel.
*/ */
int n_point; int n_point;
/* Vertical displacement.
*/
double voffset;
/* Precalculated interpolation matrices. int (used for pel /* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others). We go to * sizes up to short), and double (for all others). We go to
* scale + 1 so we can round-to-nearest safely. * scale + 1 so we can round-to-nearest safely.
@ -128,6 +131,10 @@ typedef struct _VipsReducev {
int n_pass; int n_pass;
Pass pass[MAX_PASS]; Pass pass[MAX_PASS];
/* Deprecated.
*/
gboolean centre;
} VipsReducev; } VipsReducev;
typedef VipsResampleClass VipsReducevClass; typedef VipsResampleClass VipsReducevClass;
@ -531,22 +538,22 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq,
#endif /*DEBUG*/ #endif /*DEBUG*/
s.left = r->left; s.left = r->left;
s.top = r->top * reducev->vshrink; s.top = r->top * reducev->vshrink - reducev->voffset;
s.width = r->width; s.width = r->width;
s.height = r->height * reducev->vshrink + reducev->n_point; s.height = r->height * reducev->vshrink + reducev->n_point;
if( reducev->centre )
s.height += 1;
if( vips_region_prepare( ir, &s ) ) if( vips_region_prepare( ir, &s ) )
return( -1 ); return( -1 );
VIPS_GATE_START( "vips_reducev_gen: work" ); VIPS_GATE_START( "vips_reducev_gen: work" );
double Y = (r->top + 0.5) * reducev->vshrink - 0.5 -
reducev->voffset;
for( int y = 0; y < r->height; y ++ ) { for( int y = 0; y < r->height; y ++ ) {
VipsPel *q = VipsPel *q =
VIPS_REGION_ADDR( out_region, r->left, r->top + y ); VIPS_REGION_ADDR( out_region, r->left, r->top + y );
const double Y = (r->top + y) * reducev->vshrink + const int py = (int) Y;
(reducev->centre ? 0.5 : 0.0); VipsPel *p = VIPS_REGION_ADDR( ir, r->left, py );
VipsPel *p = VIPS_REGION_ADDR( ir, r->left, (int) Y );
const int sy = Y * VIPS_TRANSFORM_SCALE * 2; const int sy = Y * VIPS_TRANSFORM_SCALE * 2;
const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int ty = (siy + 1) >> 1; const int ty = (siy + 1) >> 1;
@ -606,13 +613,15 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq,
case VIPS_FORMAT_DPCOMPLEX: case VIPS_FORMAT_DPCOMPLEX:
case VIPS_FORMAT_DOUBLE: case VIPS_FORMAT_DOUBLE:
reducev_notab<double>( reducev, reducev_notab<double>( reducev,
q, p, ne, lskip, Y - (int) Y ); q, p, ne, lskip, Y - py );
break; break;
default: default:
g_assert_not_reached(); g_assert_not_reached();
break; break;
} }
Y += reducev->vshrink;
} }
VIPS_GATE_STOP( "vips_reducev_gen: work" ); VIPS_GATE_STOP( "vips_reducev_gen: work" );
@ -646,9 +655,8 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
s.left = r->left; s.left = r->left;
s.top = r->top * reducev->vshrink; s.top = r->top * reducev->vshrink;
s.width = r->width; s.width = r->width;
s.height = r->height * reducev->vshrink + reducev->n_point; s.height = r->height * reducev->vshrink + reducev->n_point -
if( reducev->centre ) reducev->voffset;
s.height += 1;
if( vips_region_prepare( ir, &s ) ) if( vips_region_prepare( ir, &s ) )
return( -1 ); return( -1 );
@ -663,11 +671,12 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
VIPS_GATE_START( "vips_reducev_vector_gen: work" ); VIPS_GATE_START( "vips_reducev_vector_gen: work" );
double Y = (r->top + 0.5) * reducev->vshrink - 0.5 -
reducev->voffset;
for( int y = 0; y < r->height; y ++ ) { for( int y = 0; y < r->height; y ++ ) {
VipsPel *q = VipsPel *q =
VIPS_REGION_ADDR( out_region, r->left, r->top + y ); VIPS_REGION_ADDR( out_region, r->left, r->top + y );
const double Y = (r->top + y) * reducev->vshrink +
(reducev->centre ? 0.5 : 0.0);
const int py = (int) Y; const int py = (int) Y;
const int sy = Y * VIPS_TRANSFORM_SCALE * 2; const int sy = Y * VIPS_TRANSFORM_SCALE * 2;
const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1);
@ -682,7 +691,7 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
printf( "first column of pixel values:\n" ); printf( "first column of pixel values:\n" );
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, py ) );
#endif /*DEBUG_PIXELS*/ #endif /*DEBUG_PIXELS*/
/* We run our n passes to generate this scanline. /* We run our n passes to generate this scanline.
@ -709,6 +718,8 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
printf( "pixel result:\n" ); printf( "pixel result:\n" );
printf( "\t%d\n", *q ); printf( "\t%d\n", *q );
#endif /*DEBUG_PIXELS*/ #endif /*DEBUG_PIXELS*/
Y += reducev->vshrink;
} }
VIPS_GATE_STOP( "vips_reducev_vector_gen: work" ); VIPS_GATE_STOP( "vips_reducev_vector_gen: work" );
@ -830,7 +841,7 @@ vips_reducev_build( VipsObject *object )
VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 ); VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 );
VipsImage *in; VipsImage *in;
int height; double height, extra_pixels;
if( VIPS_OBJECT_CLASS( vips_reducev_parent_class )->build( object ) ) if( VIPS_OBJECT_CLASS( vips_reducev_parent_class )->build( object ) )
return( -1 ); return( -1 );
@ -855,6 +866,25 @@ vips_reducev_build( VipsObject *object )
return( -1 ); return( -1 );
} }
/* Output size. We need to always round to nearest, so round(), not
* rint().
*/
height = VIPS_ROUND_UINT(
(double) resample->in->Ysize / reducev->vshrink );
/* How many pixels we are inventing in the input, -ve for
* discarding.
*/
extra_pixels =
height * reducev->vshrink - resample->in->Ysize;
/* If we are rounding down, we are not using some input
* pixels. We need to move the origin *inside* the input image
* by half that distance so that we discard pixels equally
* from left and right.
*/
reducev->voffset = (1 + extra_pixels) / 2.0;
/* Unpack for processing. /* Unpack for processing.
*/ */
if( vips_image_decode( in, &t[0] ) ) if( vips_image_decode( in, &t[0] ) )
@ -863,12 +893,9 @@ vips_reducev_build( VipsObject *object )
/* Add new pixels around the input so we can interpolate at the edges. /* Add new pixels around the input so we can interpolate at the edges.
*/ */
height = in->Ysize + reducev->n_point - 1;
if( reducev->centre )
height += 1;
if( vips_embed( in, &t[1], if( vips_embed( in, &t[1],
0, reducev->n_point / 2 - 1, 0, reducev->n_point / 2 - 1,
in->Xsize, height, in->Xsize, in->Ysize + reducev->n_point,
"extend", VIPS_EXTEND_COPY, "extend", VIPS_EXTEND_COPY,
(void *) NULL ) ) (void *) NULL ) )
return( -1 ); return( -1 );
@ -939,13 +966,6 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
G_STRUCT_OFFSET( VipsReducev, kernel ), G_STRUCT_OFFSET( VipsReducev, kernel ),
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
VIPS_ARG_BOOL( reducev_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReducev, centre ),
FALSE );
/* Old name. /* Old name.
*/ */
VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3, VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3,
@ -955,6 +975,15 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
G_STRUCT_OFFSET( VipsReducev, vshrink ), G_STRUCT_OFFSET( VipsReducev, vshrink ),
1, 1000000, 1 ); 1, 1000000, 1 );
/* We used to let people pick centre or corner, but it's automatic now.
*/
VIPS_ARG_BOOL( reducev_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsReducev, centre ),
FALSE );
} }
static void static void

View File

@ -232,7 +232,6 @@ vips_resize_build( VipsObject *object )
g_info( "residual reducev by %g", vscale ); g_info( "residual reducev by %g", vscale );
if( vips_reducev( in, &t[2], 1.0 / vscale, if( vips_reducev( in, &t[2], 1.0 / vscale,
"kernel", resize->kernel, "kernel", resize->kernel,
"centre", TRUE,
NULL ) ) NULL ) )
return( -1 ); return( -1 );
in = t[2]; in = t[2];
@ -243,7 +242,6 @@ vips_resize_build( VipsObject *object )
hscale ); hscale );
if( vips_reduceh( in, &t[3], 1.0 / hscale, if( vips_reduceh( in, &t[3], 1.0 / hscale,
"kernel", resize->kernel, "kernel", resize->kernel,
"centre", TRUE,
NULL ) ) NULL ) )
return( -1 ); return( -1 );
in = t[3]; in = t[3];

View File

@ -82,8 +82,9 @@ class TestResample:
for fac in [1, 1.1, 1.5, 1.999]: for fac in [1, 1.1, 1.5, 1.999]:
for fmt in all_formats: for fmt in all_formats:
for kernel in ["nearest", "linear", # TODO: Add nearest kernel when https://github.com/libvips/libvips/issues/1518 is done.
"cubic", "lanczos2", "lanczos3"]: # (running the test suite with VIPS_NOVECTOR=1 should also work)
for kernel in ["linear", "cubic", "lanczos2", "lanczos3"]:
x = im.cast(fmt) x = im.cast(fmt)
r = x.reduce(fac, fac, kernel=kernel) r = x.reduce(fac, fac, kernel=kernel)
d = abs(r.avg() - im.avg()) d = abs(r.avg() - im.avg())