diff --git a/libvips/draw/flood.c b/libvips/draw/flood.c new file mode 100644 index 00000000..ecc0e98a --- /dev/null +++ b/libvips/draw/flood.c @@ -0,0 +1,612 @@ +/* flood-fill + * + * 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, about 4x faster! + * - allow separate test and mark images + * 22/1/10 + * - flood_blob could loop if start point == ink + * 6/3/10 + * - don't im_invalidate() after paint, this now needs to be at a higher + * level + * 27/9/10 + * - use Draw base class + * 21/1/14 + * - redo as a class + */ + +/* + + 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 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 + +#include "pdraw.h" + +/* 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; + +/* Base class. + */ +typedef struct _VipsFlood { + VipsDraw draw; + + /* Parameters. + */ + int x; + int y; + VipsImage *test; /* Test this image */ + gboolean equal; /* Fill to == edge, or != edge */ + + int left; /* Record bounding box of modified pixels */ + 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. + */ + Buffer *in; + Buffer *out; +} VipsFlood; + +typedef VipsDrawClass VipsFloodClass; + +G_DEFINE_TYPE( VipsFlood, vips_flood, VIPS_TYPE_DRAW ); + +/* Alloc a new buffer. + */ +static Buffer * +buffer_build( void ) +{ + Buffer *buf; + + buf = g_new( Buffer, 1 ); + 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; + g_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, VipsFlood *flood, int x1, int x2, int y, int dir ) +{ + /* Clip against image size. + */ + if( y < 0 || + y >= flood->test->Ysize ) + return( buf ); + x1 = VIPS_CLIP( 0, x1, flood->test->Xsize - 1 ); + x2 = VIPS_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; + + new = buffer_build(); + 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 +vips_flood_connected( VipsFlood *flood, VipsPel *tp ) +{ + int j; + + for( j = 0; j < flood->tsize; j++ ) + if( tp[j] != flood->edge[j] ) + break; + + /* If flood->equal, true if point == edge. + */ + return( flood->equal ^ (j < flood->tsize) ); +} + +/* Fill left and right, return the endpoints. The start point (x, y) must be + * connected and unpainted. + */ +static void +vips_flood_scanline( VipsFlood *flood, int x, int y, int *x1, int *x2 ) +{ + VipsDraw *draw = VIPS_DRAW( flood ); + const int width = flood->test->Xsize; + + VipsPel *tp; + int i; + + g_assert( vips_flood_connected( flood, + VIPS_IMAGE_ADDR( flood->test, x, y ) ) ); + g_assert( !vips__draw_painted( draw, + VIPS_IMAGE_ADDR( draw->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 ); + for( i = x + 1; i < width; i++ ) { + if( !vips_flood_connected( flood, tp ) ) + break; + tp += flood->tsize; + } + *x2 = i - 1; + + /* Search left. + */ + tp = VIPS_IMAGE_ADDR( flood->test, x - 1, y ); + for( i = x - 1; i >= 0; i-- ) { + if( !vips_flood_connected( flood, tp ) ) + break; + tp -= flood->tsize; + } + *x1 = i + 1; + + /* Paint the range we discovered. + */ + vips__draw_scanline( draw, y, *x1, *x2 ); + + flood->left = VIPS_MIN( flood->left, *x1 ); + flood->right = VIPS_MAX( flood->right, *x2 ); + flood->top = VIPS_MIN( flood->top, y ); + flood->bottom = VIPS_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 +vips_flood_around( VipsFlood *flood, Scan *scan ) +{ + VipsDraw *draw = VIPS_DRAW( flood ); + + VipsPel *tp; + int x; + + g_assert( scan->dir == 1 || + scan->dir == -1 ); + + for( tp = VIPS_IMAGE_ADDR( flood->test, scan->x1, scan->y ), + x = scan->x1; + x <= scan->x2; + tp += flood->tsize, x++ ) { + if( vips_flood_connected( flood, tp ) ) { + int x1a; + int x2a; + + /* If mark and test are different images, we also need + * to check for painted. Otherwise we can get stuck in + * connected loops. + */ + if( draw->image != flood->test ) { + VipsPel *mp = VIPS_IMAGE_ADDR( + draw->image, x, scan->y ); + + if( vips__draw_painted( draw, mp ) ) + continue; + } + + vips_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 - 2, + scan->y - scan->dir, -scan->dir ); + if( x2a > scan->x2 + 1 ) + flood->out = buffer_add( flood->out, flood, + scan->x2 + 2, x2a, + scan->y - scan->dir, -scan->dir ); + flood->out = buffer_add( flood->out, flood, + x1a, x2a, scan->y + scan->dir, + scan->dir ); + + x = x2a + 1; + tp = VIPS_IMAGE_ADDR( flood->test, x, scan->y ); + } + } +} + +static void +vips_flood_all( VipsFlood *flood ) +{ + int x1, x2; + + /* Test start pixel ... nothing to do? + */ + if( !vips_flood_connected( flood, + VIPS_IMAGE_ADDR( flood->test, flood->x, flood->y ) ) ) + return; + + vips_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 ); + + while( flood->in->n ) { + Buffer *p; + + for( p = flood->in; p; p = p->next ) { + int i; + + for( i = 0; i < p->n; i++ ) + vips_flood_around( flood, &p->scan[i] ); + + p->n = 0; + } + + VIPS_SWAP( Buffer *, flood->in, flood->out ); + } +} + +static void +vips_flood_dispose( GObject *gobject ) +{ + VipsFlood *flood = (VipsFlood *) gobject; + + VIPS_FREE( flood->edge ); + VIPS_FREEF( buffer_free, flood->in ); + VIPS_FREEF( buffer_free, flood->out ); + + G_OBJECT_CLASS( vips_flood_parent_class )->dispose( gobject ); +} + +static int +vips_flood_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_CLASS( object ); + VipsDraw *draw = VIPS_DRAW( object ); + VipsFlood *flood = (VipsFlood *) object; + + int j; + + if( VIPS_OBJECT_CLASS( vips_flood_parent_class )->build( object ) ) + return( -1 ); + + 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( !(flood->edge = (VipsPel *) im_malloc( NULL, flood->tsize )) ) + return( -1 ); + + /* @test defaults to @image. + */ + if( !vips_object_argument_isset( object, "test" ) ) + flood->test = draw->image; + + if( vips_image_wio_input( flood->test ) || + vips_check_coding_known( class->nickname, flood->test ) || + vips_check_uncoded( class->nickname, flood->test ) || + vips_check_mono( class->nickname, flood->test ) || + vips_check_format( class->nickname, flood->test, + VIPS_FORMAT_INT ) || + vips_check_size_same( class->nickname, + flood->test, draw->image ) ) + return( -1 ); + + /* Flood to ink. + */ + if( flood->equal ) { + /* Edge is set by colour of start pixel. + */ + memcpy( flood->edge, + VIPS_IMAGE_ADDR( flood->test, flood->x, flood->y ), + flood->tsize ); + + /* If edge == ink, we'll never stop :-( or rather, + * there's nothing to do. + */ + for( j = 0; j < flood->tsize; j++ ) + if( flood->edge[j] != + VIPS_DRAW( flood )->pixel_ink[j] ) + break; + + if( j != flood->tsize ) + vips_flood_all( flood ); + } + else { + memcpy( flood->edge, VIPS_DRAW( flood )->pixel_ink, + flood->tsize ); + vips_flood_all( flood ); + } + + flood->width = flood->right - flood->left + 1; + flood->height = flood->bottom - flood->top + 1; + + return( 0 ); +} + +static void +vips_flood_class_init( VipsFloodClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + + gobject_class->dispose = vips_flood_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "flood"; + vobject_class->description = _( "flood-fill an area" ); + vobject_class->build = vips_flood_build; + + VIPS_ARG_INT( class, "x", 3, + _( "x" ), + _( "Flood start point" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsFlood, x ), + 0, 1000000000, 0 ); + + VIPS_ARG_INT( class, "y", 4, + _( "y" ), + _( "Flood start point" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsFlood, y ), + 0, 1000000000, 0 ); + + VIPS_ARG_IMAGE( class, "test", 5, + _( "Test" ), + _( "Test pixels in this image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsFlood, test ) ); + + VIPS_ARG_BOOL( class, "equal", 6, + _( "Equal" ), + _( "Flood while equal to edge" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsFlood, equal ), + FALSE ); + + VIPS_ARG_INT( class, "left", 7, + _( "Left" ), + _( "Left edge of modified area" ), + VIPS_ARGUMENT_OPTIONAL_OUTPUT, + G_STRUCT_OFFSET( VipsFlood, left ), + 0, 1000000000, 0 ); + + VIPS_ARG_INT( class, "top", 8, + _( "Top" ), + _( "top edge of modified area" ), + VIPS_ARGUMENT_OPTIONAL_OUTPUT, + G_STRUCT_OFFSET( VipsFlood, top ), + 0, 1000000000, 0 ); + + VIPS_ARG_INT( class, "width", 9, + _( "Width" ), + _( "width of modified area" ), + VIPS_ARGUMENT_OPTIONAL_OUTPUT, + G_STRUCT_OFFSET( VipsFlood, width ), + 0, 1000000000, 0 ); + + VIPS_ARG_INT( class, "height", 10, + _( "Height" ), + _( "height of modified area" ), + VIPS_ARGUMENT_OPTIONAL_OUTPUT, + G_STRUCT_OFFSET( VipsFlood, height ), + 0, 1000000000, 0 ); + +} + +static void +vips_flood_init( VipsFlood *flood ) +{ +} + +static int +vips_floodv( VipsImage *image, + double *ink, int n, int x, int y, va_list ap ) +{ + VipsArea *area_ink; + int result; + + area_ink = (VipsArea *) vips_array_double_new( ink, n ); + result = vips_call_split( "flood", ap, image, area_ink, x, y ); + vips_area_unref( area_ink ); + + return( result ); +} + +/** + * vips_flood: + * @image: image to draw on + * @ink: (array length=n): value to draw + * @n length of ink array + * @x: centre of circle + * @y: centre of circle + * + * Optional arguments: + * + * @test: test this image + * @equal: fill while equal to edge + * @left: output left edge of bounding box of modified area + * @top: output top edge of bounding box of modified area + * @width: output width of bounding box of modified area + * @height: output height of bounding box of modified area + * + * Flood-fill @image 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. + * + * If @equal is set, it instead searches for pixels which are equal to the + * start point and fills them with @ink. + * + * Normally it will test and set pixels in @image. If @test is set, it will + * test pixels in @test and set pixels in @image. This lets you search for + * continuous areas of pixels without changing an image. + * + * @left, @top, @width, @height output the bounding box of the modified + * pixels. + * + * @ink is an array of double containing values to draw. + * + * See also: vips_flood1(). + * + * Returns: 0 on success, or -1 on error. + */ +int +vips_flood( VipsImage *image, + double *ink, int n, int x, int y, ... ) +{ + va_list ap; + int result; + + va_start( ap, y ); + result = vips_floodv( image, ink, n, x, y, ap ); + va_end( ap ); + + return( result ); +} + +/** + * vips_flood1: + * @image: image to draw on + * @ink: value to draw + * @x: centre of circle + * @y: centre of circle + * + * Optional arguments: + * + * @test: test this image + * @equal: fill while equal to edge + * @left: output left edge of bounding box of modified area + * @top: output top edge of bounding box of modified area + * @width: output width of bounding box of modified area + * @height: output height of bounding box of modified area + * + * As vips_flood(), but just takes a single double for @ink. + * + * See also: vips_flood(). + * + * Returns: 0 on success, or -1 on error. + */ +int +vips_flood1( VipsImage *image, double ink, int x, int y, ... ) +{ + double array_ink[1]; + va_list ap; + int result; + + array_ink[0] = ink; + + va_start( ap, y ); + result = vips_floodv( image, array_ink, 1, x, y, ap ); + va_end( ap ); + + return( result ); +}