redo im_affine as a class

This commit is contained in:
John Cupitt 2012-12-14 12:33:47 +00:00
parent 43d69e74e7
commit 4c82d45463
13 changed files with 427 additions and 287 deletions

View File

@ -32,6 +32,7 @@
- added scRGB colourspace, linear light float space with sRGB primaries - added scRGB colourspace, linear light float space with sRGB primaries
- all interpolators use corner convention ... we had round-to-nearest in - all interpolators use corner convention ... we had round-to-nearest in
several of them before, causing a range of annoying problems several of them before, causing a range of annoying problems
- redone im_affine*() as a class
14/11/12 started 7.30.6 14/11/12 started 7.30.6
- capture tiff warnings earlier - capture tiff warnings earlier

View File

@ -47,6 +47,7 @@
#include <vips/internal.h> #include <vips/internal.h>
#include <vips/debug.h> #include <vips/debug.h>
#include <vips/vector.h> #include <vips/vector.h>
#include <vips/transform.h>
VipsImage * VipsImage *
im_open( const char *filename, const char *mode ) im_open( const char *filename, const char *mode )
@ -2885,3 +2886,94 @@ im_cross_phase( IMAGE *in1, IMAGE *in2, IMAGE *out )
return( 0 ); return( 0 );
} }
static int
im__affinei( VipsImage *in, VipsImage *out,
VipsInterpolate *interpolate, VipsTransformation *trn )
{
VipsImage *x;
VipsArea *oarea;
oarea = (VipsArea *) vips_array_int_newv( 4,
trn->oarea.left, trn->oarea.top,
trn->oarea.width, trn->oarea.height );
if( vips_affine( in, &x,
trn->a, trn->b, trn->c, trn->d,
"interpolate", interpolate,
"oarea", oarea,
"odx", trn->dx,
"ody", trn->dy,
NULL ) ) {
vips_area_unref( oarea );
return( -1 );
}
vips_area_unref( oarea );
if( im_copy( x, out ) ) {
g_object_unref( x );
return( -1 );
}
g_object_unref( x );
return( 0 );
}
int
im_affinei( VipsImage *in, VipsImage *out, VipsInterpolate *interpolate,
double a, double b, double c, double d, double dx, double dy,
int ox, int oy, int ow, int oh )
{
VipsTransformation trn;
trn.iarea.left = 0;
trn.iarea.top = 0;
trn.iarea.width = in->Xsize;
trn.iarea.height = in->Ysize;
trn.oarea.left = ox;
trn.oarea.top = oy;
trn.oarea.width = ow;
trn.oarea.height = oh;
trn.a = a;
trn.b = b;
trn.c = c;
trn.d = d;
trn.dx = dx;
trn.dy = dy;
return( im__affinei( in, out, interpolate, &trn ) );
}
int
im_affinei_all( VipsImage *in, VipsImage *out, VipsInterpolate *interpolate,
double a, double b, double c, double d, double dx, double dy )
{
VipsTransformation trn;
trn.iarea.left = 0;
trn.iarea.top = 0;
trn.iarea.width = in->Xsize;
trn.iarea.height = in->Ysize;
trn.a = a;
trn.b = b;
trn.c = c;
trn.d = d;
trn.dx = dx;
trn.dy = dy;
vips__transform_set_area( &trn );
return( im__affinei( in, out, interpolate, &trn ) );
}
/* Still needed by some parts of mosaic.
*/
int
vips__affine( VipsImage *in, VipsImage *out, VipsTransformation *trn )
{
return( im__affinei( in, out,
vips_interpolate_bilinear_static(), trn ) );
}

View File

@ -30,25 +30,13 @@
*/ */
#ifndef IM_RESAMPLE_H #ifndef VIPS_RESAMPLE_H
#define IM_RESAMPLE_H #define VIPS_RESAMPLE_H
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif /*__cplusplus*/ #endif /*__cplusplus*/
int im_affinei( VipsImage *in, VipsImage *out,
VipsInterpolate *interpolate,
double a, double b, double c, double d, double dx, double dy,
int ox, int oy, int ow, int oh );
int im_affinei_all( VipsImage *in, VipsImage *out, VipsInterpolate *interpolate,
double a, double b, double c, double d, double dx, double dy ) ;
int vips_quadratic( VipsImage *in, VipsImage **out, VipsImage *coeff, ... );
int im_rightshift_size( VipsImage *in, VipsImage *out,
int xshift, int yshift, int band_fmt );
int im_match_linear( VipsImage *ref, VipsImage *sec, VipsImage *out, int im_match_linear( VipsImage *ref, VipsImage *sec, VipsImage *out,
int xr1, int yr1, int xs1, int ys1, int xr1, int yr1, int xs1, int ys1,
int xr2, int yr2, int xs2, int ys2 ); int xr2, int yr2, int xs2, int ys2 );
@ -59,13 +47,17 @@ int im_match_linear_search( VipsImage *ref, VipsImage *sec, VipsImage *out,
int vips_quadratic( VipsImage *in, VipsImage **out, VipsImage *coeff, ... )
__attribute__((sentinel));
int vips_shrink( VipsImage *in, VipsImage **out, int vips_shrink( VipsImage *in, VipsImage **out,
double xshrink, double yshrink, ... ) double xshrink, double yshrink, ... )
__attribute__((sentinel)); __attribute__((sentinel));
int vips_affine( VipsImage *in, VipsImage **out,
double a, double b, double c, double d, ... )
__attribute__((sentinel));
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif /*__cplusplus*/ #endif /*__cplusplus*/
#endif /*IM_RESAMPLE_H*/ #endif /*VIPS_RESAMPLE_H*/

View File

@ -27,6 +27,13 @@
*/ */
#ifndef VIPS_TRANSFORM_H
#define VIPS_TRANSFORM_H
#ifdef __cplusplus
extern "C" {
#endif /*__cplusplus*/
/* Params for an affine transformation. /* Params for an affine transformation.
*/ */
typedef struct { typedef struct {
@ -46,24 +53,31 @@ typedef struct {
double dx, dy; double dx, dy;
double ia, ib, ic, id; /* Inverse of matrix abcd */ double ia, ib, ic, id; /* Inverse of matrix abcd */
} Transformation; } VipsTransformation;
void im__transform_init( Transformation *trn ); void vips__transform_init( VipsTransformation *trn );
int im__transform_calc_inverse( Transformation *trn ); int vips__transform_calc_inverse( VipsTransformation *trn );
int im__transform_isidentity( const Transformation *trn ); int vips__transform_isidentity( const VipsTransformation *trn );
int im__transform_add( const Transformation *in1, const Transformation *in2, int vips__transform_add( const VipsTransformation *in1,
Transformation *out ); const VipsTransformation *in2,
void im__transform_print( const Transformation *trn ); VipsTransformation *out );
void vips__transform_print( const VipsTransformation *trn );
void im__transform_forward_point( const Transformation *trn, void vips__transform_forward_point( const VipsTransformation *trn,
const double x, const double y, double *ox, double *oy ); const double x, const double y, double *ox, double *oy );
void im__transform_invert_point( const Transformation *trn, void vips__transform_invert_point( const VipsTransformation *trn,
const double x, const double y, double *ox, double *oy ); const double x, const double y, double *ox, double *oy );
void im__transform_forward_rect( const Transformation *trn, void vips__transform_forward_rect( const VipsTransformation *trn,
const VipsRect *in, VipsRect *out ); const VipsRect *in, VipsRect *out );
void im__transform_invert_rect( const Transformation *trn, void vips__transform_invert_rect( const VipsTransformation *trn,
const VipsRect *in, VipsRect *out ); const VipsRect *in, VipsRect *out );
void im__transform_set_area( Transformation * ); void vips__transform_set_area( VipsTransformation * );
int im__affine( VipsImage *in, VipsImage *out, Transformation *trn ); int vips__affine( VipsImage *in, VipsImage *out, VipsTransformation *trn );
#ifdef __cplusplus
}
#endif /*__cplusplus*/
#endif /*VIPS_TRANSFORM_H*/

View File

@ -730,6 +730,14 @@ int im_recomb( VipsImage *in, VipsImage *out, DOUBLEMASK *recomb );
int im_argb2rgba( VipsImage *in, VipsImage *out ); int im_argb2rgba( VipsImage *in, VipsImage *out );
int im_shrink( VipsImage *in, VipsImage *out, double xshrink, double yshrink ); int im_shrink( VipsImage *in, VipsImage *out, double xshrink, double yshrink );
int im_affinei( VipsImage *in, VipsImage *out,
VipsInterpolate *interpolate,
double a, double b, double c, double d, double dx, double dy,
int ox, int oy, int ow, int oh );
int im_affinei_all( VipsImage *in, VipsImage *out, VipsInterpolate *interpolate,
double a, double b, double c, double d, double dx, double dy ) ;
int im_rightshift_size( VipsImage *in, VipsImage *out,
int xshift, int yshift, int band_fmt );
int im_Lab2XYZ_temp( IMAGE *in, IMAGE *out, double X0, double Y0, double Z0 ); int im_Lab2XYZ_temp( IMAGE *in, IMAGE *out, double X0, double Y0, double Z0 );
int im_Lab2XYZ( IMAGE *in, IMAGE *out ); int im_Lab2XYZ( IMAGE *in, IMAGE *out );

View File

@ -214,7 +214,7 @@ build_node( SymbolTable *st, char *name )
node->dirty = 0; node->dirty = 0;
node->mwidth = -2; node->mwidth = -2;
node->st = st; node->st = st;
im__transform_init( &node->cumtrn ); vips__transform_init( &node->cumtrn );
node->trnim = NULL; node->trnim = NULL;
node->arg1 = NULL; node->arg1 = NULL;
node->arg2 = NULL; node->arg2 = NULL;
@ -407,7 +407,7 @@ calc_geometry( JoinNode *node )
node->cumtrn.iarea.top = 0; node->cumtrn.iarea.top = 0;
node->cumtrn.iarea.width = um.width; node->cumtrn.iarea.width = um.width;
node->cumtrn.iarea.height = um.height; node->cumtrn.iarea.height = um.height;
im__transform_set_area( &node->cumtrn ); vips__transform_set_area( &node->cumtrn );
break; break;
case JOIN_CP: case JOIN_CP:
@ -424,7 +424,7 @@ calc_geometry( JoinNode *node )
node->cumtrn.iarea.top = 0; node->cumtrn.iarea.top = 0;
node->cumtrn.iarea.width = node->im->Xsize; node->cumtrn.iarea.width = node->im->Xsize;
node->cumtrn.iarea.height = node->im->Ysize; node->cumtrn.iarea.height = node->im->Ysize;
im__transform_set_area( &node->cumtrn ); vips__transform_set_area( &node->cumtrn );
} }
break; break;
@ -440,7 +440,7 @@ calc_geometry( JoinNode *node )
* have circularity. * have circularity.
*/ */
static int static int
propogate_transform( JoinNode *node, Transformation *trn ) propogate_transform( JoinNode *node, VipsTransformation *trn )
{ {
if( !node ) if( !node )
return( 0 ); return( 0 );
@ -460,7 +460,7 @@ propogate_transform( JoinNode *node, Transformation *trn )
/* Transform us, and recalculate our position and size. /* Transform us, and recalculate our position and size.
*/ */
im__transform_add( &node->cumtrn, trn, &node->cumtrn ); vips__transform_add( &node->cumtrn, trn, &node->cumtrn );
calc_geometry( node ); calc_geometry( node );
return( 0 ); return( 0 );
@ -475,7 +475,7 @@ make_join( SymbolTable *st, JoinType type,
JoinNode *arg1, JoinNode *arg2, JoinNode *out, JoinNode *arg1, JoinNode *arg2, JoinNode *out,
double a, double b, double dx, double dy, int mwidth ) double a, double b, double dx, double dy, int mwidth )
{ {
Transformation trn; VipsTransformation trn;
/* Check output is ok. /* Check output is ok.
*/ */
@ -1528,13 +1528,13 @@ generate_trn_leaves( JoinNode *node, SymbolTable *st )
/* Special case: if this is an untransformed leaf (there will /* Special case: if this is an untransformed leaf (there will
* always be at least one), then skip the affine. * always be at least one), then skip the affine.
*/ */
if( im__transform_isidentity( &node->cumtrn ) ) if( vips__transform_isidentity( &node->cumtrn ) )
node->trnim = node->im; node->trnim = node->im;
else else
if( !(node->trnim = if( !(node->trnim =
im_open_local( node->st->im, im_open_local( node->st->im,
"trnleaf:1", "p" )) || "trnleaf:1", "p" )) ||
im__affine( node->im, node->trnim, vips__affine( node->im, node->trnim,
&node->cumtrn ) ) &node->cumtrn ) )
return( node ); return( node );
} }

View File

@ -83,13 +83,13 @@ struct _JoinNode {
* cumtrn.area is position and size of us, thistrn.area is pos and * cumtrn.area is position and size of us, thistrn.area is pos and
* size of arg2. * size of arg2.
*/ */
Transformation cumtrn; VipsTransformation cumtrn;
/* X-tras for LR/TB. thistrn is what we do to arg2. /* X-tras for LR/TB. thistrn is what we do to arg2.
*/ */
JoinNode *arg1; /* Left or up thing to join */ JoinNode *arg1; /* Left or up thing to join */
JoinNode *arg2; /* Right or down thing to join */ JoinNode *arg2; /* Right or down thing to join */
Transformation thistrn; /* Transformation for arg2 */ VipsTransformation thistrn; /* Transformation for arg2 */
/* Special for leaves: all the join_nodes we overlap with, the /* Special for leaves: all the join_nodes we overlap with, the
* IMAGE for that file, and the index. * IMAGE for that file, and the index.

View File

@ -69,7 +69,7 @@
/* Like im_similarity(), but return the transform we generated. /* Like im_similarity(), but return the transform we generated.
*/ */
static int static int
apply_similarity( Transformation *trn, IMAGE *in, IMAGE *out, apply_similarity( VipsTransformation *trn, IMAGE *in, IMAGE *out,
double a, double b, double dx, double dy ) double a, double b, double dx, double dy )
{ {
trn->iarea.left = 0; trn->iarea.left = 0;
@ -82,11 +82,11 @@ apply_similarity( Transformation *trn, IMAGE *in, IMAGE *out,
trn->d = a; trn->d = a;
trn->dx = dx; trn->dx = dx;
trn->dy = dy; trn->dy = dy;
im__transform_set_area( trn ); vips__transform_set_area( trn );
if( im__transform_calc_inverse( trn ) ) if( vips__transform_calc_inverse( trn ) )
return( -1 ); return( -1 );
if( im__affine( in, out, trn ) ) if( vips__affine( in, out, trn ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
@ -103,7 +103,7 @@ int
im__lrmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, im__lrmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out,
double a, double b, double dx, double dy, int mwidth ) double a, double b, double dx, double dy, int mwidth )
{ {
Transformation trn; VipsTransformation trn;
IMAGE *t1 = im_open_local( out, "im_lrmosaic1:1", "p" ); IMAGE *t1 = im_open_local( out, "im_lrmosaic1:1", "p" );
VipsBuf buf; VipsBuf buf;
char text[1024]; char text[1024];
@ -145,7 +145,7 @@ int
im__tbmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, im__tbmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out,
double a, double b, double dx, double dy, int mwidth ) double a, double b, double dx, double dy, int mwidth )
{ {
Transformation trn; VipsTransformation trn;
IMAGE *t1 = im_open_local( out, "im_lrmosaic1:2", "p" ); IMAGE *t1 = im_open_local( out, "im_lrmosaic1:2", "p" );
VipsBuf buf; VipsBuf buf;
char text[1024]; char text[1024];
@ -316,7 +316,7 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn,
int balancetype, int balancetype,
int mwidth ) int mwidth )
{ {
Transformation trn; VipsTransformation trn;
double cor1, cor2; double cor1, cor2;
double a, b, dx, dy; double a, b, dx, dy;
double xs3, ys3; double xs3, ys3;
@ -357,11 +357,11 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn,
/* Map points on sec to rotated image. /* Map points on sec to rotated image.
*/ */
im__transform_forward_point( &trn, xs1, ys1, &xs3, &ys3 ); vips__transform_forward_point( &trn, xs1, ys1, &xs3, &ys3 );
im__transform_forward_point( &trn, xs2, ys2, &xs4, &ys4 ); vips__transform_forward_point( &trn, xs2, ys2, &xs4, &ys4 );
/* Refine tie-points on rotated image. Remember the clip /* Refine tie-points on rotated image. Remember the clip
* im__transform_set_area() has set, and move the sec tie-points * vips__transform_set_area() has set, and move the sec tie-points
* accordingly. * accordingly.
*/ */
if( im_correl( t[0], t[2], xr1, yr1, if( im_correl( t[0], t[2], xr1, yr1,
@ -391,8 +391,8 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn,
/* ... and now back to input space again. /* ... and now back to input space again.
*/ */
im__transform_invert_point( &trn, xs5, ys5, &xs7, &ys7 ); vips__transform_invert_point( &trn, xs5, ys5, &xs7, &ys7 );
im__transform_invert_point( &trn, xs6, ys6, &xs8, &ys8 ); vips__transform_invert_point( &trn, xs6, ys6, &xs8, &ys8 );
/* Recalc the transform using the refined points. /* Recalc the transform using the refined points.
*/ */
@ -548,7 +548,7 @@ old_lrmosaic1( IMAGE *ref, IMAGE *sec, IMAGE *out,
int balancetype, int balancetype,
int mwidth ) int mwidth )
{ {
Transformation trn1, trn2; VipsTransformation trn1, trn2;
int dx0, dy0; int dx0, dy0;
double a, b, dx, dy; double a, b, dx, dy;
double a1, b1, dx1, dy1; double a1, b1, dx1, dy1;

View File

@ -5,11 +5,11 @@
if ENABLE_CXX if ENABLE_CXX
libresample_la_SOURCES = \ libresample_la_SOURCES = \
affine.c \
quadratic.c \ quadratic.c \
resample.c \ resample.c \
resample.h \ resample.h \
shrink.c \ shrink.c \
im_affine.c \
interpolate.c \ interpolate.c \
transform.c \ transform.c \
resample_dispatch.c \ resample_dispatch.c \

View File

@ -76,6 +76,8 @@
* interpolate.c * interpolate.c
* 2/2/11 * 2/2/11
* - gtk-doc * - gtk-doc
* 14/12/12
* - redone as a class
*/ */
/* /*
@ -121,9 +123,31 @@
#include <limits.h> #include <limits.h>
#include <vips/vips.h> #include <vips/vips.h>
#include <vips/debug.h>
#include <vips/internal.h> #include <vips/internal.h>
#include <vips/transform.h> #include <vips/transform.h>
#include "resample.h"
typedef struct _VipsAffine {
VipsResample parent_instance;
VipsArea *matrix;
VipsInterpolate *interpolate;
VipsArea *oarea;
double odx;
double ody;
double idx;
double idy;
VipsTransformation trn;
} VipsAffine;
typedef VipsResampleClass VipsAffineClass;
G_DEFINE_TYPE( VipsAffine, vips_affine, VIPS_TYPE_RESAMPLE );
/* /*
* FAST_PSEUDO_FLOOR is a floor and floorf replacement which has been * FAST_PSEUDO_FLOOR is a floor and floorf replacement which has been
* found to be faster on several linux boxes than the library * found to be faster on several linux boxes than the library
@ -147,23 +171,6 @@
*/ */
#define FAST_PSEUDO_FLOOR(x) ( (int)(x) - ( (x) < 0. ) ) #define FAST_PSEUDO_FLOOR(x) ( (int)(x) - ( (x) < 0. ) )
/* Per-call state.
*/
typedef struct _Affine {
VipsImage *in;
VipsImage *out;
VipsInterpolate *interpolate;
Transformation trn;
} Affine;
static int
affine_free( Affine *affine )
{
VIPS_FREEF( g_object_unref, affine->interpolate );
return( 0 );
}
/* We have five (!!) coordinate systems. Working forward through them, these /* We have five (!!) coordinate systems. Working forward through them, these
* are: * are:
* *
@ -171,7 +178,7 @@ affine_free( Affine *affine )
* *
* 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. iarea->left/top give the offset. These are the coordinates we
* pass to VIPS_REGION_ADDR()/im_prepare() for the input image. * pass to VIPS_REGION_ADDR()/vips_region_prepare() for the input image.
* *
* 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,
@ -189,17 +196,17 @@ affine_free( Affine *affine )
* 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.
* *
* 5. Output image space. This is the wh of the xywh passed to im_affine() * 5. Output image space. This is the wh of the xywh passed to vips_affine()
* below. These are the coordinates we pass to VIPS_REGION_ADDR() for the * below. These are the coordinates we pass to VIPS_REGION_ADDR() for the
* output image, and that affinei_gen() is asked for. * output image, and that affinei_gen() is asked for.
*/ */
static int static int
affinei_gen( VipsRegion *or, void *seq, void *a, void *b ) vips_affine_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
{ {
VipsRegion *ir = (VipsRegion *) seq; VipsRegion *ir = (VipsRegion *) seq;
const VipsAffine *affine = (VipsAffine *) b;
const VipsImage *in = (VipsImage *) a; const VipsImage *in = (VipsImage *) a;
const Affine *affine = (Affine *) b;
const int window_size = const int window_size =
vips_interpolate_get_window_size( affine->interpolate ); vips_interpolate_get_window_size( affine->interpolate );
const int window_offset = const int window_offset =
@ -209,19 +216,19 @@ affinei_gen( VipsRegion *or, void *seq, void *a, void *b )
/* Area we generate in the output image. /* Area we generate in the output image.
*/ */
const Rect *r = &or->valid; const VipsRect *r = &or->valid;
const int le = r->left; const int le = r->left;
const int ri = VIPS_RECT_RIGHT( r ); const int ri = VIPS_RECT_RIGHT( r );
const int to = r->top; const int to = r->top;
const int bo = VIPS_RECT_BOTTOM( r ); const int bo = VIPS_RECT_BOTTOM( r );
const Rect *iarea = &affine->trn.iarea; const VipsRect *iarea = &affine->trn.iarea;
const Rect *oarea = &affine->trn.oarea; const VipsRect *oarea = &affine->trn.oarea;
int ps = VIPS_IMAGE_SIZEOF_PEL( in ); int ps = VIPS_IMAGE_SIZEOF_PEL( in );
int x, y, z; int x, y, z;
Rect image, want, need, clipped; VipsRect image, want, need, clipped;
#ifdef DEBUG #ifdef DEBUG
printf( "affine: generating left=%d, top=%d, width=%d, height=%d\n", printf( "affine: generating left=%d, top=%d, width=%d, height=%d\n",
@ -239,7 +246,7 @@ affinei_gen( VipsRegion *or, void *seq, void *a, void *b )
/* Find the area of the input image we need. /* Find the area of the input image we need.
*/ */
im__transform_invert_rect( &affine->trn, &want, &need ); vips__transform_invert_rect( &affine->trn, &want, &need );
/* That does round-to-nearest, because it has to stop rounding errors /* That does round-to-nearest, because it has to stop rounding errors
* growing images unexpectedly. We need round-down, so we must * growing images unexpectedly. We need round-down, so we must
@ -248,7 +255,7 @@ affinei_gen( VipsRegion *or, void *seq, void *a, void *b )
* *
* Add an extra line along the right and bottom as well, for rounding. * Add an extra line along the right and bottom as well, for rounding.
*/ */
im_rect_marginadjust( &need, 1 ); vips_rect_marginadjust( &need, 1 );
/* Now go to space (2) above. /* Now go to space (2) above.
*/ */
@ -268,19 +275,19 @@ affinei_gen( VipsRegion *or, void *seq, void *a, void *b )
image.top = 0; image.top = 0;
image.width = in->Xsize; image.width = in->Xsize;
image.height = in->Ysize; image.height = in->Ysize;
im_rect_intersectrect( &need, &image, &clipped ); vips_rect_intersectrect( &need, &image, &clipped );
/* Outside input image? All black. /* Outside input image? All black.
*/ */
if( im_rect_isempty( &clipped ) ) { if( vips_rect_isempty( &clipped ) ) {
im_region_black( or ); vips_region_black( or );
return( 0 ); return( 0 );
} }
/* We do need some pixels from the input image to make our output - /* We do need some pixels from the input image to make our output -
* ask for them. * ask for them.
*/ */
if( im_prepare( ir, &clipped ) ) if( vips_region_prepare( ir, &clipped ) )
return( -1 ); return( -1 );
#ifdef DEBUG #ifdef DEBUG
@ -355,50 +362,74 @@ affinei_gen( VipsRegion *or, void *seq, void *a, void *b )
return( 0 ); return( 0 );
} }
static int static int
affinei( VipsImage *in, VipsImage *out, vips_affine_build( VipsObject *object )
VipsInterpolate *interpolate, Transformation *trn )
{ {
Affine *affine; VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsResample *resample = VIPS_RESAMPLE( object );
VipsAffine *affine = (VipsAffine *) object;
VipsImage **t = (VipsImage **)
vips_object_local_array( object, 4 );
VipsImage *in;
gboolean repack;
int window_size;
int window_offset;
double edge; double edge;
/* Make output image. if( VIPS_OBJECT_CLASS( vips_affine_parent_class )->build( object ) )
return( -1 );
if( vips_check_coding_noneorlabq( class->nickname, in ) )
return( -1 );
if( vips_check_vector_length( class->nickname,
affine->matrix->n, 4 ) )
return( -1 );
if( vips_object_argument_isset( object, "oarea" ) &&
vips_check_vector_length( class->nickname,
affine->oarea->n, 4 ) )
return( -1 );
in = resample->in;
/* Set up transform.
*/ */
if( im_piocheck( in, out ) ||
im_cp_desc( out, in ) )
return( -1 );
/* Need a copy of the params for the lifetime of out. window_size = vips_interpolate_get_window_size( affine->interpolate );
*/ window_offset =
if( !(affine = VIPS_NEW( out, Affine )) ) vips_interpolate_get_window_offset( affine->interpolate );
return( -1 );
affine->interpolate = NULL;
if( im_add_close_callback( out,
(im_callback_fn) affine_free, affine, NULL ) )
return( -1 );
affine->in = in;
affine->out = out;
affine->interpolate = interpolate;
g_object_ref( interpolate );
affine->trn = *trn;
if( im__transform_calc_inverse( &affine->trn ) ) affine->trn.iarea.left = window_offset;
return( -1 ); affine->trn.iarea.top = window_offset;
affine->trn.iarea.width = in->Xsize;
affine->trn.iarea.height = in->Ysize;
affine->trn.a = ((double *) affine->matrix->data)[0];
affine->trn.b = ((double *) affine->matrix->data)[1];
affine->trn.c = ((double *) affine->matrix->data)[2];
affine->trn.d = ((double *) affine->matrix->data)[3];
affine->trn.dx = 0;
affine->trn.dy = 0;
out->Xsize = affine->trn.oarea.width; vips__transform_set_area( &affine->trn );
out->Ysize = affine->trn.oarea.height; if( vips_object_argument_isset( object, "oarea" ) ) {
affine->trn.oarea.left = ((int *) affine->oarea->data)[0];
/* Normally SMALLTILE ... except if this is a size up/down affine. affine->trn.oarea.top = ((int *) affine->oarea->data)[1];
*/ affine->trn.oarea.width = ((int *) affine->oarea->data)[2];
if( affine->trn.b == 0.0 && affine->trn.c == 0.0 ) { affine->trn.oarea.height = ((int *) affine->oarea->data)[3];
if( im_demand_hint( out, VIPS_DEMAND_STYLE_FATSTRIP, in, NULL ) )
return( -1 );
}
else {
if( im_demand_hint( out, VIPS_DEMAND_STYLE_SMALLTILE, in, NULL ) )
return( -1 );
} }
if( vips_object_argument_isset( object, "odx" ) )
affine->trn.dx = affine->odx;
if( vips_object_argument_isset( object, "ody" ) )
affine->trn.dx = affine->ody;
if( vips__transform_calc_inverse( &affine->trn ) )
return( -1 );
resample->out->Xsize = affine->trn.oarea.width;
resample->out->Ysize = affine->trn.oarea.height;
/* 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.
*/ */
@ -406,186 +437,189 @@ affinei( VipsImage *in, VipsImage *out,
if( affine->trn.oarea.left < -edge || affine->trn.oarea.top < -edge || if( affine->trn.oarea.left < -edge || affine->trn.oarea.top < -edge ||
VIPS_RECT_RIGHT( &affine->trn.oarea ) > edge || VIPS_RECT_RIGHT( &affine->trn.oarea ) > edge ||
VIPS_RECT_BOTTOM( &affine->trn.oarea ) > edge ) { VIPS_RECT_BOTTOM( &affine->trn.oarea ) > edge ) {
im_error( "im_affinei", vips_error( class->nickname,
"%s", _( "output coordinates out of range" ) ); "%s", _( "output coordinates out of range" ) );
return( -1 ); return( -1 );
} }
/* Generate! /* Unpack labq for processing ... we repack after, see below.
*/ */
if( im_generate( out, repack = FALSE;
im_start_one, affinei_gen, im_stop_one, in, affine ) ) if( in->Coding == VIPS_CODING_LABQ ) {
return( -1 ); if( vips_LabQ2LabS( in, &t[0], NULL ) )
return( -1 );
return( 0 ); repack = TRUE;
} in = t[0];
}
/* As above, but do VIPS_CODING_LABQ too. And embed the input.
*/
static int
im__affinei( VipsImage *in, VipsImage *out,
VipsInterpolate *interpolate, Transformation *trn )
{
VipsImage *t3 = im_open_local( out, "im_affine:3", "p" );
const int window_size =
vips_interpolate_get_window_size( interpolate );
const int window_offset =
vips_interpolate_get_window_offset( interpolate );
Transformation trn2;
/* 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.
*/ */
if( !t3 || if( vips_embed( in, &t[1], 1,
im_embed( in, t3, 1, window_offset, window_offset,
window_offset, window_offset, in->Xsize + window_size, in->Ysize + window_size,
in->Xsize + window_size, in->Ysize + window_size ) ) "extend", VIPS_EXTEND_COPY,
NULL ) )
return( -1 ); return( -1 );
in = t[1];
/* Set iarea so we know what part of the input we can take. /* Normally SMALLTILE ... except if this is a size up/down affine.
*/ */
trn2 = *trn; if( affine->trn.b == 0.0 &&
trn2.iarea.left += window_offset; affine->trn.c == 0.0 )
trn2.iarea.top += window_offset; vips_demand_hint( resample->out,
VIPS_DEMAND_STYLE_FATSTRIP, resample->in, NULL );
else
vips_demand_hint( resample->out,
VIPS_DEMAND_STYLE_SMALLTILE, resample->in, NULL );
#ifdef DEBUG_GEOMETRY /* Generate!
printf( "im__affinei: %s\n", in->filename ); */
im__transform_print( &trn2 ); if( vips_image_generate( resample->out,
#endif /*DEBUG_GEOMETRY*/ vips_start_one, vips_affine_gen, vips_stop_one,
resample->in, affine ) )
if( in->Coding == VIPS_CODING_LABQ ) {
VipsImage *t[2];
if( im_open_local_array( out, t, 2, "im_affine:2", "p" ) ||
im_LabQ2LabS( t3, t[0] ) ||
affinei( t[0], t[1], interpolate, &trn2 ) ||
im_LabS2LabQ( t[1], out ) )
return( -1 );
}
else if( in->Coding == VIPS_CODING_NONE ) {
if( affinei( t3, out, interpolate, &trn2 ) )
return( -1 );
}
else {
im_error( "im_affinei", "%s", _( "unknown coding type" ) );
return( -1 ); return( -1 );
}
/* Finally: can now set Xoffset/Yoffset. /* Finally: can now set Xoffset/Yoffset.
*/ */
out->Xoffset = trn->dx - trn->oarea.left; resample->out->Xoffset = affine->trn.dx - affine->trn.oarea.left;
out->Yoffset = trn->dy - trn->oarea.top; resample->out->Yoffset = affine->trn.dy - affine->trn.oarea.top;
if( repack ) {
if( vips_LabS2LabQ( resample->out, &t[2], NULL ) )
return( -1 );
resample->out = t[2];
}
return( 0 ); return( 0 );
} }
static void
vips_affine_class_init( VipsAffineClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
VIPS_DEBUG_MSG( "vips_affine_class_init\n" );
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "affine";
vobject_class->description = _( "affine transform of an image" );
vobject_class->build = vips_affine_build;
VIPS_ARG_BOXED( class, "matrix", 110,
_( "Matrix" ),
_( "Transformation matrix" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsAffine, matrix ),
VIPS_TYPE_ARRAY_DOUBLE );
VIPS_ARG_INTERPOLATE( class, "interpolate", 2,
_( "Interpolate" ),
_( "Interpolate pixels with this" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsAffine, interpolate ) );
VIPS_ARG_BOXED( class, "oarea", 111,
_( "Output rect" ),
_( "Area of output to generate" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsAffine, oarea ),
VIPS_TYPE_ARRAY_INT );
VIPS_ARG_DOUBLE( class, "odx", 112,
_( "Output offset" ),
_( "Horizontal output displacement" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsAffine, odx ),
0, 10000000, 0 );
VIPS_ARG_DOUBLE( class, "ody", 113,
_( "Output offset" ),
_( "Vertical output displacement" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsAffine, ody ),
0, 10000000, 0 );
VIPS_ARG_DOUBLE( class, "idx", 112,
_( "Input offset" ),
_( "Horizontal input displacement" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsAffine, idx ),
0, 10000000, 0 );
VIPS_ARG_DOUBLE( class, "idy", 113,
_( "Input offset" ),
_( "Vertical input displacement" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsAffine, idy ),
0, 10000000, 0 );
}
static void
vips_affine_init( VipsAffine *affine )
{
}
/** /**
* im_affinei: * vips_affine:
* @in: input image * @in: input image
* @out: output image * @out: output image
* @interpolate: interpolation method * @a: transformation matrix coefficient
* @a: transformation matrix * @b: transformation matrix coefficient
* @b: transformation matrix * @c: transformation matrix coefficient
* @c: transformation matrix * @d: transformation matrix coefficient
* @d: transformation matrix *
* @dx: output offset * Optional arguments:
* @dy: output offset *
* @ox: output region * @interpolate: interpolate pixels with this
* @oy: output region * @oarea: output rectangle
* @ow: output region * @idx: input horizontal offset
* @oh: output region * @idy: input vertical offset
* @odx: output horizontal offset
* @ody: output vertical offset
* *
* This operator performs an affine transform on an image using @interpolate. * This operator performs an affine transform on an image using @interpolate.
* *
* The transform is: * The transform is:
* *
* X = @a * x + @b * y + @dx * X = @a * (x + @idx) + @b * (y + @idy) + @odx
* Y = @c * x + @d * y + @dy * Y = @c * (x + @idx) + @d * (y + @idy) + @doy
* *
* x and y are the coordinates in input image. * x and y are the coordinates in input image.
* X and Y are the coordinates in output image. * X and Y are the coordinates in output image.
* (0,0) is the upper left corner. * (0,0) is the upper left corner.
* *
* The section of the output space defined by @ox, @oy, @ow, @oh is written to * The section of the output space defined by @oarea is written to
* @out. See im_affinei_all() for a function which outputs all the transformed * @out. @oarea is a four-element int array of left, top, width, height.
* pixels. * By default @oarea is just large enough to cover the whole of the
* transformed input image.
* *
* See also: im_affinei_all(), #VipsInterpolate. * @interpolate defaults to bilinear.
*
* @idx, @idy, @odx, @ody default to zero.
*
* See also: vips_shrink(), #VipsInterpolate.
* *
* Returns: 0 on success, -1 on error * Returns: 0 on success, -1 on error
*/ */
int int
im_affinei( VipsImage *in, VipsImage *out, VipsInterpolate *interpolate, vips_affine( VipsImage *in, VipsImage **out,
double a, double b, double c, double d, double dx, double dy, double a, double b, double c, double d, ... )
int ox, int oy, int ow, int oh )
{ {
Transformation trn; va_list ap;
VipsArea *matrix;
int result;
trn.iarea.left = 0; matrix = (VipsArea *) vips_array_double_newv( 4, a, b, c, d );
trn.iarea.top = 0;
trn.iarea.width = in->Xsize;
trn.iarea.height = in->Ysize;
trn.oarea.left = ox; va_start( ap, d );
trn.oarea.top = oy; result = vips_call_split( "shrink", ap, in, out, matrix );
trn.oarea.width = ow; va_end( ap );
trn.oarea.height = oh;
trn.a = a; vips_area_unref( matrix );
trn.b = b;
trn.c = c;
trn.d = d;
trn.dx = dx;
trn.dy = dy;
return( im__affinei( in, out, interpolate, &trn ) ); return( result );
}
/**
* im_affinei_all:
* @in: input image
* @out: output image
* @interpolate: interpolation method
* @a: transformation matrix
* @b: transformation matrix
* @c: transformation matrix
* @d: transformation matrix
* @dx: output offset
* @dy: output offset
*
* As im_affinei(), but the entire image is output.
*
* See also: im_affinei(), #VipsInterpolate.
*
* Returns: 0 on success, -1 on error
*/
int
im_affinei_all( VipsImage *in, VipsImage *out, VipsInterpolate *interpolate,
double a, double b, double c, double d, double dx, double dy )
{
Transformation trn;
trn.iarea.left = 0;
trn.iarea.top = 0;
trn.iarea.width = in->Xsize;
trn.iarea.height = in->Ysize;
trn.a = a;
trn.b = b;
trn.c = c;
trn.d = d;
trn.dx = dx;
trn.dy = dy;
im__transform_set_area( &trn );
return( im__affinei( in, out, interpolate, &trn ) );
}
/* Still needed by some parts of mosaic.
*/
int
im__affine( VipsImage *in, VipsImage *out, Transformation *trn )
{
return( im__affinei( in, out,
vips_interpolate_bilinear_static(), trn ) );
} }

View File

@ -115,8 +115,10 @@ vips_resample_operation_init( void )
{ {
extern GType vips_shrink_get_type( void ); extern GType vips_shrink_get_type( void );
extern GType vips_quadratic_get_type( void ); extern GType vips_quadratic_get_type( void );
extern GType vips_affine_get_type( void );
vips_shrink_get_type(); vips_shrink_get_type();
vips_quadratic_get_type(); vips_quadratic_get_type();
vips_affine_get_type();
} }

View File

@ -307,7 +307,7 @@ vips_shrink_build( VipsObject *object )
shrink->mh = ceil( shrink->yshrink ); shrink->mh = ceil( shrink->yshrink );
shrink->np = shrink->mw * shrink->mh; shrink->np = shrink->mw * shrink->mh;
if( im_check_noncomplex( class->nickname, resample->in ) ) if( vips_check_noncomplex( class->nickname, resample->in ) )
return( -1 ); return( -1 );
if( shrink->xshrink < 1.0 || if( shrink->xshrink < 1.0 ||
@ -327,9 +327,6 @@ vips_shrink_build( VipsObject *object )
shrink->yshrink == 1.0 ) shrink->yshrink == 1.0 )
return( vips_image_write( resample->in, resample->out ) ); return( vips_image_write( resample->in, resample->out ) );
if( vips_image_copy_fields( resample->out, resample->in ) )
return( -1 );
/* THINSTRIP will work, FATSTRIP will break seq mode. If you combine /* THINSTRIP will work, FATSTRIP will break seq mode. If you combine
* shrink with conv you'll need to use a line cache to maintain * shrink with conv you'll need to use a line cache to maintain
* sequentiality. * sequentiality.
@ -410,10 +407,10 @@ vips_shrink_init( VipsShrink *shrink )
* *
* You will get aliasing for non-integer shrinks. In this case, shrink with * You will get aliasing for non-integer shrinks. In this case, shrink with
* this function to the nearest integer size above the target shrink, then * this function to the nearest integer size above the target shrink, then
* downsample to the exact size with im_affinei() and your choice of * downsample to the exact size with vips_affine() and your choice of
* interpolator. * interpolator.
* *
* See also: im_affinei(). * See also: vips_affine().
* *
* Returns: 0 on success, -1 on error * Returns: 0 on success, -1 on error
*/ */

View File

@ -49,7 +49,7 @@
/* Calculate the inverse transformation. /* Calculate the inverse transformation.
*/ */
int int
im__transform_calc_inverse( Transformation *trn ) vips__transform_calc_inverse( VipsTransformation *trn )
{ {
DOUBLEMASK *msk, *msk2; DOUBLEMASK *msk, *msk2;
@ -70,10 +70,10 @@ im__transform_calc_inverse( Transformation *trn )
return( 0 ); return( 0 );
} }
/* Init a Transform. /* Init a VipsTransform.
*/ */
void void
im__transform_init( Transformation *trn ) vips__transform_init( VipsTransformation *trn )
{ {
trn->oarea.left = 0; trn->oarea.left = 0;
trn->oarea.top = 0; trn->oarea.top = 0;
@ -90,13 +90,13 @@ im__transform_init( Transformation *trn )
trn->dx = 0.0; trn->dx = 0.0;
trn->dy = 0.0; trn->dy = 0.0;
(void) im__transform_calc_inverse( trn ); (void) vips__transform_calc_inverse( trn );
} }
/* Test for transform is identity function. /* Test for transform is identity function.
*/ */
int int
im__transform_isidentity( const Transformation *trn ) vips__transform_isidentity( const VipsTransformation *trn )
{ {
if( trn->a == 1.0 && trn->b == 0.0 && trn->c == 0.0 && if( trn->a == 1.0 && trn->b == 0.0 && trn->c == 0.0 &&
trn->d == 1.0 && trn->dx == 0.0 && trn->dy == 0.0 ) trn->d == 1.0 && trn->dx == 0.0 && trn->dy == 0.0 )
@ -108,8 +108,8 @@ im__transform_isidentity( const Transformation *trn )
/* Combine two transformations. out can be one of the ins. /* Combine two transformations. out can be one of the ins.
*/ */
int int
im__transform_add( const Transformation *in1, const Transformation *in2, vips__transform_add( const VipsTransformation *in1,
Transformation *out ) const VipsTransformation *in2, VipsTransformation *out )
{ {
out->a = in1->a * in2->a + in1->c * in2->b; out->a = in1->a * in2->a + in1->c * in2->b;
out->b = in1->b * in2->a + in1->d * in2->b; out->b = in1->b * in2->a + in1->d * in2->b;
@ -119,16 +119,16 @@ im__transform_add( const Transformation *in1, const Transformation *in2,
out->dx = in1->dx * in2->a + in1->dy * in2->b + in2->dx; out->dx = in1->dx * in2->a + in1->dy * in2->b + in2->dx;
out->dy = in1->dx * in2->c + in1->dy * in2->d + in2->dy; out->dy = in1->dx * in2->c + in1->dy * in2->d + in2->dy;
if( im__transform_calc_inverse( out ) ) if( vips__transform_calc_inverse( out ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
} }
void void
im__transform_print( const Transformation *trn ) vips__transform_print( const VipsTransformation *trn )
{ {
printf( "im__transform_print:\n" ); printf( "vips__transform_print:\n" );
printf( " iarea: left=%d, top=%d, width=%d, height=%d\n", printf( " iarea: left=%d, top=%d, width=%d, height=%d\n",
trn->iarea.left, trn->iarea.left,
trn->iarea.top, trn->iarea.top,
@ -148,9 +148,9 @@ im__transform_print( const Transformation *trn )
/* Map a pixel coordinate through the transform. /* Map a pixel coordinate through the transform.
*/ */
void void
im__transform_forward_point( const Transformation *trn, vips__transform_forward_point( const VipsTransformation *trn,
const double x, const double y, /* In input space */ const double x, const double y, /* In input space */
double *ox, double *oy ) /* In output space */ double *ox, double *oy ) /* In output space */
{ {
*ox = trn->a * x + trn->b * y + trn->dx; *ox = trn->a * x + trn->b * y + trn->dx;
*oy = trn->c * x + trn->d * y + trn->dy; *oy = trn->c * x + trn->d * y + trn->dy;
@ -159,9 +159,9 @@ im__transform_forward_point( const Transformation *trn,
/* Map a pixel coordinate through the inverse transform. /* Map a pixel coordinate through the inverse transform.
*/ */
void void
im__transform_invert_point( const Transformation *trn, vips__transform_invert_point( const VipsTransformation *trn,
const double x, const double y, /* In output space */ const double x, const double y, /* In output space */
double *ox, double *oy ) /* In input space */ double *ox, double *oy ) /* In input space */
{ {
double mx = x - trn->dx; double mx = x - trn->dx;
double my = y - trn->dy; double my = y - trn->dy;
@ -170,13 +170,13 @@ im__transform_invert_point( const Transformation *trn,
*oy = trn->ic * mx + trn->id * my; *oy = trn->ic * mx + trn->id * my;
} }
typedef void (*transform_fn)( const Transformation *, typedef void (*transform_fn)( const VipsTransformation *,
const double, const double, double*, double* ); const double, const double, double*, double* );
/* Transform a rect using a point transformer. /* Transform a rect using a point transformer.
*/ */
static void static void
transform_rect( const Transformation *trn, transform_fn transform, transform_rect( const VipsTransformation *trn, transform_fn transform,
const Rect *in, /* In input space */ const Rect *in, /* In input space */
Rect *out ) /* In output space */ Rect *out ) /* In output space */
{ {
@ -211,28 +211,28 @@ transform_rect( const Transformation *trn, transform_fn transform,
* pixels in the output image. * pixels in the output image.
*/ */
void void
im__transform_forward_rect( const Transformation *trn, vips__transform_forward_rect( const VipsTransformation *trn,
const Rect *in, /* In input space */ const Rect *in, /* In input space */
Rect *out ) /* In output space */ Rect *out ) /* In output space */
{ {
transform_rect( trn, im__transform_forward_point, in, out ); transform_rect( trn, vips__transform_forward_point, in, out );
} }
/* Given an area in the output image, calculate the bounding box for the /* Given an area in the output image, calculate the bounding box for the
* corresponding pixels in the input image. * corresponding pixels in the input image.
*/ */
void void
im__transform_invert_rect( const Transformation *trn, vips__transform_invert_rect( const VipsTransformation *trn,
const Rect *in, /* In output space */ const Rect *in, /* In output space */
Rect *out ) /* In input space */ Rect *out ) /* In input space */
{ {
transform_rect( trn, im__transform_invert_point, in, out ); transform_rect( trn, vips__transform_invert_point, in, out );
} }
/* Set output area of trn so that it just holds all of our input pels. /* Set output area of trn so that it just holds all of our input pels.
*/ */
void void
im__transform_set_area( Transformation *trn ) vips__transform_set_area( VipsTransformation *trn )
{ {
im__transform_forward_rect( trn, &trn->iarea, &trn->oarea ); vips__transform_forward_rect( trn, &trn->iarea, &trn->oarea );
} }