diff --git a/ChangeLog b/ChangeLog index 7d63d4cf..a76d7492 100644 --- a/ChangeLog +++ b/ChangeLog @@ -43,6 +43,8 @@ rounding down, thanks ioquatix - better support for tile overlaps in google maps mode in dzsave - dzsave puts vips-properties.xml in the main dir for gm and zoomify layouts +- resize and reduce have @centre option for centre convention downsampling +- vipsthumbnail uses centre convention to better match imagemagick 19/8/16 started 8.3.4 - better transparency handling in gifload, thanks diegocsandrim diff --git a/TODO b/TODO index a7fbac71..715f4c8f 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,3 @@ -- test possible centre hack - - I think we're moving too far ... this is going to shift the mask by 50%, we - just want half a pixel - - put the shift into vips_reduce_make_mask() so it gets picked up by the double - path too - - not sure about utf8 error messages on win - strange: diff --git a/libvips/resample/reduce.c b/libvips/resample/reduce.c index 45960431..6f76ade8 100644 --- a/libvips/resample/reduce.c +++ b/libvips/resample/reduce.c @@ -4,6 +4,8 @@ * - from shrink.c * 15/8/16 * - rename xshrink -> hshrink for greater consistency + * 9/9/16 + * - add @centre option */ /* @@ -75,6 +77,10 @@ typedef struct _VipsReduce { */ VipsKernel kernel; + /* Use centre rather than corner sampling convention. + */ + gboolean centre; + } VipsReduce; typedef VipsResampleClass VipsReduceClass; @@ -94,9 +100,11 @@ vips_reduce_build( VipsObject *object ) if( vips_reducev( resample->in, &t[0], reduce->vshrink, "kernel", reduce->kernel, + "centre", reduce->centre, NULL ) || vips_reduceh( t[0], &t[1], reduce->hshrink, "kernel", reduce->kernel, + "centre", reduce->centre, NULL ) || vips_image_write( t[1], resample->out ) ) return( -1 ); @@ -143,6 +151,13 @@ vips_reduce_class_init( VipsReduceClass *class ) G_STRUCT_OFFSET( VipsReduce, kernel ), 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. */ VIPS_ARG_DOUBLE( class, "xshrink", 8, @@ -178,10 +193,14 @@ vips_reduce_init( VipsReduce *reduce ) * Optional arguments: * * * @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 * 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 * convenient way to resize images. * diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 5db448d6..0fda98eb 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -6,6 +6,8 @@ * - add other kernels * 15/8/16 * - rename xshrink as hshrink for consistency + * 9/9/16 + * - add @centre option */ /* @@ -321,6 +323,8 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, s.top = r->top; s.width = r->width * reduceh->hshrink + reduceh->n_point; s.height = r->height; + if( reduceh->centre ) + s.width += 1; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -335,6 +339,8 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); X = r->left * reduceh->hshrink; + if( reduceh->centre ) + X += 0.5; /* 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 @@ -439,6 +445,7 @@ vips_reduceh_build( VipsObject *object ) vips_object_local_array( object, 2 ); VipsImage *in; + int width; if( VIPS_OBJECT_CLASS( vips_reduceh_parent_class )->build( object ) ) return( -1 ); @@ -465,8 +472,6 @@ vips_reduceh_build( VipsObject *object ) return( -1 ); } for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) { - float fx; - reduceh->matrixf[x] = VIPS_ARRAY( object, reduceh->n_point, double ); reduceh->matrixi[x] = @@ -475,18 +480,9 @@ vips_reduceh_build( VipsObject *object ) !reduceh->matrixi[x] ) return( -1 ); - /* Calculate our [0, 1] float displacement. For centre - * convention we must shift by 0.5 and wrap around. - */ - fx = (float) x / VIPS_TRANSFORM_SCALE; - if( reduceh->centre ) { - fx += 0.5; - if( fx > 1.0 ) - fx -= 1.0; - } - vips_reduce_make_mask( reduceh->matrixf[x], - reduceh->kernel, reduceh->hshrink, fx ); + reduceh->kernel, reduceh->hshrink, + (float) x / VIPS_TRANSFORM_SCALE ); for( int i = 0; i < reduceh->n_point; i++ ) reduceh->matrixi[x][i] = reduceh->matrixf[x][i] * @@ -500,10 +496,15 @@ vips_reduceh_build( VipsObject *object ) in = t[0]; /* 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], reduceh->n_point / 2 - 1, 0, - in->Xsize + reduceh->n_point - 1, in->Ysize, + width, in->Ysize, "extend", VIPS_EXTEND_COPY, NULL ) ) return( -1 ); @@ -521,7 +522,7 @@ vips_reduceh_build( VipsObject *object ) * fractional part), we just see the integer part here. */ resample->out->Xsize = VIPS_ROUND_UINT( - (in->Xsize - reduceh->n_point + 1) / reduceh->hshrink ); + resample->in->Xsize / reduceh->hshrink ); if( resample->out->Xsize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index 16ac50e8..b8017ebc 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -13,6 +13,8 @@ * - better accuracy with smarter multiplication * 15/8/16 * - rename yshrink as vshrink for consistency + * 9/9/16 + * - add @centre option */ /* @@ -100,6 +102,10 @@ typedef struct _VipsReducev { */ VipsKernel kernel; + /* Use centre rather than corner sampling convention. + */ + gboolean centre; + /* Number of points in kernel. */ int n_point; @@ -526,6 +532,8 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq, s.top = r->top * reducev->vshrink; s.width = r->width; s.height = r->height * reducev->vshrink + reducev->n_point; + if( reducev->centre ) + s.height += 1; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -534,7 +542,8 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq, for( int y = 0; y < r->height; y ++ ) { VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - const double Y = (r->top + y) * reducev->vshrink; + const double Y = (r->top + y) * reducev->vshrink + + (reducev->centre ? 0.5 : 0.0); VipsPel *p = VIPS_REGION_ADDR( ir, r->left, (int) Y ); const int sy = Y * VIPS_TRANSFORM_SCALE * 2; const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); @@ -636,6 +645,8 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, s.top = r->top * reducev->vshrink; s.width = r->width; s.height = r->height * reducev->vshrink + reducev->n_point; + if( reducev->centre ) + s.height += 1; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -653,7 +664,8 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, for( int y = 0; y < r->height; y ++ ) { VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - const double Y = (r->top + y) * reducev->vshrink; + const double Y = (r->top + y) * reducev->vshrink + + (reducev->centre ? 0.5 : 0.0); const int py = (int) Y; const int sy = Y * VIPS_TRANSFORM_SCALE * 2; const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); @@ -783,7 +795,7 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in ) * fractional part), we just see the integer part here. */ resample->out->Ysize = VIPS_ROUND_UINT( - (in->Ysize - reducev->n_point + 1) / reducev->vshrink ); + resample->in->Ysize / reducev->vshrink ); if( resample->out->Ysize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); @@ -813,6 +825,7 @@ vips_reducev_build( VipsObject *object ) VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 ); VipsImage *in; + int height; if( VIPS_OBJECT_CLASS( vips_reducev_parent_class )->build( object ) ) return( -1 ); @@ -845,9 +858,12 @@ vips_reducev_build( VipsObject *object ) /* 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], 0, reducev->n_point / 2 - 1, - in->Xsize, in->Ysize + reducev->n_point - 1, + in->Xsize, height, "extend", VIPS_EXTEND_COPY, NULL ) ) return( -1 ); @@ -893,6 +909,13 @@ vips_reducev_class_init( VipsReducevClass *reducev_class ) G_STRUCT_OFFSET( VipsReducev, kernel ), 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. */ VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3, @@ -920,9 +943,13 @@ vips_reducev_init( VipsReducev *reducev ) * Optional arguments: * * * @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 - * interpolated with a 1D mask. + * 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 * convenient way to resize images. diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 1a46125a..59d963c8 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -20,6 +20,8 @@ * - faster and better upsizing * 15/8/16 * - more accurate resizing + * 9/9/16 + * - add @centre option */ /* @@ -78,6 +80,7 @@ typedef struct _VipsResize { double scale; double vscale; VipsKernel kernel; + gboolean centre; /* Deprecated. */ @@ -252,6 +255,7 @@ vips_resize_build( VipsObject *object ) vscale ); if( vips_reducev( in, &t[2], 1.0 / vscale, "kernel", resize->kernel, + "centre", resize->centre, NULL ) ) return( -1 ); in = t[2]; @@ -262,6 +266,7 @@ vips_resize_build( VipsObject *object ) hscale ); if( vips_reduceh( in, &t[3], 1.0 / hscale, "kernel", resize->kernel, + "centre", resize->centre, NULL ) ) return( -1 ); in = t[3]; @@ -354,6 +359,13 @@ vips_resize_class_init( VipsResizeClass *class ) G_STRUCT_OFFSET( VipsResize, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); + VIPS_ARG_BOOL( class, "centre", 7, + _( "Centre" ), + _( "Use centre sampling convention" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsResize, centre ), + FALSE ); + /* 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. */ @@ -398,6 +410,7 @@ vips_resize_init( VipsResize *resize ) * * * @vscale: %gdouble vertical scale factor * * @kernel: #VipsKernel to reduce with + * * @centre: %gboolean use centre rather than corner sampling convention * * Resize an image. * @@ -410,6 +423,9 @@ vips_resize_init( VipsResize *resize ) * vips_resize() normally uses #VIPS_KERNEL_LANCZOS3 for the final reduce, you * can change this with @kernel. * + * Set @centre to use centre rather than corner sampling convention. Centre + * convention can be useful to match the behaviour of other systems. + * * When upsizing (@scale > 1), the operation uses vips_affine() with * a #VipsInterpolate selected depending on @kernel. It will use * #VipsInterpolateBicubic for #VIPS_KERNEL_CUBIC and above. diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 74356306..237c1bfc 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -450,7 +450,7 @@ thumbnail_shrink( VipsObject *process, VipsImage *in ) shrink = calculate_shrink( in ); - if( vips_resize( in, &t[4], 1.0 / shrink, NULL ) ) + if( vips_resize( in, &t[4], 1.0 / shrink, "centre", TRUE, NULL ) ) return( NULL ); in = t[4];