/* draw_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_draw_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
 * 	- draw_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
 * 5/4/20
 * 	- could fail to complete for some very complex shapes
 */

/*

    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 <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <vips/vips.h>
#include <vips/internal.h>

#include "drawink.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;

/* What we track during a flood. We have this in a separate struct so that we
 * can support vips__draw_flood_direct() ... a fast path for 
 * vips_labelregions() that avoids all of the GObject call overhead. This
 * gives a huge speedup, >x10 in many cases.
 */
typedef struct _Flood {
	/* Test pixels here.
	 */
	VipsImage *test;

	/* 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;

	/* Our todo list.
	 */
	Buffer *in;
	Buffer *out;

} Flood; 

/* 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 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 = VIPS_CLIP( 0, x1, flood->test->Xsize - 1 );
	x2 = VIPS_CLIP( 0, x2, flood->test->Xsize - 1 );
	if( x2 - x1 < 0 )
		return( buf );

	if( buf->n == PBUFSIZE ) { 
		Buffer *new;

		new = buffer_build();
		new->next = buf;
		buf = new;
	}

	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;

	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. p is a pel in @test. 
 */
static gboolean
flood_connected( Flood *flood, VipsPel *p )
{
 	int j;

	for( j = 0; j < flood->tsize; j++ ) 
		if( p[j] != flood->edge[j] ) 
			break;

	/* If flood->equal, true if point == edge.
	 */
	return( flood->equal ^ (j < flood->tsize) );
}

/* Is p painted? p is a pel in @image. 
 */
static 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 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 
flood_scanline( Flood *flood, int x, int y, int *x1, int *x2 )
{
	const int width = flood->test->Xsize;

	int i;

	g_assert( flood_connected( flood, 
		VIPS_IMAGE_ADDR( flood->test, 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.
	 */
	if( x < width ) {
		VipsPel *p = VIPS_IMAGE_ADDR( flood->test, x + 1, y );

		for( i = x + 1; i < width; i++ ) {
			if( !flood_connected( flood, p ) )
				break;
			p += flood->tsize;
		}
		*x2 = i - 1;
	}
	else 
		*x2 = width;

	/* Search left.
	 */
	if( x > 0 ) {
		VipsPel *p = VIPS_IMAGE_ADDR( flood->test, x - 1, y );

		for( i = x - 1; i >= 0; i-- ) {
			if( !flood_connected( flood, p ) )
				break;
			p -= flood->tsize;
		}
		*x1 = i + 1;
	}
	else
		*x1 = 0;

	/* Paint the range we discovered.
	 */
	flood_draw_scanline( flood, 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
flood_around( Flood *flood, Scan *scan )
{
	VipsPel *p;
	int x;

	g_assert( scan->dir == 1 || 
		scan->dir == -1 );

	for( p = VIPS_IMAGE_ADDR( flood->test, scan->x1, scan->y ), 
		x = scan->x1; 
		x <= scan->x2; 
		p += flood->tsize, x++ ) {
		if( flood_connected( flood, p ) ) {
			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( flood->image != flood->test ) {
				VipsPel *mp = VIPS_IMAGE_ADDR( 
					flood->image, x, scan->y );

				if( flood_painted( flood, mp ) )
					continue;
			}

			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;
			p = VIPS_IMAGE_ADDR( flood->test, x, scan->y );
		}
	}
}

static void
flood_all( Flood *flood, int x, int y )
{
	int x1, x2;

	/* Test start pixel ... nothing to do?
	 */
	if( !flood_connected( flood, VIPS_IMAGE_ADDR( flood->test, x, y ) ) ) 
		return;

	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;

		for( p = flood->in; p; p = p->next ) {
			int i;

			for( i = 0; i < p->n; i++ )
				flood_around( flood, &p->scan[i] );

			p->n = 0;
		}

		VIPS_SWAP( Buffer *, flood->in, flood->out );
	}

	VIPS_FREEF( buffer_free, flood->in );
	VIPS_FREEF( buffer_free, flood->out );
}

/* 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 *drawflood = (VipsDrawFlood *) object;

	Flood flood; 
	int j; 

	if( VIPS_OBJECT_CLASS( vips_draw_flood_parent_class )->build( object ) )
		return( -1 );

	/* @test defaults to @image.
	 */
	if( !vips_object_argument_isset( object, "test" ) )
		/* Setting input params like this will break caching, but we
		 * don't cache draw ops anyway.
		 */
		g_object_set( object, "test", draw->image, NULL ); 

	if( vips_image_wio_input( drawflood->test ) ||
		vips_check_coding_known( class->nickname, drawflood->test ) ||
		vips_check_size_same( class->nickname, 
			drawflood->test, draw->image ) )
		return( -1 );

	flood.test = drawflood->test;
	flood.image = draw->image;
	flood.tsize = VIPS_IMAGE_SIZEOF_PEL( flood.test );
	flood.equal = drawflood->equal;
	flood.psize = VIPS_IMAGE_SIZEOF_PEL( flood.image );
	flood.ink = drawink->pixel_ink;
	flood.lsize = VIPS_IMAGE_SIZEOF_LINE( flood.image );
	flood.left = drawflood->x;
	flood.right = drawflood->x;
	flood.top = drawflood->y;
	flood.bottom = drawflood->y;

	if( flood.equal ) {
		/* Edge is set by colour of the start pixel in @test.
		 */
		if( !(flood.edge = VIPS_ARRAY( object, flood.tsize, VipsPel )) )
			return( -1 );
		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 == flood.image ) { 
			for( j = 0; j < flood.tsize; j++ ) 
				if( flood.edge[j] != flood.ink[j] ) 
					break;

			if( j != flood.tsize )
				flood_all( &flood, drawflood->x, drawflood->y );
		}
		else
			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, 
			VIPS_ARRAY_ADDR( drawink->ink, 0 ), NULL, 
			VIPS_AREA( drawink->ink )->n )) )
			return( -1 );

		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,
		NULL ); 

	return( 0 );
}

static void
vips_draw_flood_class_init( VipsDrawFloodClass *class )
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( class );
	VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	vobject_class->nickname = "draw_flood";
	vobject_class->description = _( "flood-fill an area" );
	vobject_class->build = vips_draw_flood_build;

	VIPS_ARG_INT( class, "x", 3, 
		_( "x" ), 
		_( "DrawFlood start point" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, x ),
		0, 1000000000, 0 );

	VIPS_ARG_INT( class, "y", 4, 
		_( "y" ), 
		_( "DrawFlood start point" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, y ),
		0, 1000000000, 0 );

	VIPS_ARG_IMAGE( class, "test", 5, 
		_( "Test" ), 
		_( "Test pixels in this image" ),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, test ) ); 

	VIPS_ARG_BOOL( class, "equal", 6, 
		_( "Equal" ), 
		_( "DrawFlood while equal to edge" ),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, equal ),
		FALSE ); 

	VIPS_ARG_INT( class, "left", 7, 
		_( "Left" ), 
		_( "Left edge of modified area" ),
		VIPS_ARGUMENT_OPTIONAL_OUTPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, left ),
		0, 1000000000, 0 );

	VIPS_ARG_INT( class, "top", 8, 
		_( "Top" ), 
		_( "top edge of modified area" ),
		VIPS_ARGUMENT_OPTIONAL_OUTPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, top ),
		0, 1000000000, 0 );

	VIPS_ARG_INT( class, "width", 9, 
		_( "Width" ), 
		_( "width of modified area" ),
		VIPS_ARGUMENT_OPTIONAL_OUTPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, width ),
		0, 1000000000, 0 );

	VIPS_ARG_INT( class, "height", 10, 
		_( "Height" ), 
		_( "height of modified area" ),
		VIPS_ARGUMENT_OPTIONAL_OUTPUT,
		G_STRUCT_OFFSET( VipsDrawFlood, height ),
		0, 1000000000, 0 );

}

static void
vips_draw_flood_init( VipsDrawFlood *draw_flood )
{
}

/* Direct path to flood for vips_labelregions(). We need to avoid the function
 * dispatch system for speed.
 *
 * Equivalent to:
 *
 * vips_draw_flood1( image, serial, x, y,
 *       "test", test,
 *       "equal", TRUE,
 *       NULL )
 *
 * image must be 1-band int. 
 */
int
vips__draw_flood_direct( VipsImage *image, VipsImage *test, 
	int serial, int x, int y )
{
	Flood 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 ) ||  
		vips_image_inplace( image ) )
		return( -1 );

	flood.test = test;
	flood.image = image;
	flood.tsize = VIPS_IMAGE_SIZEOF_PEL( test );
	flood.equal = TRUE;
	flood.psize = VIPS_IMAGE_SIZEOF_PEL( image );
	flood.ink = (VipsPel *) &serial;
	flood.lsize = VIPS_IMAGE_SIZEOF_LINE( image );
	flood.left = x;
	flood.right = x;
	flood.top = y;
	flood.bottom = y;

	if( !(flood.edge = VIPS_ARRAY( image, flood.tsize, VipsPel )) )
		return( -1 );
	memcpy( flood.edge, 
		VIPS_IMAGE_ADDR( test, x, y ), flood.tsize );

	flood_all( &flood, x, y );

	return( 0 ); 
}

static int
vips_draw_floodv( VipsImage *image, 
	double *ink, int n, int x, int y, va_list ap )
{
	VipsArea *area_ink;
	int result;

	area_ink = VIPS_AREA( vips_array_double_new( ink, n ) );
	result = vips_call_split( "draw_flood", ap, image, area_ink, x, y );
	vips_area_unref( area_ink );

	return( result );
}

/**
 * vips_draw_flood: (method)
 * @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
 * @...: %NULL-terminated list of optional named arguments
 *
 * 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 an edge 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 an
 * image (@test) for continuous areas of pixels without modifying it. 
 *
 * @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_draw_flood1().
 *
 * Returns: 0 on success, or -1 on error.
 */
int
vips_draw_flood( VipsImage *image, 
	double *ink, int n, int x, int y, ... )
{
	va_list ap;
	int result;

	va_start( ap, y );
	result = vips_draw_floodv( image, ink, n, x, y, ap );
	va_end( ap );

	return( result );
}

/**
 * vips_draw_flood1: (method)
 * @image: image to draw on
 * @ink: value to draw
 * @x: centre of circle
 * @y: centre of circle
 * @...: %NULL-terminated list of optional named arguments
 *
 * 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_draw_flood(), but just takes a single double for @ink. 
 *
 * See also: vips_draw_flood().
 *
 * Returns: 0 on success, or -1 on error.
 */
int
vips_draw_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_draw_floodv( image, array_ink, 1, x, y, ap );
	va_end( ap );

	return( result );
}
