/* map though a LUT
 *
 * Modified:
 * 18/6/93 JC
 *	- oops! im_incheck() added for LUT image
 * 	- some ANSIfication
 * 15/7/93 JC
 *	- adapted for partial v2
 *	- ANSIfied
 *	- now does complex LUTs too
 * 10/3/94 JC
 *	- more helpful error messages, slight reformatting
 * 24/8/94 JC
 *	- now allows non-uchar image input
 * 7/10/94 JC
 *	- uses im_malloc(), IM_NEW() etc.
 * 13/3/95 JC
 *	- now takes a private copy of LUT, so user can im_close() LUT image
 *	  after im_maplut() without fear of coredumps
 * 23/6/95 JC
 *	- lut may now have many bands if image has just one band
 * 3/3/01 JC
 *	- small speed ups
 * 30/6/04
 *	- heh, 1 band image + 3 band lut + >8bit output has been broken for 9
 *	  years :-)
 * 7/11/07
 * 	- new eval start/end system
 * 25/3/10
 * 	- gtkdoc
 * 	- small cleanups
 * 5/7/13
 * 	- convert to a class
 * 2/10/13
 * 	- add --band arg, replacing im_tone_map()
 */

/*

	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 <glib/gi18n-lib.h>

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

#include <vips/vips.h>

typedef struct _VipsMaplut {
	VipsOperation parent_instance;

	VipsImage *in;
	VipsImage *out;
	VipsImage *lut;
	int band;

	int fmt;		 /* LUT image BandFmt */
	int nb;			 /* Number of bands in lut */
	int es;			 /* VIPS_IMAGE_SIZEOF_ELEMENT() for lut image */
	int sz;			 /* Number of elements in minor dimension */
	int clp;		 /* Value we clip against */
	VipsPel **table; /* Lut converted to 2d array */
	int overflow;	 /* Number of overflows for non-uchar lut */

} VipsMaplut;

typedef VipsOperationClass VipsMaplutClass;

G_DEFINE_TYPE(VipsMaplut, vips_maplut, VIPS_TYPE_OPERATION);

static void
vips_maplut_preeval(VipsImage *image, VipsProgress *progress,
	VipsMaplut *maplut)
{
	maplut->overflow = 0;
}

static void
vips_maplut_posteval(VipsImage *image, VipsProgress *progress,
	VipsMaplut *maplut)
{
	if (maplut->overflow)
		g_warning("%d overflows detected", maplut->overflow);
}

/* Our sequence value: the region this sequence is using, and local stats.
 */
typedef struct {
	VipsRegion *ir; /* Input region */
	int overflow;	/* Number of overflows */
} VipsMaplutSequence;

/* Our start function.
 */
static void *
vips_maplut_start(VipsImage *out, void *a, void *b)
{
	VipsImage *in = (VipsImage *) a;
	VipsMaplutSequence *seq;

	if (!(seq = VIPS_NEW(out, VipsMaplutSequence)))
		return NULL;

	/* Init!
	 */
	seq->ir = NULL;
	seq->overflow = 0;

	if (!(seq->ir = vips_region_new(in)))
		return NULL;

	return seq;
}

/* Map through n non-complex luts.
 */
#define loop(OUT) \
	{ \
		int b = maplut->nb; \
\
		for (y = to; y < bo; y++) { \
			for (z = 0; z < b; z++) { \
				VipsPel *p = VIPS_REGION_ADDR(ir, le, y); \
				OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
				OUT *tlut = (OUT *) maplut->table[z]; \
\
				for (x = z; x < ne; x += b) { \
					unsigned int index = p[x]; \
\
					if (index > maplut->clp) { \
						index = maplut->clp; \
						seq->overflow++; \
					} \
\
					q[x] = tlut[index]; \
				} \
			} \
		} \
	}

/* Map through n complex luts.
 */
#define loopc(OUT) \
	{ \
		int b = in->Bands; \
\
		for (y = to; y < bo; y++) { \
			for (z = 0; z < b; z++) { \
				VipsPel *p = VIPS_REGION_ADDR(ir, le, y) + z; \
				OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y) + z * 2; \
				OUT *tlut = (OUT *) maplut->table[z]; \
\
				for (x = 0; x < ne; x += b) { \
					int n = p[x] * 2; \
\
					q[0] = tlut[n]; \
					q[1] = tlut[n + 1]; \
					q += b * 2; \
				} \
			} \
		} \
	}

#define loopg(IN, OUT) \
	{ \
		int b = maplut->nb; \
\
		for (y = to; y < bo; y++) { \
			for (z = 0; z < b; z++) { \
				IN *p = (IN *) VIPS_REGION_ADDR(ir, le, y); \
				OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
				OUT *tlut = (OUT *) maplut->table[z]; \
\
				for (x = z; x < ne; x += b) { \
					unsigned int index = p[x]; \
\
					if (index > maplut->clp) { \
						index = maplut->clp; \
						seq->overflow++; \
					} \
\
					q[x] = tlut[index]; \
				} \
			} \
		} \
	}

#define loopcg(IN, OUT) \
	{ \
		int b = in->Bands; \
\
		for (y = to; y < bo; y++) { \
			for (z = 0; z < b; z++) { \
				IN *p = (IN *) VIPS_REGION_ADDR(ir, le, y) + z; \
				OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y) + z * 2; \
				OUT *tlut = (OUT *) maplut->table[z]; \
\
				for (x = 0; x < ne; x += b) { \
					unsigned int index = p[x]; \
\
					if (index > maplut->clp) { \
						index = maplut->clp; \
						seq->overflow++; \
					} \
\
					q[0] = tlut[index * 2]; \
					q[1] = tlut[index * 2 + 1]; \
\
					q += b * 2; \
				} \
			} \
		} \
	}

/* Map image through one non-complex lut.
 */
#define loop1(OUT) \
	{ \
		OUT *tlut = (OUT *) maplut->table[0]; \
\
		for (y = to; y < bo; y++) { \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
			VipsPel *p = VIPS_REGION_ADDR(ir, le, y); \
\
			for (x = 0; x < ne; x++) { \
				unsigned int index = p[x]; \
\
				if (index > maplut->clp) { \
					index = maplut->clp; \
					seq->overflow++; \
				} \
\
				q[x] = tlut[index]; \
			} \
		} \
	}

/* Map image through one complex lut.
 */
#define loop1c(OUT) \
	{ \
		OUT *tlut = (OUT *) maplut->table[0]; \
\
		for (y = to; y < bo; y++) { \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
			VipsPel *p = VIPS_REGION_ADDR(ir, le, y); \
\
			for (x = 0; x < ne; x++) { \
				int n = p[x] * 2; \
\
				q[0] = tlut[n]; \
				q[1] = tlut[n + 1]; \
				q += 2; \
			} \
		} \
	}

/* As above, but the input image may be any unsigned integer type. We have to
 * index the lut carefully, and record the number of overflows we detect.
 */
#define loop1g(IN, OUT) \
	{ \
		OUT *tlut = (OUT *) maplut->table[0]; \
\
		for (y = to; y < bo; y++) { \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
			IN *p = (IN *) VIPS_REGION_ADDR(ir, le, y); \
\
			for (x = 0; x < ne; x++) { \
				unsigned int index = p[x]; \
\
				if (index > maplut->clp) { \
					index = maplut->clp; \
					seq->overflow++; \
				} \
\
				q[x] = tlut[index]; \
			} \
		} \
	}

#define loop1cg(IN, OUT) \
	{ \
		OUT *tlut = (OUT *) maplut->table[0]; \
\
		for (y = to; y < bo; y++) { \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
			IN *p = (IN *) VIPS_REGION_ADDR(ir, le, y); \
\
			for (x = 0; x < ne; x++) { \
				unsigned int index = p[x]; \
\
				if (index > maplut->clp) { \
					index = maplut->clp; \
					seq->overflow++; \
				} \
\
				q[0] = tlut[index * 2]; \
				q[1] = tlut[index * 2 + 1]; \
				q += 2; \
			} \
		} \
	}

/* Map 1-band image through a many-band non-complex lut.
 */
#define loop1m(OUT) \
	{ \
		OUT **tlut = (OUT **) maplut->table; \
\
		for (y = to; y < bo; y++) { \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
			VipsPel *p = VIPS_REGION_ADDR(ir, le, y); \
\
			for (i = 0, x = 0; x < np; x++) { \
				unsigned int n = p[x]; \
\
				if (n > maplut->clp) { \
					n = maplut->clp; \
					seq->overflow++; \
				} \
\
				for (z = 0; z < maplut->nb; z++, i++) \
					q[i] = tlut[z][n]; \
			} \
		} \
	}

/* Map 1-band image through many-band complex lut.
 */
#define loop1cm(OUT) \
	{ \
		OUT **tlut = (OUT **) maplut->table; \
\
		for (y = to; y < bo; y++) { \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
			VipsPel *p = VIPS_REGION_ADDR(ir, le, y); \
\
			for (x = 0; x < np; x++) { \
				int n = p[x] * 2; \
\
				for (z = 0; z < maplut->nb; z++) { \
					q[0] = tlut[z][n]; \
					q[1] = tlut[z][n + 1]; \
					q += 2; \
				} \
			} \
		} \
	}

/* Map 1-band uint or ushort image through a many-band non-complex LUT.
 */
#define loop1gm(IN, OUT) \
	{ \
		OUT **tlut = (OUT **) maplut->table; \
\
		for (y = to; y < bo; y++) { \
			IN *p = (IN *) VIPS_REGION_ADDR(ir, le, y); \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
\
			for (i = 0, x = 0; x < np; x++) { \
				unsigned int n = p[x]; \
\
				if (n > maplut->clp) { \
					n = maplut->clp; \
					seq->overflow++; \
				} \
\
				for (z = 0; z < maplut->nb; z++, i++) \
					q[i] = tlut[z][n]; \
			} \
		} \
	}

/* Map 1-band uint or ushort image through a many-band complex LUT.
 */
#define loop1cgm(IN, OUT) \
	{ \
		OUT **tlut = (OUT **) maplut->table; \
\
		for (y = to; y < bo; y++) { \
			IN *p = (IN *) VIPS_REGION_ADDR(ir, le, y); \
			OUT *q = (OUT *) VIPS_REGION_ADDR(out_region, le, y); \
\
			for (x = 0; x < np; x++) { \
				unsigned int n = p[x]; \
\
				if (n > maplut->clp) { \
					n = maplut->clp; \
					seq->overflow++; \
				} \
\
				for (z = 0; z < maplut->nb; z++) { \
					q[0] = tlut[z][n * 2]; \
					q[1] = tlut[z][n * 2 + 1]; \
					q += 2; \
				} \
			} \
		} \
	}

/* Switch for input types. Has to be uint type!
 */
#define inner_switch(UCHAR, GEN, OUT) \
	switch (ir->im->BandFmt) { \
	case VIPS_FORMAT_UCHAR: \
		UCHAR(OUT); \
		break; \
	case VIPS_FORMAT_USHORT: \
		GEN(unsigned short, OUT); \
		break; \
	case VIPS_FORMAT_UINT: \
		GEN(unsigned int, OUT); \
		break; \
	default: \
		g_assert_not_reached(); \
	}

/* Switch for LUT types. One function for non-complex images, a
 * variant for complex ones. Another pair as well, in case the input is not
 * uchar.
 */
#define outer_switch(UCHAR_F, UCHAR_FC, GEN_F, GEN_FC) \
	switch (maplut->fmt) { \
	case VIPS_FORMAT_UCHAR: \
		inner_switch(UCHAR_F, GEN_F, unsigned char); \
		break; \
	case VIPS_FORMAT_CHAR: \
		inner_switch(UCHAR_F, GEN_F, char); \
		break; \
	case VIPS_FORMAT_USHORT: \
		inner_switch(UCHAR_F, GEN_F, unsigned short); \
		break; \
	case VIPS_FORMAT_SHORT: \
		inner_switch(UCHAR_F, GEN_F, short); \
		break; \
	case VIPS_FORMAT_UINT: \
		inner_switch(UCHAR_F, GEN_F, unsigned int); \
		break; \
	case VIPS_FORMAT_INT: \
		inner_switch(UCHAR_F, GEN_F, int); \
		break; \
	case VIPS_FORMAT_FLOAT: \
		inner_switch(UCHAR_F, GEN_F, float); \
		break; \
	case VIPS_FORMAT_DOUBLE: \
		inner_switch(UCHAR_F, GEN_F, double); \
		break; \
	case VIPS_FORMAT_COMPLEX: \
		inner_switch(UCHAR_FC, GEN_FC, float); \
		break; \
	case VIPS_FORMAT_DPCOMPLEX: \
		inner_switch(UCHAR_FC, GEN_FC, double); \
		break; \
	default: \
		g_assert_not_reached(); \
	}

/* Do a map.
 */
static int
vips_maplut_gen(VipsRegion *out_region,
	void *vseq, void *a, void *b, gboolean *stop)
{
	VipsMaplutSequence *seq = (VipsMaplutSequence *) vseq;
	VipsImage *in = (VipsImage *) a;
	VipsMaplut *maplut = (VipsMaplut *) b;
	VipsRegion *ir = seq->ir;
	VipsRect *r = &out_region->valid;
	int le = r->left;
	int to = r->top;
	int bo = VIPS_RECT_BOTTOM(r);
	int np = r->width;							 /* Pels across region */
	int ne = VIPS_REGION_N_ELEMENTS(out_region); /* Number of elements */

	int x, y, z, i;

	if (vips_region_prepare(ir, r))
		return -1;

	/* clang-format off */
	if (maplut->nb == 1)
		/* One band lut.
		 */
		outer_switch(loop1, loop1c, loop1g, loop1cg)
	else
		/* Many band lut.
		 */
		if (in->Bands == 1)
			/* ... but 1 band input.
			 */
			outer_switch(loop1m, loop1cm, loop1gm, loop1cgm)
		else
			outer_switch(loop, loopc, loopg, loopcg)

	return 0;
	/* clang-format on */
}

/* Destroy a sequence value.
 */
static int
vips_maplut_stop(void *vseq, void *a, void *b)
{
	VipsMaplutSequence *seq = (VipsMaplutSequence *) vseq;
	VipsMaplut *maplut = (VipsMaplut *) b;

	/* Add to global stats.
	 */
	maplut->overflow += seq->overflow;

	VIPS_UNREF(seq->ir);

	return 0;
}

/* Save a bit of typing.
 */
#define UC VIPS_FORMAT_UCHAR
#define US VIPS_FORMAT_USHORT
#define UI VIPS_FORMAT_UINT

/* Type mapping: go to uchar / ushort / uint to make an index.
 */
static const VipsBandFormat bandfmt_maplut[10] = {
	/* Band format:  UC  C   US  S   UI  I   F   X   D   DX */
	/* Promotion: */ UC, UC, US, US, UI, UI, UI, UI, UI, UI
};

/* Repack lut into a set of band arrays. If we're just passing one band of the
 * image through the lut, put the identity function in the other bands.
 */
#define PACK_TABLE(TYPE) \
	{ \
		TYPE *data = (TYPE *) lut->data; \
		int x, b; \
\
		for (x = 0; x < maplut->sz; x++) \
			for (b = 0; b < maplut->nb; b++) { \
				TYPE *q = (TYPE *) maplut->table[b]; \
\
				if (maplut->band >= 0 && \
					lut->Bands == 1) { \
					if (b == maplut->band) \
						q[x] = data[x]; \
					else \
						q[x] = x; \
				} \
				else \
					q[x] = data[x * lut->Bands + b]; \
			} \
	}

#define PACK_TABLEC(TYPE) \
	{ \
		TYPE *data = (TYPE *) lut->data; \
		int x, b; \
\
		for (x = 0; x < maplut->sz; x++) \
			for (b = 0; b < maplut->nb; b++) { \
				TYPE *q = (TYPE *) maplut->table[b]; \
\
				if (maplut->band >= 0 && \
					lut->Bands == 1) { \
					if (b == maplut->band) { \
						q[2 * x] = data[2 * x]; \
						q[2 * x + 1] = data[2 * x + 1]; \
					} \
					else { \
						q[2 * x] = x; \
						q[2 * x + 1] = 0; \
					} \
				} \
				else { \
					q[2 * x] = data[2 * (x * lut->Bands + b)]; \
					q[2 * x + 1] = \
						data[2 * (x * lut->Bands + b) + 1]; \
				} \
			} \
	}

static int
vips_maplut_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsMaplut *maplut = (VipsMaplut *) object;
	VipsImage **t = (VipsImage **) vips_object_local_array(object, 2);

	VipsImage *in;
	VipsImage *lut;
	int i;

	g_object_set(object, "out", vips_image_new(), NULL);

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

	in = maplut->in;
	lut = maplut->lut;

	if (vips_check_hist(class->nickname, lut) ||
		vips_check_uncoded(class->nickname, lut))
		return -1;

	/* Cast @in to u8/u16/u32 to make the index image.
	 */
	if (vips_cast(in, &t[0], bandfmt_maplut[in->BandFmt], NULL))
		return -1;
	in = t[0];

	if (vips_check_uncoded(class->nickname, in) ||
		vips_check_bands_1orn(class->nickname, in, lut) ||
		vips_image_pio_input(in))
		return -1;

	if (vips_image_pipelinev(maplut->out,
			VIPS_DEMAND_STYLE_THINSTRIP, in, lut, NULL))
		return -1;
	maplut->out->BandFmt = lut->BandFmt;

	/* Output has same number of bands as LUT, unless LUT has 1 band, in
	 * which case output has same number of bands as input.
	 */
	if (lut->Bands != 1)
		maplut->out->Bands = lut->Bands;

	/* The Type comes from the image with many bands. A B_W index image,
	 * for example, needs to become an RGB image when it goes through a
	 * three-band LUT.
	 */
	if (lut->Bands != 1)
		maplut->out->Type = lut->Type;

	/* We can still set crazy interpretations -- for example, a many-band LUT
	 * made with identity will be a histogram, but we don't want that for the
	 * output image.
	 *
	 * Replace any crazy interpretations with the default.
	 */
	maplut->out->Type = vips_image_guess_interpretation(maplut->out);

	g_signal_connect(in, "preeval",
		G_CALLBACK(vips_maplut_preeval), maplut);
	g_signal_connect(in, "posteval",
		G_CALLBACK(vips_maplut_posteval), maplut);

	/* Make luts. We unpack the LUT image into a 2D C array to speed
	 * processing.
	 */
	if (!(t[1] = vips_image_copy_memory(lut)))
		return -1;
	lut = t[1];
	maplut->fmt = lut->BandFmt;
	maplut->es = VIPS_IMAGE_SIZEOF_ELEMENT(lut);
	maplut->sz = lut->Xsize * lut->Ysize;
	maplut->clp = maplut->sz - 1;

	/* If @bands is >= 0, we need to expand the lut to the number of bands
	 * in the input image.
	 */
	if (maplut->band >= 0 &&
		lut->Bands == 1)
		maplut->nb = in->Bands;
	else
		maplut->nb = lut->Bands;

	/* Attach tables.
	 */
	if (!(maplut->table = VIPS_ARRAY(maplut, maplut->nb, VipsPel *)))
		return -1;
	for (i = 0; i < maplut->nb; i++)
		if (!(maplut->table[i] = VIPS_ARRAY(maplut,
				  maplut->sz * maplut->es, VipsPel)))
			return -1;

	/* Scan LUT and fill table.
	 */
	switch (lut->BandFmt) {
	case VIPS_FORMAT_UCHAR:
		PACK_TABLE(unsigned char);
		break;
	case VIPS_FORMAT_CHAR:
		PACK_TABLE(char);
		break;
	case VIPS_FORMAT_USHORT:
		PACK_TABLE(unsigned short);
		break;
	case VIPS_FORMAT_SHORT:
		PACK_TABLE(short);
		break;
	case VIPS_FORMAT_UINT:
		PACK_TABLE(unsigned int);
		break;
	case VIPS_FORMAT_INT:
		PACK_TABLE(int);
		break;
	case VIPS_FORMAT_FLOAT:
		PACK_TABLE(float);
		break;
	case VIPS_FORMAT_DOUBLE:
		PACK_TABLE(double);
		break;
	case VIPS_FORMAT_COMPLEX:
		PACK_TABLEC(float);
		break;
	case VIPS_FORMAT_DPCOMPLEX:
		PACK_TABLEC(double);
		break;
	default:
		g_assert_not_reached();
	}

	if (vips_image_generate(maplut->out,
			vips_maplut_start, vips_maplut_gen, vips_maplut_stop,
			in, maplut))
		return -1;

	return 0;
}

static void
vips_maplut_class_init(VipsMaplutClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = VIPS_OBJECT_CLASS(class);
	VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);

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

	object_class->nickname = "maplut";
	object_class->description = _("map an image though a lut");
	object_class->build = vips_maplut_build;

	operation_class->flags = VIPS_OPERATION_SEQUENTIAL;

	VIPS_ARG_IMAGE(class, "in", 1,
		_("Input"),
		_("Input image"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsMaplut, in));

	VIPS_ARG_IMAGE(class, "out", 2,
		_("Output"),
		_("Output image"),
		VIPS_ARGUMENT_REQUIRED_OUTPUT,
		G_STRUCT_OFFSET(VipsMaplut, out));

	VIPS_ARG_IMAGE(class, "lut", 3,
		_("LUT"),
		_("Look-up table image"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsMaplut, lut));

	VIPS_ARG_INT(class, "band", 4,
		_("Band"),
		_("Apply one-band lut to this band of in"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsMaplut, band),
		-1, 10000, -1);
}

static void
vips_maplut_init(VipsMaplut *maplut)
{
	maplut->band = -1;
}

/**
 * vips_maplut: (method)
 * @in: input image
 * @out: (out): output image
 * @lut: look-up table
 * @...: `NULL`-terminated list of optional named arguments
 *
 * Map an image through another image acting as a LUT (Look Up Table).
 * The lut may have any type and the output image will be that type.
 *
 * The input image will be cast to one of the unsigned integer types, that is,
 * [enum@Vips.BandFormat.UCHAR], [enum@Vips.BandFormat.USHORT] or [enum@Vips.BandFormat.UINT].
 *
 * If @lut is too small for the input type (for example, if @in is
 * [enum@Vips.BandFormat.UCHAR] but @lut only has 100 elements), the lut is padded out
 * by copying the last element. Overflows are reported at the end of
 * computation.
 * If @lut is too large, extra values are ignored.
 *
 * If @lut has one band and @band is -1 (the default), then all bands of @in
 * pass through @lut. If @band is >= 0, then just that band of @in passes
 * through @lut and other bands are just copied.
 *
 * If @lut
 * has same number of bands as @in, then each band is mapped
 * separately. If @in has one band, then @lut may have many bands and
 * the output will have the same number of bands as @lut.
 *
 * ::: tip "Optional arguments"
 *     * @band: `gint`, apply one-band @lut to this band of @in
 *
 * ::: seealso
 *     [method@Image.hist_find], [ctor@Image.identity].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_maplut(VipsImage *in, VipsImage **out, VipsImage *lut, ...)
{
	va_list ap;
	int result;

	va_start(ap, lut);
	result = vips_call_split("maplut", ap, in, out, lut);
	va_end(ap);

	return result;
}
