From d71b6b754a1003bde848bfb3efaefc5f5fcb35a0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 17 Dec 2009 11:44:28 +0000 Subject: [PATCH] fix up flood --- ChangeLog | 1 + TODO | 9 +- libvips/inplace/im_flood.c | 258 ++++++++++++++++++++++++------------- 3 files changed, 167 insertions(+), 101 deletions(-) diff --git a/ChangeLog b/ChangeLog index 33d652a8..bc5f2171 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,7 @@ - im_disp2Lab() was broken - deprecated.h is now defined in terms of current functionality, rather than repeating stuff +- im_flood() uses inline 26/11/09 started 7.20.3 - updated en_GB.po translation diff --git a/TODO b/TODO index ae6aa6e3..37883f64 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,9 @@ -- some of deprecated.h repeats stuff we do better in image.h, eg. addr() - should just call IM_REGION_ADDR() +- add a --without-cpp option? - rename vipsCC in SWIG as pyvips? - im_flood*() should use inline rather than #define -- try the new liboil thing: - - http://code.entropywave.com/projects/orc/ - - pick something like abs and time it - - look into G_GNUC_DEPRECATED for back compat in vips8 - use diff --git a/libvips/inplace/im_flood.c b/libvips/inplace/im_flood.c index 85691a7e..b80c81d5 100644 --- a/libvips/inplace/im_flood.c +++ b/libvips/inplace/im_flood.c @@ -1,8 +1,4 @@ -/* int im_flood( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) - * - * Flood fill from point (x,y) with colour ink. Flood up to boundary == ink. - * Any type, any number of bands, IM_CODING_LABQ too. Returns the bounding box - * of the modified pixels in dout, whether it succeeds or not. +/* flood-fill * * Currently a rather inefficient pixel-based algorithm, should put something * better in, really. Speed isn't likely to be a problem, except for very @@ -22,6 +18,10 @@ * - added IM_CODING_RAD support * 28/9/09 * - ooops, tiny memleak + * 17/12/09 + * - use inline rather than defines, so we can add scanline fills more + * easily + * - gtk-doc comments */ /* @@ -114,7 +114,7 @@ typedef struct { /* Alloc a new buffer. */ static Buffer * -build_buffer( void ) +buffer_build( void ) { Buffer *buf = IM_NEW( NULL, Buffer ); @@ -129,16 +129,71 @@ build_buffer( void ) /* Free a chain of buffers. */ static void -free_buffer( Buffer *buf ) +buffer_free( Buffer *buf ) { IM_FREE( buf->next ); im_free( buf ); } +/* Add an xy point to a buffer, appending a new buffer if necessary. Return + * the new tail buffer. + */ +static inline Buffer * +buffer_add( Buffer *buf, int x, int y ) +{ + /* buf can be NULL if we've had an error. + */ + if( !buf ) + return( NULL ); + + buf->points[buf->n].x = x; + buf->points[buf->n].y = y; + buf->n++; + + if( buf->n == PBUFSIZE ) { + if( !buf->next ) { + if( !(buf->next = buffer_build()) ) + return( NULL ); + } + buf = buf->next; + buf->n = 0; + } + + return( buf ); +} + +static inline Buffer * +buffer_add_ifnotedge( Buffer *buf, State *st, PEL *p, int x, int y ) +{ + int j; + + for( j = 0; j < st->ps; j++ ) + if( p[j] != st->edge[j] ) { + buf = buffer_add( buf, x, y ); + break; + } + + return( buf ); +} + +static inline Buffer * +buffer_add_ifedge( Buffer *buf, State *st, PEL *p, int x, int y ) +{ + int j; + + for( j = 0; j < st->ps; j++ ) + if( p[j] != st->edge[j] ) + break; + if( j == st->ps ) + buf = buffer_add( buf, x, y ); + + return( buf ); +} + /* Free a state. */ static void -free_state( State *st ) +state_free( State *st ) { /* Write dirty back to caller. */ @@ -149,15 +204,15 @@ free_state( State *st ) */ IM_FREE( st->ink ); IM_FREE( st->edge ); - IM_FREEF( free_buffer, st->buf1 ); - IM_FREEF( free_buffer, st->buf2 ); + IM_FREEF( buffer_free, st->buf1 ); + IM_FREEF( buffer_free, st->buf2 ); im_free( st ); } /* Build a state. */ static State * -build_state( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) +state_build( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) { State *st = IM_NEW( NULL, State ); @@ -184,9 +239,9 @@ build_state( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) if( !(st->ink = (PEL *) im_malloc( NULL, st->ps )) || !(st->edge = (PEL *) im_malloc( NULL, st->ps )) || - !(st->buf1 = build_buffer()) || - !(st->buf2 = build_buffer()) ) { - free_state( st ); + !(st->buf1 = buffer_build()) || + !(st->buf2 = buffer_build()) ) { + state_free( st ); return( NULL ); } memcpy( st->ink, ink, st->ps ); @@ -194,46 +249,6 @@ build_state( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) return( st ); } -/* Add xy to buffer, move buffer on on overflow. - */ -#define ADD( BUF, X, Y ) { \ - BUF->points[BUF->n].x = X; \ - BUF->points[BUF->n].y = Y; \ - BUF->n++; \ - if( BUF->n == PBUFSIZE ) { \ - if( !BUF->next ) { \ - if( !(BUF->next = build_buffer()) ) \ - return( -1 ); \ - } \ - BUF = BUF->next; \ - BUF->n = 0; \ - } \ -} - -/* If point != edge, add it to out. - */ -#define ADDIFNOTEDGE( P, X, Y ) { \ - PEL *p1 = (P); \ - \ - for( j = 0; j < st->ps; j++ ) \ - if( p1[j] != st->edge[j] ) { \ - ADD( out, X, Y ); \ - break; \ - } \ -} - -/* If point == edge, add it to out. - */ -#define ADDIFEDGE( P, X, Y ) { \ - PEL *p1 = (P); \ - \ - for( j = 0; j < st->ps; j++ ) \ - if( p1[j] != st->edge[j] ) \ - break; \ - if( j == st->ps ) \ - ADD( out, X, Y ); \ -} - /* Read points to fill from in, write new points to out. */ static int @@ -291,24 +306,37 @@ dofill( State *st, Buffer *in, Buffer *out ) */ if( st->equal ) { if( x < st->right - 1 ) - ADDIFEDGE( p + st->ps, x + 1, y ); + out = buffer_add_ifedge( out, st, + p + st->ps, x + 1, y ); if( x > st->left ) - ADDIFEDGE( p - st->ps, x - 1, y ); + out = buffer_add_ifedge( out, st, + p - st->ps, x - 1, y ); if( y < st->bottom - 1 ) - ADDIFEDGE( p + st->ls, x, y + 1 ); + out = buffer_add_ifedge( out, st, + p + st->ls, x, y + 1 ); if( y > st->top ) - ADDIFEDGE( p - st->ls, x, y - 1 ); + out = buffer_add_ifedge( out, st, + p - st->ls, x, y - 1 ); } else { if( x < st->right - 1 ) - ADDIFNOTEDGE( p + st->ps, x + 1, y ); + out = buffer_add_ifnotedge( out, st, + p + st->ps, x + 1, y ); if( x > st->left ) - ADDIFNOTEDGE( p - st->ps, x - 1, y ); + out = buffer_add_ifnotedge( out, st, + p - st->ps, x - 1, y ); if( y < st->bottom - 1 ) - ADDIFNOTEDGE( p + st->ls, x, y + 1 ); + out = buffer_add_ifnotedge( out, st, + p + st->ls, x, y + 1 ); if( y > st->top ) - ADDIFNOTEDGE( p - st->ls, x, y - 1 ); + out = buffer_add_ifnotedge( out, st, + p - st->ls, x, y - 1 ); } + + /* There was an error in one of the adds. + */ + if( !out ) + return( -1 ); } if( in->n == PBUFSIZE ) @@ -322,6 +350,28 @@ dofill( State *st, Buffer *in, Buffer *out ) return( 0 ); } +/** + * im_flood: + * @im: image to fill + * @x: position to start fill + * @y: position to start fill + * @ink: colour to fill with + * @dout: output the bounding box of the filled area + * + * Flood-fill @im with @ink, starting at position @x, @y. The filled area is + * bounded by pixels that are equal to the ink colour, in other words, it + * searches for pixels enclosed by a line of @ink. + * + * The bounding box of the modified pixels is returned in @dout. + * + * This an inplace operation, so @im is changed. It does not thread and will + * not work well as part of a pipeline. On 32-bit machines, it will be limited + * to 2GB images. + * + * See also: im_flood_blob(), im_flood_other(), im_flood_blob_copy(). + * + * Returns: 0 on success, or -1 on error. + */ int im_flood( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) { @@ -329,23 +379,16 @@ im_flood( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) Buffer *in, *out, *t; PEL *p; - if( im_rwcheck( im ) ) - return( -1 ); - if( im->Coding != IM_CODING_NONE && - im->Coding != IM_CODING_LABQ && - im->Coding != IM_CODING_RAD ) { - im_error( "im_flood", "%s", - _( "Coding should be NONE, LABQ or RAD" ) ); - return( -1 ); - } - if( !(st = build_state( im, x, y, ink, dout )) ) + if( im_rwcheck( im ) || + im_check_known_coded( "im_flood", im ) ) + if( !(st = state_build( im, x, y, ink, dout )) ) return( -1 ); /* Test start pixel ... nothing to do? */ p = (PEL *) im->data + x*st->ps + y*st->ls; if( memcmp( p, ink, st->ps ) == 0 ) { - free_state( st ); + state_free( st ); return( 0 ); } @@ -356,21 +399,43 @@ im_flood( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) /* Add start pixel to the work buffer, and loop. */ - ADD( st->buf1, x, y ) + st->buf1 = buffer_add( st->buf1, x, y ); for( in = st->buf1, out = st->buf2; in->n > 0; t = in, in = out, out = t ) if( dofill( st, in, out ) ) { - free_state( st ); + state_free( st ); return( -1 ); } - - free_state( st ); + + state_free( st ); im_invalidate( im ); return( 0 ); } +/** + * im_flood_blob: + * @im: image to fill + * @x: position to start fill + * @y: position to start fill + * @ink: colour to fill with + * @dout: output the bounding box of the filled area + * + * Flood-fill @im with @ink, starting at position @x, @y. The filled area is + * bounded by pixels that are not equal to the colour of the start pixel, in + * other words, it searches for a blob of connected pixels of the same colour. + * + * The bounding box of the modified pixels is returned in @dout. + * + * This an inplace operation, so @im is changed. It does not thread and will + * not work well as part of a pipeline. On 32-bit machines, it will be limited + * to 2GB images. + * + * See also: im_flood(), im_flood_other(), im_flood_blob_copy(). + * + * Returns: 0 on success, or -1 on error. + */ int im_flood_blob( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) { @@ -378,16 +443,10 @@ im_flood_blob( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) Buffer *in, *out, *t; PEL *p; - if( im_rwcheck( im ) ) + if( im_rwcheck( im ) || + im_check_known_coded( "im_flood", im ) ) return( -1 ); - if( im->Coding != IM_CODING_NONE && - im->Coding != IM_CODING_LABQ && - im->Coding != IM_CODING_RAD ) { - im_error( "im_flood", "%s", - _( "Coding should be NONE, LABQ or RAD" ) ); - return( -1 ); - } - if( !(st = build_state( im, x, y, ink, dout )) ) + if( !(st = state_build( im, x, y, ink, dout )) ) return( -1 ); /* Edge is set by colour of start pixel. @@ -398,23 +457,38 @@ im_flood_blob( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) /* Add start pixel to the work buffer, and loop. */ - ADD( st->buf1, x, y ) + st->buf1 = buffer_add( st->buf1, x, y ); for( in = st->buf1, out = st->buf2; in->n > 0; t = in, in = out, out = t ) if( dofill( st, in, out ) ) { - free_state( st ); + state_free( st ); return( -1 ); } - free_state( st ); + state_free( st ); im_invalidate( im ); return( 0 ); } -/* A Flood blob we can call from nip. Grr! Should be a way to wrap these - * automatically. Maybe nip could do it if it sees a RW image argument? +/** + * im_flood_blob_copy: + * @in: input image + * @out: output image + * @x: position to start fill + * @y: position to start fill + * @ink: colour to fill with + * + * Copy @in to a otemporary memory buffer, then flood-fill with @ink, + * starting at position @x, @y. The filled area is + * bounded by pixels that are not equal to the colour of the start pixel, in + * other words, it searches for a blob of connected pixels of the same colour. + * The temporary image is then copied to @out. + * + * See also: im_flood(), im_flood_other(), im_flood_blob_copy(). + * + * Returns: 0 on success, or -1 on error. */ int im_flood_blob_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) @@ -429,5 +503,3 @@ im_flood_blob_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) return( 0 ); } - -