From fcec6d639bd50dbaafd7c04bb78b87b8097bcbb9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 17 Nov 2017 16:30:25 +0000 Subject: [PATCH] fix jaggies on the edge of affine output and add a "background" param --- ChangeLog | 2 + libvips/include/vips/private.h | 4 +- libvips/include/vips/region.h | 20 ++++---- libvips/iofuncs/region.c | 48 ++++++++++-------- libvips/resample/affine.c | 60 +++++++++++++++++++--- libvips/resample/similarity.c | 91 ++++++++++------------------------ 6 files changed, 120 insertions(+), 105 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3548b47c..51afb7b9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -35,6 +35,8 @@ - add vips_fill_nearest() ... fill pixels with nearest colour - add VIPS_COMBINE_MIN, a new combining mode for vips_compass() - vips_hist_find_indexed() now has a @combine parameter +- vips_affine() and vips_similarity() have a "background" parameter +- fix nasty jaggies on the edge of affine output 29/8/17 started 8.5.9 - make --fail stop jpeg read on any libjpeg warning, thanks @mceachen diff --git a/libvips/include/vips/private.h b/libvips/include/vips/private.h index e579ea3e..7d28b516 100644 --- a/libvips/include/vips/private.h +++ b/libvips/include/vips/private.h @@ -162,7 +162,7 @@ void vips__region_no_ownership( struct _VipsRegion *reg ); typedef int (*VipsRegionFillFn)( struct _VipsRegion *, void * ); int vips_region_fill( struct _VipsRegion *reg, - VipsRect *r, VipsRegionFillFn fn, void *a ); + const VipsRect *r, VipsRegionFillFn fn, void *a ); int vips__image_wio_output( struct _VipsImage *image ); int vips__image_pio_output( struct _VipsImage *image ); @@ -185,7 +185,7 @@ void vips_region_dump_all( void ); */ int vips__init( const char *argv0 ); size_t vips__get_sizeof_vipsobject( void ); -int vips_region_prepare_many( struct _VipsRegion **reg, VipsRect *r ); +int vips_region_prepare_many( struct _VipsRegion **reg, const VipsRect *r ); /* Handy for debugging. */ diff --git a/libvips/include/vips/region.h b/libvips/include/vips/region.h index a1474508..3d5972c4 100644 --- a/libvips/include/vips/region.h +++ b/libvips/include/vips/region.h @@ -104,23 +104,25 @@ GType vips_region_get_type(void); VipsRegion *vips_region_new( VipsImage *image ); -int vips_region_buffer( VipsRegion *reg, VipsRect *r ); -int vips_region_image( VipsRegion *reg, VipsRect *r ); +int vips_region_buffer( VipsRegion *reg, const VipsRect *r ); +int vips_region_image( VipsRegion *reg, const VipsRect *r ); int vips_region_region( VipsRegion *reg, VipsRegion *dest, - VipsRect *r, int x, int y ); + const VipsRect *r, int x, int y ); int vips_region_equalsregion( VipsRegion *reg1, VipsRegion *reg2 ); int vips_region_position( VipsRegion *reg, int x, int y ); -void vips_region_paint( VipsRegion *reg, VipsRect *r, int value ); -void vips_region_paint_pel( VipsRegion *reg, VipsRect *r, VipsPel *ink ); +void vips_region_paint( VipsRegion *reg, const VipsRect *r, int value ); +void vips_region_paint_pel( VipsRegion *reg, + const VipsRect *r, const VipsPel *ink ); void vips_region_black( VipsRegion *reg ); void vips_region_copy( VipsRegion *reg, VipsRegion *dest, - VipsRect *r, int x, int y ); -int vips_region_shrink( VipsRegion *from, VipsRegion *to, VipsRect *target ); + const VipsRect *r, int x, int y ); +int vips_region_shrink( VipsRegion *from, + VipsRegion *to, const VipsRect *target ); -int vips_region_prepare( VipsRegion *reg, VipsRect *r ); +int vips_region_prepare( VipsRegion *reg, const VipsRect *r ); int vips_region_prepare_to( VipsRegion *reg, - VipsRegion *dest, VipsRect *r, int x, int y ); + VipsRegion *dest, const VipsRect *r, int x, int y ); void vips_region_invalidate( VipsRegion *reg ); diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index 835026a9..cf3412e8 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -563,7 +563,7 @@ vips_region_new( VipsImage *image ) * Returns: 0 on success, or -1 for error. */ int -vips_region_buffer( VipsRegion *reg, VipsRect *r ) +vips_region_buffer( VipsRegion *reg, const VipsRect *r ) { VipsImage *im = reg->im; @@ -626,14 +626,14 @@ vips_region_buffer( VipsRegion *reg, VipsRect *r ) * @reg: region to operate upon * @r: #VipsRect of pixels you need to be able to address * - * The region is transformed so that at least @r pixels are available to be read from - * image. The image needs to be a memory buffer or represent a file - * on disc that has been mapped or can be mapped. + * The region is transformed so that at least @r pixels are available to be + * read from the image. The image needs to be a memory buffer or represent a + * file on disc that has been mapped or can be mapped. * * Returns: 0 on success, or -1 for error. */ int -vips_region_image( VipsRegion *reg, VipsRect *r ) +vips_region_image( VipsRegion *reg, const VipsRect *r ) { VipsImage *image = reg->im; @@ -725,16 +725,15 @@ vips_region_image( VipsRegion *reg, VipsRect *r ) * Performs all clipping necessary to ensure that @reg->valid is indeed * valid. * - * If the region we attach to is moved or destroyed, we can be left with dangling - * pointers! If the region we attach to is on another image, the two images - * must have - * the same sizeof( pel ). + * If the region we attach to is moved or destroyed, we can be left with + * dangling pointers! If the region we attach to is on another image, the + * two images must have the same sizeof( pel ). * * Returns: 0 on success, or -1 for error. */ int vips_region_region( VipsRegion *reg, - VipsRegion *dest, VipsRect *r, int x, int y ) + VipsRegion *dest, const VipsRect *r, int x, int y ) { VipsRect image; VipsRect wanted; @@ -885,7 +884,8 @@ vips_region_position( VipsRegion *reg, int x, int y ) } int -vips_region_fill( VipsRegion *reg, VipsRect *r, VipsRegionFillFn fn, void *a ) +vips_region_fill( VipsRegion *reg, + const VipsRect *r, VipsRegionFillFn fn, void *a ) { g_assert( reg->im->dtype == VIPS_IMAGE_PARTIAL ); g_assert( reg->im->generate_fn ); @@ -947,7 +947,7 @@ vips_region_fill( VipsRegion *reg, VipsRect *r, VipsRegionFillFn fn, void *a ) * See also: vips_region_black(). */ void -vips_region_paint( VipsRegion *reg, VipsRect *r, int value ) +vips_region_paint( VipsRegion *reg, const VipsRect *r, int value ) { VipsRect clipped; @@ -1011,7 +1011,7 @@ vips_region_paint( VipsRegion *reg, VipsRect *r, int value ) * See also: vips_region_paint(). */ void -vips_region_paint_pel( VipsRegion *reg, VipsRect *r, VipsPel *ink ) +vips_region_paint_pel( VipsRegion *reg, const VipsRect *r, const VipsPel *ink ) { VipsRect ovl; @@ -1076,7 +1076,8 @@ vips_region_black( VipsRegion *reg ) * See also: vips_region_paint(). */ void -vips_region_copy( VipsRegion *reg, VipsRegion *dest, VipsRect *r, int x, int y ) +vips_region_copy( VipsRegion *reg, + VipsRegion *dest, const VipsRect *r, int x, int y ) { int z; int len = VIPS_IMAGE_SIZEOF_PEL( reg->im ) * r->width; @@ -1135,7 +1136,8 @@ vips_region_copy( VipsRegion *reg, VipsRegion *dest, VipsRect *r, int x, int y ) * VIPS_CODING_LABQ only. */ static void -vips_region_shrink_labpack( VipsRegion *from, VipsRegion *to, VipsRect *target ) +vips_region_shrink_labpack( VipsRegion *from, + VipsRegion *to, const VipsRect *target ) { int ls = VIPS_REGION_LSKIP( from ); @@ -1212,7 +1214,8 @@ vips_region_shrink_labpack( VipsRegion *from, VipsRegion *to, VipsRect *target ) /* Generate area @target in @to using pixels in @from. Non-complex. */ static void -vips_region_shrink_uncoded( VipsRegion *from, VipsRegion *to, VipsRect *target ) +vips_region_shrink_uncoded( VipsRegion *from, + VipsRegion *to, const VipsRect *target ) { int ls = VIPS_REGION_LSKIP( from ); int ps = VIPS_IMAGE_SIZEOF_PEL( from->im ); @@ -1295,7 +1298,8 @@ vips_region_shrink_uncoded( VipsRegion *from, VipsRegion *to, VipsRect *target ) * last band as alpha. */ static void -vips_region_shrink_alpha( VipsRegion *from, VipsRegion *to, VipsRect *target ) +vips_region_shrink_alpha( VipsRegion *from, + VipsRegion *to, const VipsRect *target ) { int ls = VIPS_REGION_LSKIP( from ); int nb = from->im->Bands; @@ -1347,7 +1351,7 @@ vips_region_shrink_alpha( VipsRegion *from, VipsRegion *to, VipsRect *target ) * See also: vips_region_copy(). */ int -vips_region_shrink( VipsRegion *from, VipsRegion *to, VipsRect *target ) +vips_region_shrink( VipsRegion *from, VipsRegion *to, const VipsRect *target ) { VipsImage *image = from->im; @@ -1419,7 +1423,7 @@ vips_region_generate( VipsRegion *reg ) * Returns: 0 on success, or -1 on error. */ int -vips_region_prepare( VipsRegion *reg, VipsRect *r ) +vips_region_prepare( VipsRegion *reg, const VipsRect *r ) { VipsImage *im = reg->im; @@ -1492,7 +1496,7 @@ vips_region_prepare( VipsRegion *reg, VipsRect *r ) */ static int vips_region_prepare_to_generate( VipsRegion *reg, - VipsRegion *dest, VipsRect *r, int x, int y ) + VipsRegion *dest, const VipsRect *r, int x, int y ) { VipsImage *im = reg->im; VipsPel *p; @@ -1551,7 +1555,7 @@ vips_region_prepare_to_generate( VipsRegion *reg, */ int vips_region_prepare_to( VipsRegion *reg, - VipsRegion *dest, VipsRect *r, int x, int y ) + VipsRegion *dest, const VipsRect *r, int x, int y ) { VipsImage *im = reg->im; VipsRect image; @@ -1686,7 +1690,7 @@ vips_region_prepare_to( VipsRegion *reg, /* Don't use this, use vips_reorder_prepare_many() instead. */ int -vips_region_prepare_many( VipsRegion **reg, VipsRect *r ) +vips_region_prepare_many( VipsRegion **reg, const VipsRect *r ) { for( ; *reg; ++reg ) if( vips_region_prepare( *reg, r ) ) diff --git a/libvips/resample/affine.c b/libvips/resample/affine.c index e737395c..f7809a26 100644 --- a/libvips/resample/affine.c +++ b/libvips/resample/affine.c @@ -1,6 +1,5 @@ /* affine transform with a supplied interpolator. * - * * Copyright N. Dessipris * Written on: 01/11/1991 * Modified on: 12/3/92 JC @@ -84,6 +83,9 @@ * 1/8/14 * - revise transform ... again * - see new stress test in nip2/test/extras + * 7/11/17 + * - add "background" parameter + * - better clipping means we have no jaggies on edges */ /* @@ -116,6 +118,7 @@ /* #define DEBUG_VERBOSE #define DEBUG +#define VIPS_DEBUG */ #ifdef HAVE_CONFIG_H @@ -149,6 +152,14 @@ typedef struct _VipsAffine { VipsTransformation trn; + /* Background colour. + */ + VipsArrayDouble *background; + + /* The [double] converted to the input image format. + */ + VipsPel *ink; + } VipsAffine; typedef VipsResampleClass VipsAffineClass; @@ -275,7 +286,7 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) #endif /*DEBUG_VERBOSE*/ if( vips_rect_isempty( &clipped ) ) { - vips_region_black( or ); + vips_region_paint_pel( or, r, affine->ink ); return( 0 ); } if( vips_region_prepare( ir, &clipped ) ) @@ -336,9 +347,9 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) /* Clip against iarea. */ if( fx >= ile && - fx < iri && + fx <= iri && fy >= ito && - fy < ibo ) { + fy <= ibo ) { /* Verify that we can read the whole stencil. * With DEBUG on this will range-check. */ @@ -355,8 +366,10 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) q, ir, ix, iy ); } else { + /* Out of range: paint the background. + */ for( z = 0; z < ps; z++ ) - q[z] = 0; + q[z] = affine->ink[z]; } ix += ddx; @@ -478,16 +491,36 @@ vips_affine_build( VipsObject *object ) return( -1 ); in = t[0]; + /* Convert the background to the image's format. + */ + if( !(affine->ink = vips__vector_to_ink( class->nickname, + resample->out, + VIPS_AREA( affine->background )->data, NULL, + VIPS_AREA( affine->background )->n )) ) + return( -1 ); + /* Add new pixels around the input so we can interpolate at the edges. + * + * We add the interpolate stencil, plus one extra pixel on all the + * edges. This means when we clip in generate (above) we can be sure + * we clip outside the real pixels and don't get jaggies on edges. */ if( vips_embed( in, &t[2], - window_offset, window_offset, - in->Xsize + window_size - 1, in->Ysize + window_size - 1, - "extend", VIPS_EXTEND_COPY, + window_offset + 1, window_offset + 1, + in->Xsize + window_size - 1 + 2, + in->Ysize + window_size - 1 + 2, + "extend", VIPS_EXTEND_BACKGROUND, + "background", affine->background, NULL ) ) return( -1 ); in = t[2]; + /* We've added a one-pixel border to the input: displace the transform + * to compensate. + */ + affine->trn.idx -= 1; + affine->trn.idy -= 1; + /* Normally SMALLTILE ... except if this is strictly a size * up/down affine. */ @@ -591,11 +624,19 @@ vips_affine_class_init( VipsAffineClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsAffine, idy ), -10000000, 10000000, 0 ); + + VIPS_ARG_BOXED( class, "background", 2, + _( "Background" ), + _( "Background value" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsAffine, background ), + VIPS_TYPE_ARRAY_DOUBLE ); } static void vips_affine_init( VipsAffine *affine ) { + affine->background = vips_array_double_newv( 1, 0.0 ); } /** @@ -616,6 +657,7 @@ vips_affine_init( VipsAffine *affine ) * * @idy: %gdouble, input vertical offset * * @odx: %gdouble, output horizontal offset * * @ody: %gdouble, output vertical offset + * * @background: #VipsArrayDouble colour for new pixels * * This operator performs an affine transform on an image using @interpolate. * @@ -633,6 +675,8 @@ vips_affine_init( VipsAffine *affine ) * By default @oarea is just large enough to cover the whole of the * transformed input image. * + * New pixels are filled with @background. This defaults to zero (black). + * * @interpolate defaults to bilinear. * * @idx, @idy, @odx, @ody default to zero. diff --git a/libvips/resample/similarity.c b/libvips/resample/similarity.c index 35312072..106dfaa5 100644 --- a/libvips/resample/similarity.c +++ b/libvips/resample/similarity.c @@ -10,6 +10,9 @@ * - oops, missing scale from b, thanks Topochicho * 7/2/16 * - use vips_reduce(), if we can + * 17/11/17 + * ` - add optional "background" param + * ` - don't use vips_reduce() since it has no "background" param */ /* @@ -65,6 +68,7 @@ typedef struct _VipsSimilarity { double ody; double idx; double idy; + VipsArrayDouble *background; } VipsSimilarity; @@ -72,19 +76,6 @@ typedef VipsResampleClass VipsSimilarityClass; G_DEFINE_TYPE( VipsSimilarity, vips_similarity, VIPS_TYPE_RESAMPLE ); -/* Map interpolator names to vips kernels. - */ -typedef struct _VipsInterpolateKernel { - const char *nickname; - VipsKernel kernel; -} VipsInterpolateKernel; - -static VipsInterpolateKernel vips_similarity_kernel[] = { - { "bicubic", VIPS_KERNEL_CUBIC }, - { "bilinear", VIPS_KERNEL_LINEAR }, - { "nearest", VIPS_KERNEL_NEAREST } -}; - static int vips_similarity_build( VipsObject *object ) { @@ -92,61 +83,23 @@ vips_similarity_build( VipsObject *object ) VipsSimilarity *similarity = (VipsSimilarity *) object; VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 ); - - gboolean handled; + double a = similarity->scale * cos( VIPS_RAD( similarity->angle ) ); + double b = similarity->scale * -sin( VIPS_RAD( similarity->angle ) ); + double c = -b; + double d = a; if( VIPS_OBJECT_CLASS( vips_similarity_parent_class )->build( object ) ) return( -1 ); - handled = FALSE; - - /* Use vips_reduce(), if we can. - */ - if( similarity->interpolate && - similarity->angle == 0.0 && - similarity->idx == 0.0 && - similarity->idy == 0.0 && - similarity->odx == 0.0 && - similarity->ody == 0.0 ) { - const char *nickname = VIPS_OBJECT_GET_CLASS( - similarity->interpolate )->nickname; - - int i; - - for( i = 0; i < VIPS_NUMBER( vips_similarity_kernel ); i++ ) { - VipsInterpolateKernel *ik = &vips_similarity_kernel[i]; - - if( strcmp( nickname, ik->nickname ) == 0 ) { - if( vips_reduce( resample->in, &t[0], - 1.0 / similarity->scale, - 1.0 / similarity->scale, - "kernel", ik->kernel, - NULL ) ) - return( -1 ); - - handled = TRUE; - break; - } - } - } - - if( !handled ) { - double a = similarity->scale * - cos( VIPS_RAD( similarity->angle ) ); - double b = similarity->scale * - -sin( VIPS_RAD( similarity->angle ) ); - double c = -b; - double d = a; - - if( vips_affine( resample->in, &t[0], a, b, c, d, - "interpolate", similarity->interpolate, - "odx", similarity->odx, - "ody", similarity->ody, - "idx", similarity->idx, - "idy", similarity->idy, - NULL ) ) - return( -1 ); - } + if( vips_affine( resample->in, &t[0], a, b, c, d, + "interpolate", similarity->interpolate, + "odx", similarity->odx, + "ody", similarity->ody, + "idx", similarity->idx, + "idy", similarity->idy, + "background", similarity->background, + NULL ) ) + return( -1 ); if( vips_image_write( t[0], resample->out ) ) return( -1 ); @@ -214,6 +167,13 @@ vips_similarity_class_init( VipsSimilarityClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsSimilarity, idy ), -10000000, 10000000, 0 ); + + VIPS_ARG_BOXED( class, "background", 2, + _( "Background" ), + _( "Background value" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsSimilarity, background ), + VIPS_TYPE_ARRAY_DOUBLE ); } static void @@ -226,6 +186,7 @@ vips_similarity_init( VipsSimilarity *similarity ) similarity->ody = 0; similarity->idx = 0; similarity->idy = 0; + similarity->background = vips_array_double_newv( 1, 0.0 ); } /** @@ -243,6 +204,8 @@ vips_similarity_init( VipsSimilarity *similarity ) * * @idy: %gdouble, input vertical offset * * @odx: %gdouble, output horizontal offset * * @ody: %gdouble, output vertical offset + * * @ody: %gdouble, output vertical offset + * * @background: #VipsArrayDouble colour for new pixels * * This operator calls vips_affine() for you, calculating the matrix for the * affine transform from @scale and @angle. Other parameters are passed on to