fix jaggies on the edge of affine output

and add a "background" param
This commit is contained in:
John Cupitt 2017-11-17 16:30:25 +00:00
parent ef9b23b95a
commit fcec6d639b
6 changed files with 120 additions and 105 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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