/* im_blend.c --- blend images with a condition image
 *
 * Modified:
 * 15/4/05
 *	- from im_ifthenelse()
 * 8/7/05
 *	- oops, broken for some combinations of band differences (thanks Joe)
 * 23/9/09
 * 	- gtkdoc comments
 * 	- use im_check*()
 * 	- allow many-band conditional and single-band a/b
 * 	- allow a/b to differ in format and bands
 */

/*

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

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

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

#define iblend1( TYPE ) { \
	TYPE *a = (TYPE *) ap; \
	TYPE *b = (TYPE *) bp; \
	TYPE *q = (TYPE *) qp; \
 	\
	for( i = 0, x = 0; x < n; i++, x += bands ) { \
		const int v = c[i]; \
 		\
		for( z = x; z < x + bands; z++ )  \
			q[z] = (v * a[z] + (255 - v) * b[z] + 128) / 255; \
	} \
}

#define iblendn( TYPE ) { \
	TYPE *a = (TYPE *) ap; \
	TYPE *b = (TYPE *) bp; \
	TYPE *q = (TYPE *) qp; \
 	\
	for( x = 0; x < n; x += bands ) { \
		for( z = x; z < x + bands; z++ ) { \
			const int v = c[z]; \
 			\
			q[z] = (v * a[z] + (255 - v) * b[z] + 128) / 255; \
		} \
	} \
}

#define fblend1( TYPE ) { \
	TYPE *a = (TYPE *) ap; \
	TYPE *b = (TYPE *) bp; \
	TYPE *q = (TYPE *) qp; \
 	\
	for( i = 0, x = 0; x < n; i++, x += bands ) { \
		const double v = c[i] / 255.0; \
 		\
		for( z = x; z < x + bands; z++ )  \
			q[z] = v * a[z] + (1.0 - v) * b[z]; \
	} \
}

#define fblendn( TYPE ) { \
	TYPE *a = (TYPE *) ap; \
	TYPE *b = (TYPE *) bp; \
	TYPE *q = (TYPE *) qp; \
 	\
	for( x = 0; x < n; x += bands ) { \
		for( z = x; z < x + bands; z++ ) { \
			const double v = c[z] / 255.0; \
 			\
			q[z] = v * a[z] + (1.0 - v) * b[z]; \
		} \
	} \
}

#define cblend1( TYPE ) { \
	TYPE *a = (TYPE *) ap; \
	TYPE *b = (TYPE *) bp; \
	TYPE *q = (TYPE *) qp; \
 	\
	for( i = 0, x = 0; x < n; i++, x += bands ) { \
		const double v = c[i] / 255.0; \
 		\
		for( z = x; z < x + 2 * bands; z++ )  \
			q[z] = v * a[z] + (1.0 - v) * b[z]; \
	} \
}

#define cblendn( TYPE ) { \
	TYPE *a = (TYPE *) ap; \
	TYPE *b = (TYPE *) bp; \
	TYPE *q = (TYPE *) qp; \
 	\
	for( x = 0; x < n; x += bands ) { \
		for( z = x; z < x + bands; z++ ) { \
			const double v = c[z] / 255.0; \
 			\
			q[2 * z] = v * a[2 * z] + (1.0 - v) * b[2 * z]; \
			q[2 * z + 1] = v * a[2 * z + 1] + \
				(1.0 - v) * b[2 * z + 1]; \
		} \
	} \
}

/* Blend with a 1-band conditional image.
 */
static void
blend1_buffer( PEL *qp, PEL *c, PEL *ap, PEL *bp, int width, IMAGE *im )
{
	int i, x, z;
	const int bands = im->Bands;
	const int n = width * bands;

	switch( im->BandFmt ) {
	case IM_BANDFMT_UCHAR:
		iblend1( unsigned char ); break;
	case IM_BANDFMT_CHAR:
		iblend1( signed char ); break;
	case IM_BANDFMT_USHORT:
		iblend1( unsigned short ); break;
	case IM_BANDFMT_SHORT:
		iblend1( signed short ); break;
	case IM_BANDFMT_UINT:
		iblend1( unsigned int ); break;
	case IM_BANDFMT_INT:
		iblend1( signed int );  break;
	case IM_BANDFMT_FLOAT:
		fblend1( float ); break;
	case IM_BANDFMT_DOUBLE:
		fblend1( double ); break;
	case IM_BANDFMT_COMPLEX:
		cblend1( float ); break;
	case IM_BANDFMT_DPCOMPLEX:
		cblend1( double ); break;

	default:
		g_assert( 0 );
	}
}

/* Blend with a many band conditional image.
 */
static void
blendn_buffer( PEL *qp, PEL *c, PEL *ap, PEL *bp, int width, IMAGE *im )
{
	int x, z;
	const int bands = im->Bands;
	const int n = width * bands;

	switch( im->BandFmt ) {
	case IM_BANDFMT_UCHAR:
		iblendn( unsigned char ); break;
	case IM_BANDFMT_CHAR:
		iblendn( signed char ); break;
	case IM_BANDFMT_USHORT:
		iblendn( unsigned short ); break;
	case IM_BANDFMT_SHORT:
		iblendn( signed short ); break;
	case IM_BANDFMT_UINT:
		iblendn( unsigned int ); break;
	case IM_BANDFMT_INT:
		iblendn( signed int );  break;
	case IM_BANDFMT_FLOAT:
		fblendn( float ); break;
	case IM_BANDFMT_DOUBLE:
		fblendn( double ); break;
	case IM_BANDFMT_COMPLEX:
		cblendn( float ); break;
	case IM_BANDFMT_DPCOMPLEX:
		cblendn( double ); break;

	default:
		g_assert( 0 );
	}
}

static int
blend_gen( REGION *or, void *seq, void *client1, void *client2 )
{
	REGION **ir = (REGION **) seq;
	Rect *r = &or->valid;
	int le = r->left;
	int to = r->top;
	int bo = IM_RECT_BOTTOM(r);

	IMAGE *c = ir[0]->im;
	IMAGE *a = ir[1]->im;

	int c_elements = r->width * c->Bands;
	int x, y;

	int all0, all255;

	/* Ask for condition pixels.
	 */
	if( im_prepare( ir[0], r ) )
		return( -1 );

	/* Is the conditional all zero or all non-zero? We can avoid asking
	 * for one of the inputs to be calculated.
	 */
	all0 = *((PEL *) IM_REGION_ADDR( ir[0], le, to )) == 0;
	all255 = *((PEL *) IM_REGION_ADDR( ir[0], le, to )) == 255;
	for( y = to; y < bo; y++ ) {
		PEL *p = (PEL *) IM_REGION_ADDR( ir[0], le, y );

		for( x = 0; x < c_elements; x++ ) {
			all0 &= p[x] == 0;
			all255 &= p[x] == 255;
		}

		if( !all0 && !all255 )
			break;
	}

	if( all255 ) {
		/* All 255. Point or at the then image.
		 */
		if( im_prepare( ir[1], r ) ||
			im_region_region( or, ir[1], r, r->left, r->top ) )
			return( -1 );
	}
	else if( all0 ) {
		/* All zero. Point or at the else image.
		 */
		if( im_prepare( ir[2], r ) ||
			im_region_region( or, ir[2], r, r->left, r->top ) )
			return( -1 );
	}
	else {
		/* Mix of set and clear ... ask for both then and else parts and
		 * interleave.
		 */
		if( im_prepare( ir[1], r ) || im_prepare( ir[2], r ) ) 
			return( -1 );

		for( y = to; y < bo; y++ ) {
			PEL *cp = (PEL *) IM_REGION_ADDR( ir[0], le, y );
			PEL *ap = (PEL *) IM_REGION_ADDR( ir[1], le, y );
			PEL *bp = (PEL *) IM_REGION_ADDR( ir[2], le, y );
			PEL *q = (PEL *) IM_REGION_ADDR( or, le, y );

			if( c->Bands == 1 ) 
				blend1_buffer( q, cp, ap, bp, r->width, a );
			else
				blendn_buffer( q, cp, ap, bp, r->width, a );
		}
	}

	return( 0 );
}

static int
blend( IMAGE *c, IMAGE *a, IMAGE *b, IMAGE *out )
{
	IMAGE **in;

	/* Check args.
	 */
	if( im_check_uncoded( "im_blend", c ) ||
		im_check_uncoded( "im_blend", a ) ||
		im_check_uncoded( "im_blend", b ) ||
		im_check_format( "im_blend", c, IM_BANDFMT_UCHAR ) || 
		im_check_format_same( "im_blend", a, b ) ||
		im_check_bands_same( "im_blend", a, b ) ||
		im_check_bands_1orn( "im_blend", c, a ) || 
		im_piocheck( c, out ) || 
		im_pincheck( a ) || 
		im_pincheck( b ) )
                return( -1 );

	/* Make output image.
	 */
	if( im_cp_descv( out, a, b, c, NULL ) )
		return( -1 );
	out->Bands = IM_MAX( c->Bands, a->Bands );
	if( im_demand_hint( out, IM_THINSTRIP, a, b, c, NULL ) )
		return( -1 );

	if( !(in = im_allocate_input_array( out, c, a, b, NULL )) ||
		im_generate( out, 
			im_start_many, blend_gen, im_stop_many, in, NULL ) )
		return( -1 );

	return( 0 );
}

/**
 * im_blend:
 * @c: condition #IMAGE
 * @a: then #IMAGE
 * @b: else #IMAGE
 * @out: output #IMAGE
 *
 * This operation scans the condition image @c (which must be unsigned char) 
 * and uses it to blend pixels from either the then image @a or the else
 * image @b. 255 means @a only, 0 means @b only, and intermediate values are a
 * mixture.
 *
 * Any image can have either 1 band or n bands, where n is the same for all
 * the non-1-band images. Single band images are then effectively copied to 
 * make n-band images.
 *
 * Images @a and @b are cast up to the smallest common format.
 *
 * Images @a and @b must match exactly in size.
 *
 * See also: im_ifthenelse(), im_equal().
 *
 * Returns: 0 on success, -1 on error
 */
int
im_blend( IMAGE *c, IMAGE *a, IMAGE *b, IMAGE *out )
{
	/* If a and b are both LABPACK, repack agan after the blend.
	 */
	const int repack = a->Coding == IM_CODING_LABQ && 
		b->Coding == IM_CODING_LABQ;

	IMAGE *t[7];

	if( im_open_local_array( out, t, 7, "im_blend", "p" ) )
		return( -1 );

	/* Unpack LABPACK as a courtesy.
	 */
	if( a->Coding == IM_CODING_LABQ ) {
		if( im_LabQ2Lab( a, t[0] ) )
			return( -1 );
		a = t[0];
	}
	if( b->Coding == IM_CODING_LABQ ) {
		if( im_LabQ2Lab( b, t[1] ) )
			return( -1 );
		b = t[1];
	}

	/* Make a and b match in bands and format.
	 */
	if( im__formatalike( a, b, t[2], t[3] ) ||
		im__bandalike( t[2], t[3], t[4], t[5] ) )
		return( -1 );

	if( blend( c, t[4], t[5], t[6] ) )
		return( -1 );

	if( repack ) {
		if( im_Lab2LabQ( t[6], out ) )
			return( -1 );
	}
	else {
		if( im_copy( t[6], out ) )
			return( -1 );
	}

	return( 0 );
}
