revise affine, yet again

how can this be so hard

the tougher self-tests in nip2/test/extras now all pass
This commit is contained in:
John Cupitt 2014-08-01 16:34:09 +01:00
parent 9ddca0e99e
commit 274e6c1b2b
4 changed files with 124 additions and 111 deletions

View File

@ -3,6 +3,7 @@
- limit n_thr on tiny images - limit n_thr on tiny images
- don't exit() on memleak detected, just warn - don't exit() on memleak detected, just warn
- add "autocrop" option to openslide load - add "autocrop" option to openslide load
- argh fix affine, again
4/7/14 started 7.40.4 4/7/14 started 7.40.4
- fix vips_rawsave_fd(), thanks aferrero2707 - fix vips_rawsave_fd(), thanks aferrero2707

View File

@ -709,10 +709,6 @@ vips_image_rewind( VipsObject *object )
/* Delayed save. /* Delayed save.
*/ */
/* If we write to (eg.) TIFF, actually do the write
* to a "p" and on "written" do im_vips2tiff() or whatever.
*/
/* From "written" callback: save to image->filename using VipsForeign. /* From "written" callback: save to image->filename using VipsForeign.
*/ */
static void static void
@ -1102,10 +1098,10 @@ vips_image_class_init( VipsImageClass *class )
* can't :-( * can't :-(
* *
* For example, a "p" image might be made with vips_image_new() and * For example, a "p" image might be made with vips_image_new() and
* constructed, then passed to im_copy() of whatever to be written to. * constructed, then passed to vips_copy() of whatever to be written to.
* That operation will then need to set width/height etc. * That operation will then need to set width/height etc.
* *
* We can't set_once either, since im_copy_set() etc. need to update * We can't set_once either, since vips_copy() etc. need to update
* xoffset and friends on the way through. * xoffset and friends on the way through.
*/ */
@ -2321,10 +2317,10 @@ vips_image_write_to_memory( VipsImage *in, void **buf_out, size_t *len_out )
len = VIPS_IMAGE_SIZEOF_IMAGE( in ); len = VIPS_IMAGE_SIZEOF_IMAGE( in );
if( !(buf = g_try_malloc( len )) ) { if( !(buf = g_try_malloc( len )) ) {
vips_error( "vips_image_write_to_buffer", vips_error( "vips_image_write_to_memory",
_( "out of memory --- size == %dMB" ), _( "out of memory --- size == %dMB" ),
(int) (len / (1024.0 * 1024.0)) ); (int) (len / (1024.0 * 1024.0)) );
vips_warn( "vips_image_write_to_buffer", vips_warn( "vips_image_write_to_memory",
_( "out of memory --- size == %dMB" ), _( "out of memory --- size == %dMB" ),
(int) (len / (1024.0*1024.0)) ); (int) (len / (1024.0*1024.0)) );
return( -1 ); return( -1 );
@ -3025,7 +3021,7 @@ vips_image_pio_output( VipsImage *image )
case VIPS_IMAGE_PARTIAL: case VIPS_IMAGE_PARTIAL:
if( image->generate_fn ) { if( image->generate_fn ) {
vips_error( "im_poutcheck", vips_error( "vips_image_pio_output",
"%s", _( "image already written" ) ); "%s", _( "image already written" ) );
return( -1 ); return( -1 );
} }

View File

@ -81,6 +81,9 @@
* - added input space translation * - added input space translation
* 22/1/14 * 22/1/14
* - auto RAD decode * - auto RAD decode
* 1/8/14
* - revise transform ... again
* - see new stress test in nip2/test/extras
*/ */
/* /*
@ -111,6 +114,7 @@
*/ */
/* /*
#define DEBUG_VERBOSE
#define DEBUG #define DEBUG
*/ */
@ -177,11 +181,12 @@ G_DEFINE_TYPE( VipsAffine, vips_affine, VIPS_TYPE_RESAMPLE );
/* We have five (!!) coordinate systems. Working forward through them, these /* We have five (!!) coordinate systems. Working forward through them, these
* are: * are:
* *
* 1. The original input image * 1. The original input image. iarea is defined on this image.
* *
* 2. This is embedded in a larger image to provide borders for the * 2. This is embedded in a larger image to provide borders for the
* interpolator. iarea->left/top give the offset. These are the coordinates we * interpolator. window_offset and window_size control the embedding.
* pass to VIPS_REGION_ADDR()/vips_region_prepare() for the input image. * These are the coordinates we pass to VIPS_REGION_ADDR()/
* vips_region_prepare() and the interpolator.
* *
* The borders are sized by the interpolator's window_size property and offset * The borders are sized by the interpolator's window_size property and offset
* by the interpolator's window_offset property. For example, * by the interpolator's window_offset property. For example,
@ -194,7 +199,7 @@ G_DEFINE_TYPE( VipsAffine, vips_affine, VIPS_TYPE_RESAMPLE );
* 3. We need point (0, 0) in (1) to be at (0, 0) for the transformation. So * 3. We need point (0, 0) in (1) to be at (0, 0) for the transformation. So
* shift everything up and left to make the displaced input image. This is the * shift everything up and left to make the displaced input image. This is the
* space that the transformation maps from, and can have negative pixels * space that the transformation maps from, and can have negative pixels
* (up and left of the image, for interpolation). * (up and left of the image, for interpolation). iarea works here too.
* *
* 4. Output transform space. This is the where the transform maps to. Pixels * 4. Output transform space. This is the where the transform maps to. Pixels
* can be negative, since a rotated image can go up and left of the origin. * can be negative, since a rotated image can go up and left of the origin.
@ -233,22 +238,23 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
VipsRect image, want, need, clipped; VipsRect image, want, need, clipped;
#ifdef DEBUG #ifdef DEBUG_VERBOSE
printf( "vips_affine_gen: " printf( "vips_affine_gen: "
"generating left=%d, top=%d, width=%d, height=%d\n", "generating left=%d, top=%d, width=%d, height=%d\n",
r->left, r->left,
r->top, r->top,
r->width, r->width,
r->height ); r->height );
#endif /*DEBUG*/ #endif /*DEBUG_VERBOSE*/
/* We are generating this chunk of the transformed image. /* We are generating this chunk of the transformed image. This takes
* us to space 4.
*/ */
want = *r; want = *r;
want.left += oarea->left; want.left += oarea->left;
want.top += oarea->top; want.top += oarea->top;
/* Find the area of the input image we need. /* Find the area of the input image we need. This takes us to space 3.
*/ */
vips__transform_invert_rect( &affine->trn, &want, &need ); vips__transform_invert_rect( &affine->trn, &want, &need );
@ -261,17 +267,18 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
*/ */
vips_rect_marginadjust( &need, 1 ); vips_rect_marginadjust( &need, 1 );
/* Now go to space (2) above. /* We need to fetch a larger area for the interpolator.
*/
need.left += iarea->left;
need.top += iarea->top;
/* Add a border for interpolation.
*/ */
need.left -= window_offset;
need.top -= window_offset;
need.width += window_size - 1; need.width += window_size - 1;
need.height += window_size - 1; need.height += window_size - 1;
need.left -= window_offset;
need.top -= window_offset; /* Now go to space 2, the expanded input image. This is the one we
* read pixels from.
*/
need.left += window_offset;
need.top += window_offset;
/* Clip against the size of (2). /* Clip against the size of (2).
*/ */
@ -281,38 +288,34 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
image.height = in->Ysize; image.height = in->Ysize;
vips_rect_intersectrect( &need, &image, &clipped ); vips_rect_intersectrect( &need, &image, &clipped );
/* Outside input image? All black. #ifdef DEBUG_VERBOSE
*/ printf( "vips_affine_gen: "
if( vips_rect_isempty( &clipped ) ) { "preparing left=%d, top=%d, width=%d, height=%d\n",
vips_region_black( or );
return( 0 );
}
/* We do need some pixels from the input image to make our output -
* ask for them.
*/
if( vips_region_prepare( ir, &clipped ) )
return( -1 );
#ifdef DEBUG
printf( "affine: preparing left=%d, top=%d, width=%d, height=%d\n",
clipped.left, clipped.left,
clipped.top, clipped.top,
clipped.width, clipped.width,
clipped.height ); clipped.height );
#endif /*DEBUG*/ #endif /*DEBUG_VERBOSE*/
if( vips_rect_isempty( &clipped ) ) {
vips_region_black( or );
return( 0 );
}
if( vips_region_prepare( ir, &clipped ) )
return( -1 );
VIPS_GATE_START( "vips_affine_gen: work" ); VIPS_GATE_START( "vips_affine_gen: work" );
/* Resample! x/y loop over pixels in the output image (5). /* Resample! x/y loop over pixels in the output image (5).
*/ */
for( y = to; y < bo; y++ ) { for( y = to; y < bo; y++ ) {
/* Input clipping rectangle. /* Input clipping rectangle. We offset this so we can clip in
* space 2.
*/ */
const int ile = iarea->left; const int ile = iarea->left + window_offset;
const int ito = iarea->top; const int ito = iarea->top + window_offset;
const int iri = iarea->left + iarea->width; const int iri = ile + iarea->width;
const int ibo = iarea->top + iarea->height; const int ibo = ito + iarea->height;
/* Derivative of matrix. /* Derivative of matrix.
*/ */
@ -335,16 +338,16 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
ix = affine->trn.ia * ox + affine->trn.ib * oy; ix = affine->trn.ia * ox + affine->trn.ib * oy;
iy = affine->trn.ic * ox + affine->trn.id * oy; iy = affine->trn.ic * ox + affine->trn.id * oy;
/* Now move to (2). /* And the input offset in (3).
*/
ix += iarea->left;
iy += iarea->top;
/* And the input offset.
*/ */
ix -= affine->trn.idx; ix -= affine->trn.idx;
iy -= affine->trn.idy; iy -= affine->trn.idy;
/* Finally to 2.
*/
ix += window_offset;
iy += window_offset;
q = VIPS_REGION_ADDR( or, le, y ); q = VIPS_REGION_ADDR( or, le, y );
for( x = le; x < ri; x++ ) { for( x = le; x < ri; x++ ) {
@ -353,19 +356,31 @@ vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
fx = FAST_PSEUDO_FLOOR( ix ); fx = FAST_PSEUDO_FLOOR( ix );
fy = FAST_PSEUDO_FLOOR( iy ); fy = FAST_PSEUDO_FLOOR( iy );
/* Clipping! /* Clip against iarea.
*/ */
if( fx < ile || if( fx >= ile &&
fx > iri || fx < iri &&
fy < ito || fy >= ito &&
fy > ibo ) { fy < ibo ) {
for( z = 0; z < ps; z++ ) /* Verify that we can read the whole stencil.
q[z] = 0; * With DEBUG on this will range-check.
} */
else { g_assert( VIPS_REGION_ADDR( ir,
(int) ix - window_offset,
(int) iy - window_offset ) );
g_assert( VIPS_REGION_ADDR( ir,
(int) ix - window_offset +
window_size - 1,
(int) iy - window_offset +
window_size - 1 ) );
interpolate( affine->interpolate, interpolate( affine->interpolate,
q, ir, ix, iy ); q, ir, ix, iy );
} }
else {
for( z = 0; z < ps; z++ )
q[z] = 0;
}
ix += ddx; ix += ddx;
iy += ddy; iy += ddy;
@ -417,8 +432,8 @@ vips_affine_build( VipsObject *object )
window_offset = window_offset =
vips_interpolate_get_window_offset( affine->interpolate ); vips_interpolate_get_window_offset( affine->interpolate );
affine->trn.iarea.left = window_offset; affine->trn.iarea.left = 0;
affine->trn.iarea.top = window_offset; affine->trn.iarea.top = 0;
affine->trn.iarea.width = in->Xsize; affine->trn.iarea.width = in->Xsize;
affine->trn.iarea.height = in->Ysize; affine->trn.iarea.height = in->Ysize;
affine->trn.a = ((double *) affine->matrix->data)[0]; affine->trn.a = ((double *) affine->matrix->data)[0];
@ -430,6 +445,11 @@ vips_affine_build( VipsObject *object )
affine->trn.odx = 0; affine->trn.odx = 0;
affine->trn.ody = 0; affine->trn.ody = 0;
if( vips__transform_calc_inverse( &affine->trn ) )
return( -1 );
/* Set the default value for oarea.
*/
vips__transform_set_area( &affine->trn ); vips__transform_set_area( &affine->trn );
if( vips_object_argument_isset( object, "oarea" ) ) { if( vips_object_argument_isset( object, "oarea" ) ) {
@ -449,18 +469,16 @@ vips_affine_build( VipsObject *object )
if( vips_object_argument_isset( object, "idy" ) ) if( vips_object_argument_isset( object, "idy" ) )
affine->trn.idy = affine->idy; affine->trn.idy = affine->idy;
if( vips__transform_calc_inverse( &affine->trn ) ) #ifdef DEBUG
return( -1 ); printf( "vips_affine_build: copy on identity transform disabled\n" );
#else /*!DEBUG*/
if( vips__transform_isidentity( &affine->trn ) && if( vips__transform_isidentity( &affine->trn ) &&
affine->trn.oarea.left == 0 && affine->trn.oarea.left == 0 &&
affine->trn.oarea.top == 0 && affine->trn.oarea.top == 0 &&
affine->trn.oarea.width == in->Xsize && affine->trn.oarea.width == in->Xsize &&
affine->trn.oarea.height == in->Ysize ) affine->trn.oarea.height == in->Ysize )
return( vips_image_write( in, resample->out ) ); return( vips_image_write( in, resample->out ) );
/* #endif /*!DEBUG*/
printf( "vips_affine_build: identity shortcut disabled\n" );
*/
/* Check for coordinate overflow ... we want to be able to hold the /* Check for coordinate overflow ... we want to be able to hold the
* output space inside INT_MAX / TRANSFORM_SCALE. * output space inside INT_MAX / TRANSFORM_SCALE.
@ -483,13 +501,14 @@ vips_affine_build( VipsObject *object )
*/ */
if( vips_embed( in, &t[2], if( vips_embed( in, &t[2],
window_offset, window_offset, window_offset, window_offset,
in->Xsize + window_size, in->Ysize + window_size, in->Xsize + window_size - 1, in->Ysize + window_size - 1,
"extend", VIPS_EXTEND_COPY, "extend", VIPS_EXTEND_COPY,
NULL ) ) NULL ) )
return( -1 ); return( -1 );
in = t[2]; in = t[2];
/* Normally SMALLTILE ... except if this is a size up/down affine. /* Normally SMALLTILE ... except if this is strictly a size
* up/down affine.
*/ */
if( affine->trn.b == 0.0 && if( affine->trn.b == 0.0 &&
affine->trn.c == 0.0 ) affine->trn.c == 0.0 )
@ -504,21 +523,14 @@ vips_affine_build( VipsObject *object )
resample->out->Ysize = affine->trn.oarea.height; resample->out->Ysize = affine->trn.oarea.height;
#ifdef DEBUG #ifdef DEBUG
printf( "vips_affine_build: transform: " printf( "vips_affine_build: transform: " );
"a = %g, b = %g, c = %g, d = %g\n", vips__transform_print( &affine->trn );
affine->trn.a, printf( " window_offset = %d, window_size = %d\n",
affine->trn.b, window_offset, window_size );
affine->trn.c, printf( " input image width = %d, height = %d\n",
affine->trn.d ); in->Xsize, in->Ysize );
#endif /*DEBUG*/ printf( " output image width = %d, height = %d\n",
resample->out->Xsize, resample->out->Ysize );
#ifdef DEBUG
printf( "vips_affine_build: output area: "
"left = %d, top = %d, width = %d, height = %d\n",
affine->trn.oarea.left,
affine->trn.oarea.top,
affine->trn.oarea.width,
affine->trn.oarea.height );
#endif /*DEBUG*/ #endif /*DEBUG*/
/* Generate! /* Generate!

View File

@ -156,8 +156,8 @@ vips__transform_print( const VipsTransformation *trn )
*/ */
void void
vips__transform_forward_point( const VipsTransformation *trn, vips__transform_forward_point( const VipsTransformation *trn,
double x, double y, /* In input space */ double x, double y, /* In input space */
double *ox, double *oy ) /* In output space */ double *ox, double *oy )/* In output space */
{ {
x += trn->idx; x += trn->idx;
y += trn->idy; y += trn->idy;
@ -170,11 +170,11 @@ vips__transform_forward_point( const VipsTransformation *trn,
*/ */
void void
vips__transform_invert_point( const VipsTransformation *trn, vips__transform_invert_point( const VipsTransformation *trn,
double x, double y, /* In output space */ double x, double y, /* In output space */
double *ox, double *oy ) /* In input space */ double *ox, double *oy )/* In input space */
{ {
x -= trn->odx; x -= trn->odx;
y -= trn->ody; y -= trn->ody;
*ox = trn->ia * x + trn->ib * y - trn->idx; *ox = trn->ia * x + trn->ib * y - trn->idx;
*oy = trn->ic * x + trn->id * y - trn->idy; *oy = trn->ic * x + trn->id * y - trn->idy;
@ -187,8 +187,8 @@ typedef void (*transform_fn)( const VipsTransformation *,
*/ */
static void static void
transform_rect( const VipsTransformation *trn, transform_fn transform, transform_rect( const VipsTransformation *trn, transform_fn transform,
const Rect *in, /* In input space */ const VipsRect *in, /* In input space */
Rect *out ) /* In output space */ VipsRect *out ) /* In output space */
{ {
double x1, y1; /* Map corners */ double x1, y1; /* Map corners */
double x2, y2; double x2, y2;
@ -196,25 +196,29 @@ transform_rect( const VipsTransformation *trn, transform_fn transform,
double x4, y4; double x4, y4;
double left, right, top, bottom; double left, right, top, bottom;
/* Map input Rect. /* Map input VipsRect.
*/ */
transform( trn, in->left, in->top, &x1, &y1 ); transform( trn, in->left, in->top,
transform( trn, in->left, IM_RECT_BOTTOM( in ), &x3, &y3 ); &x1, &y1 );
transform( trn, IM_RECT_RIGHT( in ), in->top, &x2, &y2 ); transform( trn, in->left, VIPS_RECT_BOTTOM( in ),
transform( trn, IM_RECT_RIGHT( in ), IM_RECT_BOTTOM( in ), &x4, &y4 ); &x3, &y3 );
transform( trn, VIPS_RECT_RIGHT( in ), in->top,
&x2, &y2 );
transform( trn, VIPS_RECT_RIGHT( in ), VIPS_RECT_BOTTOM( in ),
&x4, &y4 );
/* Find bounding box for these four corners. Round-to-nearest to try /* Find bounding box for these four corners. Round-to-nearest to try
* to stop rounding errors growing images. * to stop rounding errors growing images.
*/ */
left = IM_MIN( x1, IM_MIN( x2, IM_MIN( x3, x4 ) ) ); left = VIPS_MIN( x1, VIPS_MIN( x2, VIPS_MIN( x3, x4 ) ) );
right = IM_MAX( x1, IM_MAX( x2, IM_MAX( x3, x4 ) ) ); right = VIPS_MAX( x1, VIPS_MAX( x2, VIPS_MAX( x3, x4 ) ) );
top = IM_MIN( y1, IM_MIN( y2, IM_MIN( y3, y4 ) ) ); top = VIPS_MIN( y1, VIPS_MIN( y2, VIPS_MIN( y3, y4 ) ) );
bottom = IM_MAX( y1, IM_MAX( y2, IM_MAX( y3, y4 ) ) ); bottom = VIPS_MAX( y1, VIPS_MAX( y2, VIPS_MAX( y3, y4 ) ) );
out->left = IM_RINT( left ); out->left = VIPS_RINT( left );
out->top = IM_RINT( top ); out->top = VIPS_RINT( top );
out->width = IM_RINT( right - left ); out->width = VIPS_RINT( right - left );
out->height = IM_RINT( bottom - top ); out->height = VIPS_RINT( bottom - top );
} }
/* Given an area in the input image, calculate the bounding box for those /* Given an area in the input image, calculate the bounding box for those
@ -222,8 +226,8 @@ transform_rect( const VipsTransformation *trn, transform_fn transform,
*/ */
void void
vips__transform_forward_rect( const VipsTransformation *trn, vips__transform_forward_rect( const VipsTransformation *trn,
const Rect *in, /* In input space */ const VipsRect *in, /* In input space */
Rect *out ) /* In output space */ VipsRect *out ) /* In output space */
{ {
transform_rect( trn, vips__transform_forward_point, in, out ); transform_rect( trn, vips__transform_forward_point, in, out );
} }
@ -233,8 +237,8 @@ vips__transform_forward_rect( const VipsTransformation *trn,
*/ */
void void
vips__transform_invert_rect( const VipsTransformation *trn, vips__transform_invert_rect( const VipsTransformation *trn,
const Rect *in, /* In output space */ const VipsRect *in, /* In output space */
Rect *out ) /* In input space */ VipsRect *out ) /* In input space */
{ {
transform_rect( trn, vips__transform_invert_point, in, out ); transform_rect( trn, vips__transform_invert_point, in, out );
} }