improve direct path for flood

This commit is contained in:
John Cupitt 2014-02-17 19:42:58 +00:00
parent c5a48effa2
commit 36c6654374
3 changed files with 213 additions and 197 deletions

19
TODO
View File

@ -1,22 +1,3 @@
- break vipsflood into a separate struct we can use directly
same for draw_mask
- it was quick ..
$ time vips-7.38 vips im_label_regions ~/pics/babe.jpg x.v
196640
real 0m0.189s
user 0m0.173s
sys 0m0.031s
best now:
$ time vips labelregions babe.jpg x.v
real 0m3.951s
user 0m3.940s
sys 0m0.032s
- ink to vec etc. should work for complex .. output or accept a double-length
vector

View File

@ -100,39 +100,58 @@ typedef struct _Buffer {
Scan scan[PBUFSIZE];
} Buffer;
/* Base class.
/* What we track during a flood. We have this in a separet struct so that we
* can support vips__draw_flood_direct() ... a fast path for
* vips_labelregions() taht avoids all of the GObject call overhead. This
* gives a huge speedup, >x10 in many cases.
*/
typedef struct _VipsDrawFlood {
VipsDrawink parent_object;
/* Parameters.
typedef struct _Flood {
/* Test pixels here.
*/
int x;
int y;
VipsImage *test; /* Test this image */
gboolean equal; /* Fill to == edge, or != edge */
VipsImage *test;
int left; /* Record bounding box of modified pixels */
/* Draw pixels here, can be equal to test.
*/
VipsImage *image;
/* Sizeof pel in test.
*/
int tsize;
/* Pixel we compare test to for edges.
*/
VipsPel *edge;
/* True for flood while test == edge, false for flood while test !=
* edge.
*/
gboolean equal;
/* Sizeof pel in @image.
*/
int psize;
/* Ink we write to @image.
*/
VipsPel *ink;
/* Add to move down a line in @image.
*/
int lsize;
/* Record bounding box of modified pixels.
*/
int left;
int right;
int top;
int bottom;
int width;
int height;
/* Derived stuff.
*/
VipsPel *edge; /* Boundary colour */
int tsize; /* sizeof( one pel in test ) */
/* Read from in, add new possibilities to out.
/* Our todo list.
*/
Buffer *in;
Buffer *out;
} VipsDrawFlood;
typedef VipsDrawinkClass VipsDrawFloodClass;
G_DEFINE_TYPE( VipsDrawFlood, vips_draw_flood, VIPS_TYPE_DRAWINK );
} Flood;
/* Alloc a new buffer.
*/
@ -166,7 +185,7 @@ buffer_free( Buffer *buf )
* the new head buffer.
*/
static inline Buffer *
buffer_add( Buffer *buf, VipsDrawFlood *flood, int x1, int x2, int y, int dir )
buffer_add( Buffer *buf, Flood *flood, int x1, int x2, int y, int dir )
{
/* Clip against image size.
*/
@ -197,15 +216,15 @@ buffer_add( Buffer *buf, VipsDrawFlood *flood, int x1, int x2, int y, int dir )
/* Is p "connected"? ie. is equal to or not equal to flood->edge, depending on
* whether we are flooding to the edge boundary or flooding edge-coloured
* pixels.
* pixels. p is a pel in @test.
*/
static inline gboolean
vips_draw_flood_connected( VipsDrawFlood *flood, VipsPel *tp )
flood_connected( Flood *flood, VipsPel *p )
{
int j;
for( j = 0; j < flood->tsize; j++ )
if( tp[j] != flood->edge[j] )
if( p[j] != flood->edge[j] )
break;
/* If flood->equal, true if point == edge.
@ -213,49 +232,104 @@ vips_draw_flood_connected( VipsDrawFlood *flood, VipsPel *tp )
return( flood->equal ^ (j < flood->tsize) );
}
/* Is p painted? p is a pel in @image.
*/
static inline gboolean
flood_painted( Flood *flood, VipsPel *p )
{
int j;
for( j = 0; j < flood->psize; j++ )
if( p[j] != flood->ink[j] )
break;
return( j == flood->psize );
}
static inline void
flood_pel( Flood *flood, VipsPel *q )
{
int j;
/* Faster than memcopy() for n < about 20.
*/
for( j = 0; j < flood->psize; j++ )
q[j] = flood->ink[j];
}
/* Fill a scanline between points x1 and x2 inclusive. x1 < x2.
*/
static void
flood_draw_scanline( Flood *flood, int y, int x1, int x2 )
{
VipsPel *p;
int i;
int len;
g_assert( x1 <= x2 );
if( y < 0 ||
y >= flood->image->Ysize )
return;
if( x1 < 0 &&
x2 < 0 )
return;
if( x1 >= flood->image->Xsize &&
x2 >= flood->image->Xsize )
return;
x1 = VIPS_CLIP( 0, x1, flood->image->Xsize - 1 );
x2 = VIPS_CLIP( 0, x2, flood->image->Xsize - 1 );
p = VIPS_IMAGE_ADDR( flood->image, x1, y );
len = x2 - x1 + 1;
for( i = 0; i < len; i++ ) {
flood_pel( flood, p );
p += flood->psize;
}
}
/* Fill left and right, return the endpoints. The start point (x, y) must be
* connected and unpainted.
*/
static void
vips_draw_flood_scanline( VipsDrawFlood *flood, int x, int y, int *x1, int *x2 )
flood_scanline( Flood *flood, int x, int y, int *x1, int *x2 )
{
VipsDraw *draw = VIPS_DRAW( flood );
VipsDrawink *drawink = VIPS_DRAWINK( flood );
const int width = flood->test->Xsize;
VipsPel *tp;
VipsPel *p;
int i;
g_assert( vips_draw_flood_connected( flood,
g_assert( flood_connected( flood,
VIPS_IMAGE_ADDR( flood->test, x, y ) ) );
g_assert( !vips__drawink_painted( drawink,
VIPS_IMAGE_ADDR( draw->image, x, y ) ) );
g_assert( !flood_painted( flood,
VIPS_IMAGE_ADDR( flood->image, x, y ) ) );
/* Search to the right for the first non-connected pixel. If the start
* pixel is unpainted, we know all the intervening pixels must be
* unpainted too.
*/
tp = VIPS_IMAGE_ADDR( flood->test, x + 1, y );
p = VIPS_IMAGE_ADDR( flood->test, x + 1, y );
for( i = x + 1; i < width; i++ ) {
if( !vips_draw_flood_connected( flood, tp ) )
if( !flood_connected( flood, p ) )
break;
tp += flood->tsize;
p += flood->tsize;
}
*x2 = i - 1;
/* Search left.
*/
tp = VIPS_IMAGE_ADDR( flood->test, x - 1, y );
p = VIPS_IMAGE_ADDR( flood->test, x - 1, y );
for( i = x - 1; i >= 0; i-- ) {
if( !vips_draw_flood_connected( flood, tp ) )
if( !flood_connected( flood, p ) )
break;
tp -= flood->tsize;
p -= flood->tsize;
}
*x1 = i + 1;
/* Paint the range we discovered.
*/
vips__drawink_scanline( drawink, y, *x1, *x2 );
flood_draw_scanline( flood, y, *x1, *x2 );
flood->left = VIPS_MIN( flood->left, *x1 );
flood->right = VIPS_MAX( flood->right, *x2 );
@ -267,22 +341,19 @@ vips_draw_flood_scanline( VipsDrawFlood *flood, int x, int y, int *x1, int *x2 )
* line in this range looking for an edge pixel we can flood from.
*/
static void
vips_draw_flood_around( VipsDrawFlood *flood, Scan *scan )
flood_around( Flood *flood, Scan *scan )
{
VipsDraw *draw = VIPS_DRAW( flood );
VipsDrawink *drawink = VIPS_DRAWINK( flood );
VipsPel *tp;
VipsPel *p;
int x;
g_assert( scan->dir == 1 ||
scan->dir == -1 );
for( tp = VIPS_IMAGE_ADDR( flood->test, scan->x1, scan->y ),
for( p = VIPS_IMAGE_ADDR( flood->test, scan->x1, scan->y ),
x = scan->x1;
x <= scan->x2;
tp += flood->tsize, x++ ) {
if( vips_draw_flood_connected( flood, tp ) ) {
p += flood->tsize, x++ ) {
if( flood_connected( flood, p ) ) {
int x1a;
int x2a;
@ -290,16 +361,15 @@ vips_draw_flood_around( VipsDrawFlood *flood, Scan *scan )
* to check for painted. Otherwise we can get stuck in
* connected loops.
*/
if( draw->image != flood->test ) {
if( flood->image != flood->test ) {
VipsPel *mp = VIPS_IMAGE_ADDR(
draw->image, x, scan->y );
flood->image, x, scan->y );
if( vips__drawink_painted( drawink, mp ) )
if( flood_painted( flood, mp ) )
continue;
}
vips_draw_flood_scanline( flood,
x, scan->y, &x1a, &x2a );
flood_scanline( flood, x, scan->y, &x1a, &x2a );
/* Our new scanline can have up to three more
* scanlines connected to it: above, below left, below
@ -318,25 +388,27 @@ vips_draw_flood_around( VipsDrawFlood *flood, Scan *scan )
scan->dir );
x = x2a + 1;
tp = VIPS_IMAGE_ADDR( flood->test, x, scan->y );
p = VIPS_IMAGE_ADDR( flood->test, x, scan->y );
}
}
}
static void
vips_draw_flood_all( VipsDrawFlood *flood )
flood_all( Flood *flood, int x, int y )
{
int x1, x2;
/* Test start pixel ... nothing to do?
*/
if( !vips_draw_flood_connected( flood,
VIPS_IMAGE_ADDR( flood->test, flood->x, flood->y ) ) )
if( !flood_connected( flood, VIPS_IMAGE_ADDR( flood->test, x, y ) ) )
return;
vips_draw_flood_scanline( flood, flood->x, flood->y, &x1, &x2 );
flood->in = buffer_add( flood->in, flood, x1, x2, flood->y + 1, 1 );
flood->in = buffer_add( flood->in, flood, x1, x2, flood->y - 1, -1 );
flood->in = buffer_build();
flood->out = buffer_build();
flood_scanline( flood, x, y, &x1, &x2 );
flood->in = buffer_add( flood->in, flood, x1, x2, y + 1, 1 );
flood->in = buffer_add( flood->in, flood, x1, x2, y - 1, -1 );
while( flood->in->n ) {
Buffer *p;
@ -345,34 +417,49 @@ vips_draw_flood_all( VipsDrawFlood *flood )
int i;
for( i = 0; i < p->n; i++ )
vips_draw_flood_around( flood, &p->scan[i] );
flood_around( flood, &p->scan[i] );
p->n = 0;
}
VIPS_SWAP( Buffer *, flood->in, flood->out );
}
}
static void
vips_draw_flood_dispose( GObject *gobject )
{
VipsDrawFlood *flood = (VipsDrawFlood *) gobject;
VIPS_FREEF( buffer_free, flood->in );
VIPS_FREEF( buffer_free, flood->out );
G_OBJECT_CLASS( vips_draw_flood_parent_class )->dispose( gobject );
}
/* Base class.
*/
typedef struct _VipsDrawFlood {
VipsDrawink parent_object;
/* Parameters.
*/
int x;
int y;
VipsImage *test;
gboolean equal;
int left;
int top;
int width;
int height;
} VipsDrawFlood;
typedef VipsDrawinkClass VipsDrawFloodClass;
G_DEFINE_TYPE( VipsDrawFlood, vips_draw_flood, VIPS_TYPE_DRAWINK );
static int
vips_draw_flood_build( VipsObject *object )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsDraw *draw = VIPS_DRAW( object );
VipsDrawink *drawink = VIPS_DRAWINK( object );
VipsDrawFlood *flood = (VipsDrawFlood *) object;
VipsDrawFlood *drawflood = (VipsDrawFlood *) object;
Flood flood;
int j;
if( VIPS_OBJECT_CLASS( vips_draw_flood_parent_class )->build( object ) )
@ -383,60 +470,62 @@ vips_draw_flood_build( VipsObject *object )
if( !vips_object_argument_isset( object, "test" ) )
g_object_set( object, "test", draw->image, NULL );
flood->tsize = VIPS_IMAGE_SIZEOF_PEL( flood->test );
flood->left = flood->x;
flood->top = flood->y;
flood->right = flood->x;
flood->bottom = flood->y;
flood->in = buffer_build();
flood->out = buffer_build();
if( vips_image_wio_input( flood->test ) ||
vips_check_coding_known( class->nickname, flood->test ) ||
if( vips_image_wio_input( drawflood->test ) ||
vips_check_coding_known( class->nickname, drawflood->test ) ||
vips_check_size_same( class->nickname,
flood->test, draw->image ) )
drawflood->test, draw->image ) )
return( -1 );
if( flood->equal ) {
flood.test = drawflood->test;
flood.image = draw->image;
flood.equal = drawflood->equal;
flood.tsize = VIPS_IMAGE_SIZEOF_PEL( drawflood->test );
flood.left = drawflood->x;
flood.top = drawflood->y;
flood.right = drawflood->x;
flood.bottom = drawflood->y;
if( flood.equal ) {
/* Edge is set by colour of the start pixel in @test.
*/
if( !(flood->edge =
(VipsPel *) im_malloc( object, flood->tsize )) )
if( !(flood.edge =
(VipsPel *) im_malloc( object, flood.tsize )) )
return( -1 );
memcpy( flood->edge,
VIPS_IMAGE_ADDR( flood->test, flood->x, flood->y ),
flood->tsize );
memcpy( flood.edge,
VIPS_IMAGE_ADDR( flood.test,
drawflood->x, drawflood->y ),
flood.tsize );
/* If @test and @image are the same and edge == ink, we'll
* never stop :-( or rather, there's nothing to do.
*/
if( flood->test == draw->image ) {
for( j = 0; j < flood->tsize; j++ )
if( flood->edge[j] != drawink->pixel_ink[j] )
if( flood.test == flood.image ) {
for( j = 0; j < flood.tsize; j++ )
if( flood.edge[j] != drawink->pixel_ink[j] )
break;
if( j != flood->tsize )
vips_draw_flood_all( flood );
if( j != flood.tsize )
flood_all( &flood, drawflood->x, drawflood->y );
}
else
vips_draw_flood_all( flood );
flood_all( &flood, drawflood->x, drawflood->y );
}
else {
/* Flood to ink colour. We need to be able to compare @test to
* @ink.
*/
if( !(flood->edge = vips__vector_to_ink( class->nickname,
flood->test, drawink->ink->data, drawink->ink->n )) )
if( !(flood.edge = vips__vector_to_ink( class->nickname,
flood.test, drawink->ink->data, drawink->ink->n )) )
return( -1 );
vips_draw_flood_all( flood );
flood_all( &flood, drawflood->x, drawflood->y );
}
g_object_set( object,
"left", flood->left,
"top", flood->top,
"width", flood->right - flood->left + 1,
"height", flood->bottom - flood->top + 1,
"left", flood.left,
"top", flood.top,
"width", flood.right - flood.left + 1,
"height", flood.bottom - flood.top + 1,
NULL );
return( 0 );
@ -448,7 +537,6 @@ vips_draw_flood_class_init( VipsDrawFloodClass *class )
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
gobject_class->dispose = vips_draw_flood_dispose;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
@ -534,55 +622,36 @@ int
vips__draw_flood_direct( VipsImage *image, VipsImage *test,
int serial, int x, int y )
{
VipsDrawFlood *flood = (VipsDrawFlood *)
vips_operation_new( "draw_flood" );
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( flood );
Flood flood;
if( vips_check_format( class->nickname, image, VIPS_FORMAT_INT ) ||
vips_check_mono( class->nickname, image ) ||
vips_check_coding_known( class->nickname, test ) ||
vips_check_size_same( class->nickname, test, image ) ||
vips_image_wio_input( test ) ) {
g_object_unref( flood );
if( vips_check_format( "vips__draw_flood_direct",
image, VIPS_FORMAT_INT ) ||
vips_check_mono( "vips__draw_flood_direct", image ) ||
vips_check_coding_known( "vips__draw_flood_direct", test ) ||
vips_check_size_same( "vips__draw_flood_direct",
test, image ) ||
vips_image_wio_input( test ) )
return( -1 );
}
((VipsDraw *) flood)->image = image;
g_object_ref( image );
((VipsDraw *) flood)->lsize = VIPS_IMAGE_SIZEOF_LINE( image );
((VipsDraw *) flood)->psize = VIPS_IMAGE_SIZEOF_PEL( image );
flood.image = image;
flood.test = test;
flood.lsize = VIPS_IMAGE_SIZEOF_LINE( image );
flood.psize = VIPS_IMAGE_SIZEOF_PEL( image );
flood.ink = (VipsPel *) &serial;
flood.equal = TRUE;
flood.tsize = VIPS_IMAGE_SIZEOF_PEL( test );
flood.left = x;
flood.top = y;
flood.right = x;
flood.bottom = y;
if( !(((VipsDrawink *) flood)->pixel_ink = VIPS_ARRAY( flood,
VIPS_IMAGE_SIZEOF_PEL( image ), VipsPel )) ) {
g_object_unref( flood );
return( -1 );
}
*((int *) (((VipsDrawink *) flood)->pixel_ink)) = serial;
flood->x = x;
flood->y = y;
flood->test = test;
g_object_ref( test );
flood->equal = TRUE;
flood->tsize = VIPS_IMAGE_SIZEOF_PEL( test );
flood->left = x;
flood->top = y;
flood->right = x;
flood->bottom = y;
flood->in = buffer_build();
flood->out = buffer_build();
if( !(flood->edge =
(VipsPel *) im_malloc( flood, flood->tsize )) ) {
g_object_unref( flood );
if( !(flood.edge =
(VipsPel *) im_malloc( image, flood.tsize )) )
return( -1 );
}
memcpy( flood->edge,
VIPS_IMAGE_ADDR( test, x, y ), flood->tsize );
memcpy( flood.edge,
VIPS_IMAGE_ADDR( test, x, y ), flood.tsize );
vips_draw_flood_all( flood );
g_object_unref( flood );
flood_all( &flood, x, y );
return( 0 );
}

View File

@ -102,37 +102,3 @@ vips_drawink_init( VipsDrawink *drawink )
((double *) (drawink->ink->data))[0] = 0;
}
/* Fill a scanline between points x1 and x2 inclusive. x1 < x2.
*/
void
vips__drawink_scanline( VipsDrawink *drawink, int y, int x1, int x2 )
{
VipsDraw *draw = (VipsDraw *) drawink;
VipsPel *mp;
int i;
int len;
g_assert( x1 <= x2 );
if( y < 0 ||
y >= draw->image->Ysize )
return;
if( x1 < 0 &&
x2 < 0 )
return;
if( x1 >= draw->image->Xsize &&
x2 >= draw->image->Xsize )
return;
x1 = VIPS_CLIP( 0, x1, draw->image->Xsize - 1 );
x2 = VIPS_CLIP( 0, x2, draw->image->Xsize - 1 );
mp = VIPS_IMAGE_ADDR( draw->image, x1, y );
len = x2 - x1 + 1;
for( i = 0; i < len; i++ ) {
vips__drawink_pel( drawink, mp );
mp += draw->psize;
}
}