From cb888b9765aba58d7e429a88fee3188ccbddc046 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 27 Dec 2009 16:40:54 +0000 Subject: [PATCH] new flood --- ChangeLog | 2 +- TODO | 2 - libvips/include/vips/inplace.h | 16 +- libvips/inplace/Makefile.am | 2 +- libvips/inplace/flood.c | 572 ++++++++++++++++++++++++++ libvips/inplace/im_flood.c | 27 +- libvips/inplace/im_flood_new.c | 451 -------------------- libvips/inplace/im_flood_other.c | 6 +- libvips/inplace/inplace_dispatch.c | 74 +++- libvips/morphology/im_label_regions.c | 3 +- 10 files changed, 676 insertions(+), 479 deletions(-) create mode 100644 libvips/inplace/flood.c delete mode 100644 libvips/inplace/im_flood_new.c diff --git a/ChangeLog b/ChangeLog index bc5f2171..739419a0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,7 +9,7 @@ - im_disp2Lab() was broken - deprecated.h is now defined in terms of current functionality, rather than repeating stuff -- im_flood() uses inline +- im_flood() and friends rewritten, typically 4x faster 26/11/09 started 7.20.3 - updated en_GB.po translation diff --git a/TODO b/TODO index 37883f64..eed3a20c 100644 --- a/TODO +++ b/TODO @@ -2,8 +2,6 @@ - rename vipsCC in SWIG as pyvips? -- im_flood*() should use inline rather than #define - - look into G_GNUC_DEPRECATED for back compat in vips8 - use diff --git a/libvips/include/vips/inplace.h b/libvips/include/vips/inplace.h index d292c651..8f4c6855 100644 --- a/libvips/include/vips/inplace.h +++ b/libvips/include/vips/inplace.h @@ -48,16 +48,28 @@ int im_fastlineuser( IMAGE *im, int x1, int y1, int x2, int y2, int (*fn)(), void *client1, void *client2, void *client3 ); int im_readpoint( IMAGE *im, int x, int y, PEL *pel ); + int im_flood( IMAGE *im, int x, int y, PEL *ink, Rect *dout ); int im_flood_blob( IMAGE *im, int x, int y, PEL *ink, Rect *dout ); +int im_flood_other( IMAGE *test, IMAGE *mark, + int x, int y, int serial, Rect *dout ); + +int im_flood_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ); int im_flood_blob_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ); -int im_flood_other( IMAGE *mask, IMAGE *test, int x, int y, int serial ); -int im_flood_other_copy( IMAGE *mask, IMAGE *test, IMAGE *out, +int im_flood_other_copy( IMAGE *test, IMAGE *mark, IMAGE *out, int x, int y, int serial ); int im_lineset( IMAGE *in, IMAGE *out, IMAGE *mask, IMAGE *ink, int n, int *x1v, int *y1v, int *x2v, int *y2v ); +int im_flood_old( IMAGE *im, int x, int y, PEL *ink, Rect *dout ); +int im_flood_blob_old( IMAGE *im, int x, int y, PEL *ink, Rect *dout ); +int im_flood_other_old( IMAGE *mask, IMAGE *test, int x, int y, int serial ); +int im_flood_copy_old( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ); +int im_flood_blob_copy_old( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ); +int im_flood_other_copy_old( IMAGE *mask, IMAGE *test, IMAGE *out, + int x, int y, int serial ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/inplace/Makefile.am b/libvips/inplace/Makefile.am index 6fd184c0..49dc0d97 100644 --- a/libvips/inplace/Makefile.am +++ b/libvips/inplace/Makefile.am @@ -4,7 +4,7 @@ libinplace_la_SOURCES = \ im_circle.c \ im_flood.c \ im_flood_other.c \ - im_flood_new.c \ + flood.c \ im_insertplace.c \ im_paintrect.c \ im_plotmask.c \ diff --git a/libvips/inplace/flood.c b/libvips/inplace/flood.c new file mode 100644 index 00000000..e0d75aa9 --- /dev/null +++ b/libvips/inplace/flood.c @@ -0,0 +1,572 @@ +/* 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 + * large images. + * + * JC 30/8/97 + * - VIPSified, cleaned up, from "John Robinson's prog to fill + * enclosed areas" + * - something Kirk gave me, so thanks John + * JC 1/10/97 + * - swapped inner memcmp/cpy for a loop ... faster for small pixels + * 13/7/02 JC + * - im_flood_blob() added + * 5/12/06 + * - im_invalidate() after paint + * 24/3/09 + * - 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 + * 21/12/09 + * - rewrite for a scanline based fill, 4x faster! + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +#define SWAP( TYPE, A, B ) { \ + TYPE t = (A); \ + (A) = (B); \ + (B) = t; \ +} + +/* Size of a scanline buffer. We allocate a list of these to hold scanlines + * we need to visit. + */ +#define PBUFSIZE (1000) + +/* A scanline we know could contain pixels connected to us. + * + * Dir is the direction of connection: +1 means y is increasing, ie. the line + * above this one is filled. -1 if y is decreasing. + */ +typedef struct { + int x1, x2; + int y; + int dir; +} Scan; + +/* A buffer of scanlines, and how many of them have been used. If ->next is + * non-NULL, the next block could contain more of them. + * + * We keep a pair of these, then loop over one and write to the other. + */ +typedef struct _Buffer { + struct _Buffer *next; + int n; + Scan scan[PBUFSIZE]; +} Buffer; + +/* Our state. + */ +typedef struct { + /* Parameters. + */ + IMAGE *test; /* Test this image */ + IMAGE *mark; /* Mark this image */ + int x, y; + PEL *ink; /* Copy of ink param */ + Rect *dout; /* Write dirty here at end */ + + /* Derived stuff. + */ + PEL *edge; /* Boundary colour */ + int equal; /* Fill to == edge, or != edge */ + int tsize; /* sizeof( one pel in test ) */ + int msize; /* sizeof( one pel in mark ) */ + int left, right; /* Record bounding box of modified pixels */ + int top, bottom; + + /* Read from in, add new possibilities to out. + */ + Buffer *in; + Buffer *out; +} Flood; + +/* Alloc a new buffer. + */ +static Buffer * +buffer_build( void ) +{ + Buffer *buf = IM_NEW( NULL, Buffer ); + + if( !buf ) + return( NULL ); + buf->next = NULL; + buf->n = 0; + + return( buf ); +} + +/* Free a chain of buffers. + */ +static void +buffer_free( Buffer *buf ) +{ + while( buf ) { + Buffer *p; + + p = buf->next; + im_free( buf ); + buf = p; + } +} + +/* Add a scanline to a buffer, prepending a new buffer if necessary. Return + * the new head buffer. + */ +static inline Buffer * +buffer_add( Buffer *buf, Flood *flood, int x1, int x2, int y, int dir ) +{ + /* Clip against image size. + */ + if( y < 0 || y >= flood->test->Ysize ) + return( buf ); + x1 = IM_CLIP( 0, x1, flood->test->Xsize - 1 ); + x2 = IM_CLIP( 0, x2, flood->test->Xsize - 1 ); + if( x2 - x1 < 0 ) + return( buf ); + + buf->scan[buf->n].x1 = x1; + buf->scan[buf->n].x2 = x2; + buf->scan[buf->n].y = y; + buf->scan[buf->n].dir = dir; + buf->n += 1; + + if( buf->n == PBUFSIZE ) { + Buffer *new; + + if( !(new = buffer_build()) ) + return( NULL ); + new->next = buf; + buf = new; + } + + return( buf ); +} + +/* 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. + */ +static inline gboolean +flood_connected( Flood *flood, PEL *p ) +{ + int j; + + for( j = 0; j < flood->tsize; j++ ) + if( p[j] != flood->edge[j] ) + break; + + return( flood->equal ^ (j < flood->tsize) ); +} + +/* Faster than memcpy for n < about 20. + */ +static inline void +flood_paint( Flood *flood, PEL *q ) +{ + int j; + + for( j = 0; j < flood->msize; j++ ) + q[j] = flood->ink[j]; +} + +/* Fill left and right, return the endpoints. The start point (x, y) must be + * connected. + */ +static void +flood_scanline( Flood *flood, int x, int y, int *x1, int *x2 ) +{ + const int width = flood->mark->Xsize; + + PEL *tp; + PEL *mp; + int i; + + g_assert( flood_connected( flood, + (PEL *) IM_IMAGE_ADDR( flood->test, x, y ) ) ); + + /* Fill this pixel and to the right. + */ + tp = (PEL *) IM_IMAGE_ADDR( flood->test, x, y ); + mp = (PEL *) IM_IMAGE_ADDR( flood->mark, x, y ); + for( i = x; i < width && flood_connected( flood, tp ); i++ ) { + flood_paint( flood, mp ); + tp += flood->tsize; + mp += flood->msize; + } + *x2 = i - 1; + + /* Fill to the left. + */ + tp = (PEL *) IM_IMAGE_ADDR( flood->test, x - 1, y ); + mp = (PEL *) IM_IMAGE_ADDR( flood->mark, x - 1, y ); + for( i = x - 1; i > 0 && flood_connected( flood, tp ); i-- ) { + flood_paint( flood, mp ); + tp -= flood->tsize; + mp -= flood->msize; + } + *x1 = i + 1; + + if( flood->dout ) { + flood->left = IM_MIN( flood->left, *x1 ); + flood->right = IM_MAX( flood->right, *x2 ); + flood->top = IM_MIN( flood->top, y ); + flood->bottom = IM_MAX( flood->bottom, y ); + } +} + +/* We know the line below or above us is filled between x1 and x2. Search our + * line in this range looking for an edge pixel we can flood from. + */ +static void +flood_around( Flood *flood, Scan *scan ) +{ + int x; + + g_assert( scan->dir == 1 || scan->dir == -1 ); + + for( x = scan->x1; x <= scan->x2; x++ ) { + PEL *p = (PEL *) IM_IMAGE_ADDR( flood->test, x, scan->y ); + + if( flood_connected( flood, p ) ) { + int x1a; + int 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 + * right. + */ + if( x1a < scan->x1 - 1 ) + flood->out = buffer_add( flood->out, flood, + x1a, scan->x1 - 1, + scan->y - scan->dir, -scan->dir ); + if( x2a > scan->x2 + 1 ) + flood->out = buffer_add( flood->out, flood, + scan->x2 + 1, x2a, + scan->y - scan->dir, -scan->dir ); + flood->out = buffer_add( flood->out, flood, + x1a, x2a, scan->y + scan->dir, + scan->dir ); + + x = x2a; + } + } +} + +static void +flood_buffer( Flood *flood, Buffer *buf ) +{ + Buffer *p; + + for( p = buf; p; p = p->next ) { + int i; + + for( i = 0; i < p->n; i++ ) + flood_around( flood, &buf->scan[i] ); + + p->n = 0; + } +} + +static void +flood_all( Flood *flood, int x, int y ) +{ + /* Test start pixel ... nothing to do? + */ + if( flood_connected( flood, + (PEL *) IM_IMAGE_ADDR( flood->test, x, y ) ) ) { + int x1, x2; + + 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 ) { + flood_buffer( flood, flood->in ); + + SWAP( Buffer *, flood->in, flood->out ); + } + } +} + +static void +flood_free( Flood *flood ) +{ + /* Write dirty back to caller. + */ + if( flood->dout ) { + flood->dout->left = flood->left; + flood->dout->top = flood->top; + flood->dout->width = flood->right - flood->left + 1; + flood->dout->height = flood->bottom - flood->top + 1; + } + + IM_FREE( flood->ink ); + IM_FREE( flood->edge ); + IM_FREEF( buffer_free, flood->in ); + IM_FREEF( buffer_free, flood->out ); + im_free( flood ); +} + +static Flood * +flood_build( IMAGE *test, IMAGE *mark, int x, int y, PEL *ink, Rect *dout ) +{ + Flood *flood = IM_NEW( NULL, Flood ); + + if( !flood ) + return( NULL ); + flood->test = test; + flood->mark = mark; + flood->x = x; + flood->y = y; + flood->ink = NULL; + flood->dout = dout; + flood->edge = NULL; + flood->tsize = IM_IMAGE_SIZEOF_PEL( test ); + flood->msize = IM_IMAGE_SIZEOF_PEL( mark ); + flood->left = x; + flood->top = y; + flood->right = x; + flood->bottom = y; + + flood->in = NULL; + flood->out = NULL; + + if( !(flood->ink = (PEL *) im_malloc( NULL, flood->msize )) || + !(flood->edge = (PEL *) im_malloc( NULL, flood->tsize )) || + !(flood->in = buffer_build()) || + !(flood->out = buffer_build()) ) { + flood_free( flood ); + return( NULL ); + } + memcpy( flood->ink, ink, flood->msize ); + + return( flood ); +} + +/** + * 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 ) +{ + Flood *flood; + + if( im_rwcheck( im ) || + im_check_known_coded( "im_flood", im ) ) + return( -1 ); + if( !(flood = flood_build( im, im, x, y, ink, dout )) ) + return( -1 ); + + /* Flood to != ink. + */ + memcpy( flood->edge, ink, flood->tsize ); + flood->equal = 0; + + flood_all( flood, x, y ); + + flood_free( flood ); + + 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 equal to the start pixel, in other words, it + * searches for a blob of same-coloured pixels. + * + * 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 ) +{ + Flood *flood; + + if( im_rwcheck( im ) || + im_check_known_coded( "im_flood", im ) ) + return( -1 ); + if( !(flood = flood_build( im, im, x, y, ink, dout )) ) + return( -1 ); + + /* Edge is set by colour of start pixel. + */ + memcpy( flood->edge, IM_IMAGE_ADDR( im, x, y ), flood->tsize ); + flood->equal = 1; + + flood_all( flood, x, y ); + + flood_free( flood ); + + im_invalidate( im ); + + return( 0 ); +} + +int +im_flood_other( IMAGE *test, IMAGE *mark, int x, int y, int serial, Rect *dout ) +{ + int *m; + Flood *flood; + + if( im_incheck( test ) || + im_rwcheck( mark ) || + im_check_known_coded( "im_flood_other", test ) || + im_check_uncoded( "im_flood_other", mark ) || + im_check_mono( "im_flood_other", mark ) || + im_check_format( "im_flood_other", mark, IM_BANDFMT_INT ) || + im_check_same_size( "im_flood_other", test, mark ) ) + return( -1 ); + + /* Have we done this point already? + */ + m = (int *) IM_IMAGE_ADDR( mark, x, y ); + if( *m == serial ) + return( 0 ); + + if( !(flood = flood_build( test, mark, x, y, (PEL *) &serial, dout )) ) + return( -1 ); + + /* Edge is set by colour of start pixel. + */ + memcpy( flood->edge, IM_IMAGE_ADDR( test, x, y ), flood->tsize ); + flood->equal = 1; + + flood_all( flood, x, y ); + + flood_free( flood ); + + im_invalidate( mark ); + + 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? + */ + +int +im_flood_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) +{ + IMAGE *t; + + if( !(t = im_open_local( out, "im_flood_blob_copy", "t" )) || + im_copy( in, t ) || + im_flood( t, x, y, ink, NULL ) || + im_copy( t, out ) ) + return( -1 ); + + return( 0 ); +} + +int +im_flood_blob_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) +{ + IMAGE *t; + + if( !(t = im_open_local( out, "im_flood_blob_copy", "t" )) || + im_copy( in, t ) || + im_flood_blob( t, x, y, ink, NULL ) || + im_copy( t, out ) ) + return( -1 ); + + return( 0 ); +} + +int +im_flood_other_copy( IMAGE *test, IMAGE *mark, IMAGE *out, + int x, int y, int serial ) +{ + IMAGE *t; + + if( !(t = im_open_local( out, "im_flood_other_copy", "t" )) || + im_copy( mark, t ) || + im_flood_other( test, t, x, y, serial, NULL ) || + im_copy( t, out ) ) + return( -1 ); + + return( 0 ); +} + diff --git a/libvips/inplace/im_flood.c b/libvips/inplace/im_flood.c index 85691a7e..3e8f9dbb 100644 --- a/libvips/inplace/im_flood.c +++ b/libvips/inplace/im_flood.c @@ -323,7 +323,7 @@ dofill( State *st, Buffer *in, Buffer *out ) } int -im_flood( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) +im_flood_old( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) { State *st; Buffer *in, *out, *t; @@ -372,7 +372,7 @@ im_flood( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) } int -im_flood_blob( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) +im_flood_blob_old( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) { State *st; Buffer *in, *out, *t; @@ -417,13 +417,30 @@ im_flood_blob( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) * automatically. Maybe nip could do it if it sees a RW image argument? */ int -im_flood_blob_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) +im_flood_blob_copy_old( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) { IMAGE *t; if( !(t = im_open_local( out, "im_flood_blob_copy", "t" )) || im_copy( in, t ) || - im_flood_blob( t, x, y, ink, NULL ) || + im_flood_blob_old( t, x, y, ink, NULL ) || + im_copy( t, out ) ) + return( -1 ); + + 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? + */ +int +im_flood_copy_old( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) +{ + IMAGE *t; + + if( !(t = im_open_local( out, "im_flood_copy", "t" )) || + im_copy( in, t ) || + im_flood_old( t, x, y, ink, NULL ) || im_copy( t, out ) ) return( -1 ); @@ -431,3 +448,5 @@ im_flood_blob_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) } + + diff --git a/libvips/inplace/im_flood_new.c b/libvips/inplace/im_flood_new.c deleted file mode 100644 index 7375d5ff..00000000 --- a/libvips/inplace/im_flood_new.c +++ /dev/null @@ -1,451 +0,0 @@ -/* 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 - * large images. - * - * JC 30/8/97 - * - VIPSified, cleaned up, from "John Robinson's prog to fill - * enclosed areas" - * - something Kirk gave me, so thanks John - * JC 1/10/97 - * - swapped inner memcmp/cpy for a loop ... faster for small pixels - * 13/7/02 JC - * - im_flood_blob() added - * 5/12/06 - * - im_invalidate() after paint - * 24/3/09 - * - 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 - * 21/12/09 - * - rewrite for a scanline based fill - */ - -/* - - This file is part of VIPS. - - VIPS is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - */ - -/* - - These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk - - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#include -#include -#include - -#include - -#ifdef WITH_DMALLOC -#include -#endif /*WITH_DMALLOC*/ - -/* Size of a scanline buffer. We allocate a list of these to hold scanlines - * we need to visit. - */ -#define PBUFSIZE (1000) - -/* A scanline we know could contain pixels connected to us. - */ -typedef struct { - int x1, x2; - int y; -} Scan; - -/* A buffer of scanlines, and how many of them have been used. If ->next is - * non-NULL, the next block could contain more of them. - * - * We keep a pair of these, then loop over one and write to the other. - */ -typedef struct _Buffer { - struct _Buffer *next; - int n; - Scan scan[PBUFSIZE]; -} Buffer; - -/* Our state. - */ -typedef struct { - /* Parameters. - */ - IMAGE *im; - int x, y; - PEL *ink; /* Copy of ink param */ - Rect *dout; /* Write dirty here at end */ - - /* Derived stuff. - */ - PEL *edge; /* Boundary colour */ - int equal; /* Fill to == edge, or != edge */ - int ps; /* sizeof( one pel ) */ - int ls; /* sizeof( one line ) */ - int left, right; /* Area will fill within */ - int top, bottom; - Rect dirty; /* Bounding box of pixels we have changed */ - - /* We need to flood above and below these scanlines. - */ - Buffer *up; - Buffer *down; - Buffer *read_up; - Buffer *read_down; -} State; - -/* Alloc a new buffer. - */ -static Buffer * -buffer_build( void ) -{ - Buffer *buf = IM_NEW( NULL, Buffer ); - - if( !buf ) - return( NULL ); - buf->next = NULL; - buf->n = 0; - - return( buf ); -} - -/* Free a chain of buffers. - */ -static void -buffer_free( Buffer *buf ) -{ - IM_FREE( buf->next ); - im_free( buf ); -} - -/* Add a scanline to a buffer, prepending a new buffer if necessary. Return - * the new head buffer. - */ -static inline Buffer * -buffer_add( State *st, Buffer *buf, int x1, int x2, int y ) -{ - /* Clip against image size. - */ - if( y < 0 || y > st->im->Ysize ) - return( buf ); - x1 = IM_CLIP( 0, x1, st->im->Xsize ); - x2 = IM_CLIP( 0, x2, st->im->Xsize ); - if( x2 - x1 <= 0 ) - return( buf ); - - buf->scan[buf->n].x1 = x1; - buf->scan[buf->n].x2 = x2; - buf->scan[buf->n].y = y; - buf->n++; - - if( buf->n == PBUFSIZE ) { - Buffer *new; - - if( !(new = buffer_build()) ) - return( NULL ); - new->next = buf; - buf = new; - } - - return( buf ); -} - -/* Is p "connected"? ie. is equal to or not equal to st->edge, depending on - * whether we are flooding to the edge boundary, or flooding edge-coloured - * pixels. - */ -static inline gboolean -pixel_connected( State *st, PEL *p ) -{ - int j; - - for( j = 0; j < st->ps; j++ ) - if( p[j] != st->edge[j] ) - break; - - return( st->equal ^ (j == st->ps) ); -} - -/* Faster than memcpy for n < about 20. - */ -static inline void -pixel_paint( State *st, PEL *q ) -{ - int j; - - for( j = 0; j < st->ps; j++ ) - q[j] = st->ink[j]; -} - -/* Fill a left and right, return the endpoints. The start point (x, y) must be - * connected. - */ -static void -fill_scanline( State *st, int x, int y, int *x1, int *x2 ) -{ - PEL *p = (PEL *) IM_IMAGE_ADDR( st->im, x, y ); - - int i; - PEL *q; - - g_assert( pixel_connected( st, p ) ); - - /* Fill this pixel and to the right. - */ - for( q = p, i = 0; - i < st->im->Xsize - x && pixel_connected( st, q ); - q += st->ps, i++ ) - pixel_paint( st, q ); - *x2 = x + i - 1; - - /* Fill to the left. - */ - for( q = p - st->ps, i = 1; - i > x && pixel_connected( st, q ); - q -= st->ps, i++ ) - pixel_paint( st, q ); - *x1 = x - (i - 1); -} - -/* We know the line below or above us is filled between x1 and x2. Search our - * line in this range looking for an edge pixel we can flood from. - * - * "direction" is -1 if we're going up, +1 if we're filling down. - */ -static void -fill_around( State *st, Scan *scan, int dir ) -{ - int x; - - g_assert( dir == 1 || dir == -1 ); - - for( x = scan->x1; x <= scan->x2; x++ ) { - PEL *p = (PEL *) IM_IMAGE_ADDR( st->im, x, scan->y ); - - if( pixel_connected( st, p ) ) { - int x1a; - int x2a; - - fill_scanline( st, x, scan->y, &x1a, &x2a ); - - /* Our new scanline can have up to three more - * scanlines connected to it: above, below left, below - * right. - */ - if( x1a < scan->x1 - 1 ) - st->down = buffer_add( st, st->down, - x1a, scan->x1 - 1, scan->y - dir ); - if( x2a > scan->x2 + 1 ) - st->down = buffer_add( st, st->down, - scan->x2 + 1, x2a, scan->y - dir ); - st->up = buffer_add( st, st->up, - x1a, x2a, scan->y + dir ); - - x = x2a; - } - } -} - -static void -fill_buffer( State *st, Buffer *buf, int dir ) -{ - Buffer *p; - - for( p = buf; p; p = p->next ) { - int i; - - for( i = 0; i < p->n; i++ ) - fill_around( st, &buf->scan[i], dir ); - - p->n = 0; - } -} - -static void -fill_all( State *st ) -{ - while( st->read_up || st->read_down ) { - Buffer *p; - - fill_buffer( st, st->read_up, -1 ); - fill_buffer( st, st->read_down, -1 ); - - p = st->read_up; - st->read_up = st->up; - st->up = p; - - p = st->read_down; - st->read_down = st->down; - st->down = p; - } -} - -/* Free a state. - */ -static void -state_free( State *st ) -{ - /* Write dirty back to caller. - */ - if( st->dout ) - *st->dout = st->dirty; - - /* Free our stuff. - */ - IM_FREE( st->ink ); - IM_FREE( st->edge ); - IM_FREEF( buffer_free, st->up ); - IM_FREEF( buffer_free, st->down ); - IM_FREEF( buffer_free, st->read_up ); - IM_FREEF( buffer_free, st->read_down ); - im_free( st ); -} - -/* Build a state. - */ -static State * -state_build( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) -{ - State *st = IM_NEW( NULL, State ); - - if( !st ) - return( NULL ); - st->im = im; - st->x = x; - st->y = y; - st->ink = NULL; - st->dout = dout; - st->edge = NULL; - st->ps = IM_IMAGE_SIZEOF_PEL( im ); - st->ls = IM_IMAGE_SIZEOF_LINE( im ); - st->left = 0; - st->top = 0; - st->right = im->Xsize; - st->bottom = im->Ysize; - st->dirty.left = x; - st->dirty.top = y; - st->dirty.width = 0; - st->dirty.height = 0; - - st->up = NULL; - st->down = NULL; - st->read_up = NULL; - st->read_down = NULL; - - if( !(st->ink = (PEL *) im_malloc( NULL, st->ps )) || - !(st->edge = (PEL *) im_malloc( NULL, st->ps )) || - !(st->up = buffer_build()) || - !(st->down = buffer_build()) || - !(st->read_up = buffer_build()) || - !(st->read_down = buffer_build()) ) { - state_free( st ); - return( NULL ); - } - memcpy( st->ink, ink, st->ps ); - - return( st ); -} - -/** - * 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_new( IMAGE *im, int x, int y, PEL *ink, Rect *dout ) -{ - State *st; - Buffer *in, *out, *t; - PEL *p; - - 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 ) { - state_free( st ); - return( 0 ); - } - - /* Flood to != ink. - */ - memcpy( st->edge, ink, st->ps ); - st->equal = 0; - - /* Add start pixel to the work buffer, and loop. - st->buf1 = buffer_add( st, 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 ) ) { - state_free( st ); - return( -1 ); - } - */ - - state_free( st ); - - im_invalidate( im ); - - return( 0 ); -} - -int -im_flood_new_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ) -{ - IMAGE *t; - - if( !(t = im_open_local( out, "im_flood_blob_copy", "t" )) || - im_copy( in, t ) || - im_flood_new( t, x, y, ink, NULL ) || - im_copy( t, out ) ) - return( -1 ); - - return( 0 ); -} - - diff --git a/libvips/inplace/im_flood_other.c b/libvips/inplace/im_flood_other.c index fb7947d7..fd594bba 100644 --- a/libvips/inplace/im_flood_other.c +++ b/libvips/inplace/im_flood_other.c @@ -261,7 +261,7 @@ dofill( State *st, Buffer *in, Buffer *out ) } int -im_flood_other( IMAGE *mask, IMAGE *test, int x, int y, int serial ) +im_flood_other_old( IMAGE *mask, IMAGE *test, int x, int y, int serial ) { State *st; Buffer *in, *out, *t; @@ -315,14 +315,14 @@ im_flood_other( IMAGE *mask, IMAGE *test, int x, int y, int serial ) * automatically. Maybe nip could do it if it sees a RW image argument? */ int -im_flood_other_copy( IMAGE *mask, IMAGE *test, IMAGE *out, +im_flood_other_copy_old( IMAGE *mask, IMAGE *test, IMAGE *out, int x, int y, int serial ) { IMAGE *t; if( !(t = im_open_local( out, "im_flood_other_copy", "t" )) || im_copy( mask, t ) || - im_flood_other( t, test, x, y, serial ) || + im_flood_other_old( t, test, x, y, serial ) || im_copy( t, out ) ) return( -1 ); diff --git a/libvips/inplace/inplace_dispatch.c b/libvips/inplace/inplace_dispatch.c index 4001a494..ac442d60 100644 --- a/libvips/inplace/inplace_dispatch.c +++ b/libvips/inplace/inplace_dispatch.c @@ -188,9 +188,9 @@ vector_to_ink( IMAGE *im, double *vec ) return( (PEL *) t[2]->data ); } -/* Args for im_flood_new(). +/* Args for im_flood_old(). */ -static im_arg_desc flood_new_args[] = { +static im_arg_desc flood_old_args[] = { IM_INPUT_IMAGE( "in" ), IM_OUTPUT_IMAGE( "out" ), IM_INPUT_INT( "start_x" ), @@ -198,10 +198,10 @@ static im_arg_desc flood_new_args[] = { IM_INPUT_DOUBLEVEC( "ink" ) }; -/* Call im_flood_new() via arg vector. +/* Call im_flood_old() via arg vector. */ static int -flood_new_vec( im_object *argv ) +flood_old_vec( im_object *argv ) { IMAGE *in = argv[0]; IMAGE *out = argv[1]; @@ -209,30 +209,30 @@ flood_new_vec( im_object *argv ) int start_y = *((int *) argv[3]); im_doublevec_object *dv = (im_doublevec_object *) argv[4]; - extern int im_flood_new_copy( IMAGE *, IMAGE *, int, int, PEL * ); + extern int im_flood_copy_old( IMAGE *, IMAGE *, int, int, PEL * ); PEL *ink; if( dv->n != in->Bands ) { - im_error( "im_flood_new", + im_error( "im_flood_old", "%s", _( "bad vector length" ) ); return( -1 ); } if( !(ink = vector_to_ink( in, dv->vec )) ) return( -1 ); - return( im_flood_new_copy( in, out, start_x, start_y, ink ) ); + return( im_flood_copy_old( in, out, start_x, start_y, ink ) ); } -/* Description of im_flood_new(). +/* Description of im_flood_old(). */ -static im_function flood_new_desc = { - "im_flood_new", /* Name */ +static im_function flood_old_desc = { + "im_flood_old", /* Name */ "flood with ink from start_x, start_y while pixel == start pixel", 0, /* Flags */ - flood_new_vec, /* Dispatch function */ - IM_NUMBER( flood_new_args ),/* Size of arg list */ - flood_new_args /* Arg list */ + flood_old_vec, /* Dispatch function */ + IM_NUMBER( flood_old_args ),/* Size of arg list */ + flood_old_args /* Arg list */ }; /* Args for im_flood_blob_copy(). @@ -280,6 +280,51 @@ static im_function flood_blob_copy_desc = { flood_blob_copy_args /* Arg list */ }; +/* Args for im_flood_copy(). + */ +static im_arg_desc flood_copy_args[] = { + IM_INPUT_IMAGE( "in" ), + IM_OUTPUT_IMAGE( "out" ), + IM_INPUT_INT( "start_x" ), + IM_INPUT_INT( "start_y" ), + IM_INPUT_DOUBLEVEC( "ink" ) +}; + +/* Call im_flood_copy() via arg vector. + */ +static int +flood_copy_vec( im_object *argv ) +{ + IMAGE *in = argv[0]; + IMAGE *out = argv[1]; + int start_x = *((int *) argv[2]); + int start_y = *((int *) argv[3]); + im_doublevec_object *dv = (im_doublevec_object *) argv[4]; + + PEL *ink; + + if( dv->n != in->Bands ) { + im_error( "im_flood_copy", + "%s", _( "bad vector length" ) ); + return( -1 ); + } + if( !(ink = vector_to_ink( in, dv->vec )) ) + return( -1 ); + + return( im_flood_copy( in, out, start_x, start_y, ink ) ); +} + +/* Description of im_flood_copy(). + */ +static im_function flood_copy_desc = { + "im_flood_copy", /* Name */ + "flood with ink from start_x, start_y while pixel == start pixel", + 0, /* Flags */ + flood_copy_vec, /* Dispatch function */ + IM_NUMBER( flood_copy_args ),/* Size of arg list */ + flood_copy_args /* Arg list */ +}; + /* Args for im_flood_other_copy(). */ static im_arg_desc flood_other_copy_args[] = { @@ -335,7 +380,8 @@ static im_function flood_other_copy_desc = { static im_function *inplace_list[] = { &circle_desc, &flood_blob_copy_desc, - &flood_new_desc, + &flood_copy_desc, + &flood_old_desc, &flood_other_copy_desc, &insertplace_desc, &lineset_desc diff --git a/libvips/morphology/im_label_regions.c b/libvips/morphology/im_label_regions.c index 2ff389eb..4dba872d 100644 --- a/libvips/morphology/im_label_regions.c +++ b/libvips/morphology/im_label_regions.c @@ -92,7 +92,8 @@ im_label_regions( IMAGE *test, IMAGE *mask, int *segments ) for( y = 0; y < test->Ysize; y++ ) { for( x = 0; x < test->Xsize; x++ ) { if( !m[x] ) { - if( im_flood_other( t[1], test, x, y, serial ) ) + if( im_flood_other( test, t[1], + x, y, serial, NULL ) ) return( -1 ); serial += 1;