/* ifthenelse.c -- use a condition image to join two images together
 *
 * Modified:
 * 9/2/95 JC
 *	- partialed and ANSIfied
 * 11/9/95 JC
 *	- return( 0 ) missing! oops
 * 15/4/05
 *	- now just evals left/right if all zero/all one
 * 7/10/06
 * 	- set THINSTRIP
 * 23/9/09
 * 	- gtkdoc comment
 * 23/9/09
 * 	- use im_check*()
 * 	- allow many-band conditional and single-band a/b
 * 	- allow a/b to differ in format and bands
 * 25/6/10
 * 	- let the conditional image be any format by adding a (!=0) if
 * 	  necessary
 * 17/5/11
 * 	- added sizealike
 * 14/11/11
 * 	- redone as a class
 * 19/4/12
 * 	- fix blend
 * 	- small blend speedup
 */

/*

	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

 */

/*
#define VIPS_DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

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

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

#include "pconversion.h"

typedef struct _VipsIfthenelse {
	VipsConversion parent_instance;

	/* Params.
	 */
	VipsImage *cond;
	VipsImage *in1;
	VipsImage *in2;

	gboolean blend;

} VipsIfthenelse;

typedef VipsConversionClass VipsIfthenelseClass;

G_DEFINE_TYPE(VipsIfthenelse, vips_ifthenelse, VIPS_TYPE_CONVERSION);

#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
vips_blend1_buffer(VipsPel *qp,
	VipsPel *c, VipsPel *ap, VipsPel *bp, int width,
	VipsImage *im)
{
	int i, x, z;
	const int bands = im->Bands;
	const int n = width * bands;

	switch (im->BandFmt) {
	case VIPS_FORMAT_UCHAR:
		IBLEND1(unsigned char);
		break;
	case VIPS_FORMAT_CHAR:
		IBLEND1(signed char);
		break;
	case VIPS_FORMAT_USHORT:
		IBLEND1(unsigned short);
		break;
	case VIPS_FORMAT_SHORT:
		IBLEND1(signed short);
		break;
	case VIPS_FORMAT_UINT:
		IBLEND1(unsigned int);
		break;
	case VIPS_FORMAT_INT:
		IBLEND1(signed int);
		break;
	case VIPS_FORMAT_FLOAT:
		FBLEND1(float);
		break;
	case VIPS_FORMAT_DOUBLE:
		FBLEND1(double);
		break;
	case VIPS_FORMAT_COMPLEX:
		CBLEND1(float);
		break;
	case VIPS_FORMAT_DPCOMPLEX:
		CBLEND1(double);
		break;

	default:
		g_assert_not_reached();
	}
}

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

	switch (im->BandFmt) {
	case VIPS_FORMAT_UCHAR:
		IBLENDN(unsigned char);
		break;
	case VIPS_FORMAT_CHAR:
		IBLENDN(signed char);
		break;
	case VIPS_FORMAT_USHORT:
		IBLENDN(unsigned short);
		break;
	case VIPS_FORMAT_SHORT:
		IBLENDN(signed short);
		break;
	case VIPS_FORMAT_UINT:
		IBLENDN(unsigned int);
		break;
	case VIPS_FORMAT_INT:
		IBLENDN(signed int);
		break;
	case VIPS_FORMAT_FLOAT:
		FBLENDN(float);
		break;
	case VIPS_FORMAT_DOUBLE:
		FBLENDN(double);
		break;
	case VIPS_FORMAT_COMPLEX:
		CBLENDN(float);
		break;
	case VIPS_FORMAT_DPCOMPLEX:
		CBLENDN(double);
		break;

	default:
		g_assert_not_reached();
	}
}

static int
vips_blend_gen(VipsRegion *out_region,
	void *seq, void *client1, void *client2, gboolean *stop)
{
	VipsRegion **ir = (VipsRegion **) seq;
	VipsRect *r = &out_region->valid;
	int le = r->left;
	int to = r->top;
	int bo = VIPS_RECT_BOTTOM(r);

	VipsImage *c = ir[2]->im;
	VipsImage *a = ir[0]->im;

	int x, y;
	int all0, all255;

	if (vips_region_prepare(ir[2], r))
		return -1;

	/* Is the conditional all zero or all 255? We can avoid asking
	 * for one of the inputs to be calculated.
	 */
	all0 = *VIPS_REGION_ADDR(ir[2], le, to) == 0;
	all255 = *VIPS_REGION_ADDR(ir[2], le, to) == 255;
	for (y = to; y < bo; y++) {
		VipsPel *p = VIPS_REGION_ADDR(ir[2], le, y);
		int width = r->width * c->Bands;

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

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

	if (all255) {
		/* All 255. Point or at the then image.
		 */
		if (vips_region_prepare(ir[0], r) ||
			vips_region_region(out_region, ir[0], r, r->left, r->top))
			return -1;
	}
	else if (all0) {
		/* All zero. Point or at the else image.
		 */
		if (vips_region_prepare(ir[1], r) ||
			vips_region_region(out_region, ir[1], r, r->left, r->top))
			return -1;
	}
	else {
		/* Mix of set and clear ... ask for both then and else parts
		 * and interleave.
		 *
		 * We can't use vips_reorder_prepare_many() since we always
		 * want the c image first.
		 */
		if (vips_region_prepare(ir[0], r) ||
			vips_region_prepare(ir[1], r))
			return -1;

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

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

	return 0;
}

static int
vips_ifthenelse_gen(VipsRegion *out_region,
	void *seq, void *client1, void *client2, gboolean *stop)
{
	VipsRegion **ir = (VipsRegion **) seq;
	VipsRect *r = &out_region->valid;
	int le = r->left;
	int to = r->top;
	int bo = VIPS_RECT_BOTTOM(r);

	VipsImage *c = ir[2]->im;
	VipsImage *a = ir[0]->im;

	int size, width;
	int i, x, y, z;

	int all0, alln0;

	if (c->Bands == 1) {
		/* Copying PEL-sized units with a one-band conditional.
		 */
		size = VIPS_IMAGE_SIZEOF_PEL(a);
		width = r->width;
	}
	else {
		/* Copying ELEMENT sized-units with an n-band conditional.
		 */
		size = VIPS_IMAGE_SIZEOF_ELEMENT(a);
		width = r->width * a->Bands;
	}

	if (vips_region_prepare(ir[2], 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 = *VIPS_REGION_ADDR(ir[2], le, to) == 0;
	alln0 = *VIPS_REGION_ADDR(ir[2], le, to) != 0;
	for (y = to; y < bo; y++) {
		VipsPel *p = VIPS_REGION_ADDR(ir[2], le, y);

		for (x = 0; x < width; x++) {
			all0 &= p[x] == 0;
			alln0 &= p[x] != 0;
		}

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

	if (alln0) {
		/* All non-zero. Point or at the then image.
		 */
		if (vips_region_prepare(ir[0], r) ||
			vips_region_region(out_region, ir[0], r, r->left, r->top))
			return -1;
	}
	else if (all0) {
		/* All zero. Point or at the else image.
		 */
		if (vips_region_prepare(ir[1], r) ||
			vips_region_region(out_region, ir[1], r, r->left, r->top))
			return -1;
	}
	else {
		/* Mix of set and clear ... ask for both then and else parts
		 * and interleave.
		 */
		if (vips_region_prepare(ir[0], r) ||
			vips_region_prepare(ir[1], r))
			return -1;

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

			for (x = 0, i = 0; i < width; i++, x += size)
				if (cp[i])
					for (z = x; z < x + size; z++)
						q[z] = ap[z];
				else
					for (z = x; z < x + size; z++)
						q[z] = bp[z];
		}
	}

	return 0;
}

static int
vips_ifthenelse_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsConversion *conversion = VIPS_CONVERSION(object);
	VipsIfthenelse *ifthenelse = (VipsIfthenelse *) object;
	VipsGenerateFn generate_fn = ifthenelse->blend
		? vips_blend_gen
		: vips_ifthenelse_gen;

	VipsImage **band = (VipsImage **) vips_object_local_array(object, 3);
	VipsImage **size = (VipsImage **) vips_object_local_array(object, 3);
	VipsImage **format =
		(VipsImage **) vips_object_local_array(object, 3);

	VipsImage *all[3];

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

	/* We have to have the condition image last since we want the output
	 * image to inherit its properties from the then/else parts.
	 */
	all[0] = ifthenelse->in1;
	all[1] = ifthenelse->in2;
	all[2] = ifthenelse->cond;

	/* No need to check input images, sizealike and friends will do this
	 * for us.
	 */

	/* Cast our input images up to a common bands and size.
	 */
	if (vips__bandalike_vec(class->nickname, all, band, 3, 0) ||
		vips__sizealike_vec(band, size, 3))
		return -1;

	/* Condition is cast to uchar, then/else to a common type.
	 */
	if (size[2]->BandFmt != VIPS_FORMAT_UCHAR) {
		if (vips_cast(size[2], &format[2], VIPS_FORMAT_UCHAR, NULL))
			return -1;
	}
	else {
		format[2] = size[2];
		g_object_ref(format[2]);
	}

	if (vips__formatalike_vec(size, format, 2))
		return -1;

	if (vips_image_pipeline_array(conversion->out,
			VIPS_DEMAND_STYLE_SMALLTILE, format))
		return -1;

	if (vips_image_generate(conversion->out,
			vips_start_many, generate_fn, vips_stop_many,
			format, ifthenelse))
		return -1;

	return 0;
}

static void
vips_ifthenelse_class_init(VipsIfthenelseClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class);

	VIPS_DEBUG_MSG("vips_ifthenelse_class_init\n");

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

	vobject_class->nickname = "ifthenelse";
	vobject_class->description = _("ifthenelse an image");
	vobject_class->build = vips_ifthenelse_build;

	VIPS_ARG_IMAGE(class, "cond", -2,
		_("Condition"),
		_("Condition input image"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsIfthenelse, cond));

	VIPS_ARG_IMAGE(class, "in1", -1,
		_("Then image"),
		_("Source for TRUE pixels"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsIfthenelse, in1));

	VIPS_ARG_IMAGE(class, "in2", 0,
		_("Else image"),
		_("Source for FALSE pixels"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsIfthenelse, in2));

	VIPS_ARG_BOOL(class, "blend", 4,
		_("Blend"),
		_("Blend smoothly between then and else parts"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsIfthenelse, blend),
		FALSE);
}

static void
vips_ifthenelse_init(VipsIfthenelse *ifthenelse)
{
}

/**
 * vips_ifthenelse: (method)
 * @cond: condition [class@Image]
 * @in1: then [class@Image]
 * @in2: else [class@Image]
 * @out: (out): output [class@Image]
 * @...: `NULL`-terminated list of optional named arguments
 *
 * This operation scans the condition image @cond
 * and uses it to select pixels from either the then image @in1 or the else
 * image @in2. Non-zero means @in1, 0 means @in2.
 *
 * 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 @in1 and @in2 are cast up to the smallest common format. @cond is
 * cast to uchar.
 *
 * If the images differ in size, the smaller images are enlarged to match the
 * largest by adding zero pixels along the bottom and right.
 *
 * If @blend is `TRUE`, then values in @out are smoothly blended between @in1
 * and @in2 using the formula:
 *
 * ```
 * out = (cond / 255) * in1 + (1 - cond / 255) * in2
 * ```
 *
 * ::: tip "Optional arguments"
 *     * @blend: `gboolean`, blend smoothly between @in1 and @in2
 *
 * ::: seealso
 *     [method@Image.equal].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_ifthenelse(VipsImage *cond, VipsImage *in1, VipsImage *in2,
	VipsImage **out, ...)
{
	va_list ap;
	int result;

	va_start(ap, out);
	result = vips_call_split("ifthenelse", ap, cond, in1, in2, out);
	va_end(ap);

	return result;
}
