/* Parse ".desc" files from mosaiced images to generate (x,y) offsets for
 * every sub-image. Find all overlap stats and solve balancing with LMS.
 * Regenerate mosaic, with balancing fixed.
 *
 * 1/12/93 JC
 *	- first version, unfinished!
 * 6/9/95 JC
 *	- LMS fixed, now works, more or less
 * 12/9/95 JC
 *	- now does positions correctly too
 *	- ignores trivial overlaps
 * 19/9/95 JC
 *	- prints correct number of balance factors!
 * 10/11/95 JC
 *	- now tracks im_copy() calls too, so you can save sub-images
 * 12/1/96 JC
 *	- slightly clearer diagnostics
 *	- better centre of factors around 1.0 with log() average
 * 1/3/96 JC
 *	- new im_global_balance_float variant lets our caller adjust factor
 *	  range if output has burn-out
 *	- im_global_balance_search uses the above to produce scaled output ...
 *	  very slow!
 * 11/3/96 JC
 *	- now tries current directory too for input files
 * 22/3/96 JC
 *	- horrible bug in position finding! now fixed
 * 1/8/97 JC
 *	- revised for new mosaic functions and non-square images
 * 12/9/97 JC
 *	- code for im_lrmosaic1() support
 *	- output type == input type, so works for short images too
 * 6/1/99 JC
 *	- new gamma parameter, do scale in linear space
 *	- removed _search version, as can now be done with ip
 *	- renamed _float to f suffix, in line with im_conv()/im_convf()
 * 15/2/00 JC
 *	- balancef() did not scale in linear space
 * 2/2/01 JC
 *	- added tunable max blend width
 * 7/11/01 JC
 *	- global_balance.h broken out for im_remosaic()
 * 25/02/02 JC
 *	- better transform function scheme
 * 21/3/01 JC
 *	- quicker bailout on error
 * 8/11/02 JC
 * 	- add <> around file names so you can have spaces :(
 * 9/12/02 JC
 *	- track original params and always reuse them ... makes us proof
 *	  against geo reconstruct errors
 * 10/3/03 JC
 *	- weed out overlaps which contain only transparent pixels
 * 4/1/07
 * 	- switch to new history thing, switch im_errormsg() too
 * 24/1/11
 * 	- gtk-doc
 * 12/7/12
 * 	- always allocate local to an output descriptor ... stops ref cycles
 * 	  with the new base class
 * 18/6/20 kleisauke
 * 	- convert to vips8
 */

/*

	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

 */

/* Strategy: build a tree describing the file
 * relationships in the desc file, then walk that passing constraints
 * back up to the root. Look up file names in symbol_table.
 */

/* Define for debug output.
#define DEBUG
 */

#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 <stdarg.h>
#include <math.h>

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

#include "pmosaicing.h"
#include "global_balance.h"

#define MAX_ITEMS (50)

/* How pix an overlap has to be (in pixels) before we think it's trivial and
 * we ignore it.
 */
#define TRIVIAL (20 * 20)

/* Break a string into a list of strings. Write '\0's into the string. out
 * needs to be MAX_FILES long. -1 for error, otherwise number of args found.

	"<fred> <jim poop> <sn aff le>"

	out[0] = "fred"
	out[1] = "jim poop"
	out[2] = "sn aff le"

 */
static int
break_items(char *line, char **out)
{
	int i;
	char *p;

	for (i = 0; i < MAX_ITEMS; i++) {
		/* Skip to first '<'.
		 */
		if (!(p = strchr(line, '<')))
			break;

		out[i] = line = p + 1;

		if (!(p = strchr(line, '>'))) {
			vips_error("break_files", "%s", _("no matching '>'"));
			return -1;
		}

		*p = '\0';
		line = p + 1;
	}

	if (i == MAX_ITEMS) {
		vips_error("break_files", "%s", _("too many items"));
		return -1;
	}

	return i;
}

/* Try to open a file. If full path fails, try the current directory.
 */
VipsImage *
vips__global_open_image(SymbolTable *st, char *name)
{
	char *basename;
	VipsImage *image;

	if (!(image = vips_image_new_from_file(name, NULL))) {
		/* TODO(kleisauke): Is this behavior the same as im_skip_dir?
		 * i.e. could we open a filename which came
		 * from a win32 (`\\`) on a *nix machine?
		 */
		basename = g_path_get_basename(name);

		if (!(image = vips_image_new_from_file(basename, NULL))) {
			g_free(basename);
			return NULL;
		}

		g_free(basename);
	}

	vips_object_local(st->im, image);

	return image;
}

static void
junk_node(VipsImage *image, JoinNode *node)
{
	VIPS_FREEF(g_slist_free, node->overlaps);
}

/* Hash from a filename to an index into symbol_table.
 */
static int
hash(char *n)
{
	int i;
	int r = 0;
	int l = strlen(n);

	for (i = 0; i < l; i++)
		r = ((r + n[i]) * 43) & 0xffffff;

	return r % SYM_TAB_SIZE;
}

/* Make a leaf for a file.
 */
static JoinNode *
build_node(SymbolTable *st, char *name)
{
	JoinNode *node = VIPS_NEW(st->im, JoinNode);
	int n = hash(name);

	/* Fill fields.
	 */
	if (!node ||
		!(node->name = vips_strdup(VIPS_OBJECT(st->im), name)))
		return NULL;

	node->type = JOIN_LEAF;
	node->dirty = 0;
	node->mwidth = -2;
	node->st = st;
	vips__transform_init(&node->cumtrn);
	node->trnim = NULL;
	node->arg1 = NULL;
	node->arg2 = NULL;
	node->overlaps = NULL;
	node->im = NULL;
	node->index = 0;

	g_signal_connect(st->im, "close",
		G_CALLBACK(junk_node), node);

	/* Try to open.
	 */
	if ((node->im = vips__global_open_image(st, name))) {
		/* There is a file there - set width and height.
		 */
		node->cumtrn.oarea.width = node->im->Xsize;
		node->cumtrn.oarea.height = node->im->Ysize;
	}
	else {
		/* Clear the error buffer to lessen confusion.
		 */
		vips_error_clear();
	}

	st->table[n] = g_slist_prepend(st->table[n], node);

	return node;
}

/* Make a new overlap struct.
 */
static OverlapInfo *
build_overlap(JoinNode *node, JoinNode *other, VipsRect *overlap)
{
	OverlapInfo *lap = VIPS_NEW(node->st->im, OverlapInfo);

	if (!lap)
		return NULL;

	lap->node = node;
	lap->other = other;
	lap->overlap = *overlap;
	lap->nstats = NULL;
	lap->ostats = NULL;
	node->overlaps = g_slist_prepend(node->overlaps, lap);
	node->st->novl++;

	return lap;
}

static void
overlap_destroy(OverlapInfo *lap)
{
	JoinNode *node = lap->node;

	node->overlaps = g_slist_remove(node->overlaps, lap);
	g_assert(node->st->novl > 0);
	node->st->novl--;
}

static void
junk_table(VipsImage *image, SymbolTable *st)
{
	int i;

	for (i = 0; i < st->sz; i++)
		VIPS_FREEF(g_slist_free, st->table[i]);
}

/* Build a new symbol table.
 */
SymbolTable *
vips__build_symtab(VipsImage *out, int sz)
{
	SymbolTable *st = VIPS_NEW(out, SymbolTable);
	int i;

	if (!st ||
		!(st->table = VIPS_ARRAY(out, sz, GSList *)))
		return NULL;
	st->sz = sz;
	st->im = out;
	st->novl = 0;
	st->nim = 0;
	st->njoin = 0;
	st->root = NULL;
	st->leaf = NULL;
	st->fac = NULL;

	g_signal_connect(out, "close",
		G_CALLBACK(junk_table), st);

	for (i = 0; i < sz; i++)
		st->table[i] = NULL;

	return st;
}

/* Does this node have this file name?
 */
static JoinNode *
test_name(JoinNode *node, char *name, void *b)
{
	if (strcmp(node->name, name) == 0)
		return node;
	else
		return NULL;
}

/* Look up a filename in the symbol_table.
 */
static JoinNode *
find_node(SymbolTable *st, char *name)
{
	return vips_slist_map2(st->table[hash(name)],
		(VipsSListMap2Fn) test_name, name, NULL);
}

/* Given a name: return either the existing node for that name, or a new node
 * we have made.
 */
static JoinNode *
add_node(SymbolTable *st, char *name)
{
	JoinNode *node;

	if (!(node = find_node(st, name)) &&
		!(node = build_node(st, name)))
		return NULL;

	return node;
}

/* Map a user function over the whole of the symbol table.
 */
void *
vips__map_table(SymbolTable *st, VipsSListMap2Fn fn, void *a, void *b)
{
	int i;
	void *r;

	for (i = 0; i < st->sz; i++)
		if ((r = vips_slist_map2(st->table[i], fn, a, b)))
			return r;

	return NULL;
}

/* Set the dirty field on a join.
 */
static void *
set_dirty(JoinNode *node, int state, void *b)
{
	node->dirty = state;

	return NULL;
}

/* Clean the whole table.
 */
static void
clean_table(SymbolTable *st)
{
	(void) vips__map_table(st,
		(VipsSListMap2Fn) set_dirty, (void *) 0, NULL);
}

/* Do geometry calculations on a node, assuming geo is up to date for any
 * children.
 */
static void
calc_geometry(JoinNode *node)
{
	VipsRect um;

	switch (node->type) {
	case JOIN_LR:
	case JOIN_TB:
	case JOIN_LRROTSCALE:
	case JOIN_TBROTSCALE:
		/* Join two areas.
		 */
		vips_rect_unionrect(&node->arg1->cumtrn.oarea,
			&node->arg2->cumtrn.oarea, &um);
		node->cumtrn.iarea.left = 0;
		node->cumtrn.iarea.top = 0;
		node->cumtrn.iarea.width = um.width;
		node->cumtrn.iarea.height = um.height;
		vips__transform_set_area(&node->cumtrn);
		break;

	case JOIN_CP:
		/* Copy from child.
		 */
		node->cumtrn = node->arg1->cumtrn;
		break;

	case JOIN_LEAF:
		/* Just use leaf dimensions, if there are any.
		 */
		if (node->im) {
			node->cumtrn.iarea.left = 0;
			node->cumtrn.iarea.top = 0;
			node->cumtrn.iarea.width = node->im->Xsize;
			node->cumtrn.iarea.height = node->im->Ysize;
			vips__transform_set_area(&node->cumtrn);
		}
		break;

	default:
		vips_error_exit("internal error #98356");
		/*NOTREACHED*/
	}
}

/* Propagate a transform down a tree. If dirty is set, we've been here before,
 * so there is a doubling up of this node. If this is a leaf, then we have the
 * same leaf twice (which, in fact, we can cope with); if this is a node, we
 * have circularity.
 */
static int
propagate_transform(JoinNode *node, VipsTransformation *trn)
{
	if (!node)
		return 0;

	if (node->dirty && node->arg1 && node->arg2) {
		vips_error("vips_global_balance",
			"%s", _("circularity detected"));
		return -1;
	}
	node->dirty = 1;

	/* Transform our children.
	 */
	if (propagate_transform(node->arg1, trn) ||
		propagate_transform(node->arg2, trn))
		return -1;

	/* Transform us, and recalculate our position and size.
	 */
	vips__transform_add(&node->cumtrn, trn, &node->cumtrn);
	calc_geometry(node);

	return 0;
}

/* Ah ha! A leaf is actually made up of two smaller files with an lr or a tb
 * merge. Turn a leaf node into a join node. Propagate the transform down
 * arg2's side of the tree.
 */
static int
make_join(SymbolTable *st, JoinType type,
	JoinNode *arg1, JoinNode *arg2, JoinNode *out,
	double a, double b, double dx, double dy, int mwidth)
{
	VipsTransformation trn;

	/* Check output is ok.
	 */
	if (out->type != JOIN_LEAF) {
		vips_error("vips_global_balance",
			_("image \"%s\" used twice as output"), out->name);
		return -1;
	}

	/* Fill fields.
	 */
	out->type = type;
	out->mwidth = mwidth;
	out->a = a;
	out->b = b;
	out->dx = dx;
	out->dy = dy;
	out->arg1 = arg1;
	out->arg2 = arg2;
	out->thistrn.a = a;
	out->thistrn.b = -b;
	out->thistrn.c = b;
	out->thistrn.d = a;
	out->thistrn.idx = 0;
	out->thistrn.idy = 0;
	out->thistrn.odx = dx;
	out->thistrn.ody = dy;

	/* Clean the table and propagate the transform down the RHS of the
	 * graph.
	 */
	clean_table(st);
	if (propagate_transform(arg2, &out->thistrn))
		return -1;

	/* Find the position and size of our output.
	 */
	calc_geometry(out);

	/* Now normalise the result, so that out is at (0,0) again.
	 */
	trn.a = 1.0;
	trn.b = 0.0;
	trn.c = 0.0;
	trn.d = 1.0;
	trn.idx = 0;
	trn.idy = 0;
	trn.odx = -out->cumtrn.oarea.left;
	trn.ody = -out->cumtrn.oarea.top;
	clean_table(st);
	if (propagate_transform(out, &trn))
		return -1;

	return 0;
}

/* Make a copy node.
 */
static int
make_copy(SymbolTable *st, JoinNode *before, JoinNode *after)
{
	/* Check output is ok.
	 */
	if (after->type != JOIN_LEAF) {
		vips_error("vips_global_balance",
			_("image \"%s\" used twice as output"), after->name);
		return -1;
	}

	/* Fill fields.
	 */
	after->type = JOIN_CP;
	after->arg1 = before;
	after->arg2 = NULL;

	/* Copy over the position and size from the before to the after.
	 */
	calc_geometry(after);

	return 0;
}

/* Process a single .desc line.
 */
static int
process_line(SymbolTable *st, const char *text)
{
	char line[1024];

#ifdef DEBUG
	printf("read: %s\n", text);
#endif /*DEBUG*/

	/* We destroy line during the parse.
	 */
	g_strlcpy(line, text, 1024);

	if (vips_isprefix("#LRJOIN ", line) ||
		vips_isprefix("#TBJOIN ", line)) {
		/* Yes: magic join command. Break into tokens. Format is eg.

			#LRJOIN <left> <right> <out> <x> <y> [<mwidth>]

		 */
		char *item[MAX_ITEMS];
		int nitems;
		JoinType type;
		JoinNode *arg1, *arg2, *join;
		int dx, dy, mwidth;

		if ((nitems = break_items(line, item)) < 0)
			return -1;
		if (nitems != 5 && nitems != 6) {
			vips_error("global_balance",
				"%s", _("bad number of args in join line"));
			return -1;
		}

		if (!(arg1 = add_node(st, item[0])) ||
			!(arg2 = add_node(st, item[1])) ||
			!(join = add_node(st, item[2])))
			return -1;
		dx = atoi(item[3]);
		dy = atoi(item[4]);
		if (nitems == 6)
			mwidth = atoi(item[5]);
		else
			mwidth = -1;
		if (vips_isprefix("#LRJOIN ", line))
			type = JOIN_LR;
		else
			type = JOIN_TB;

		if (make_join(st, type, arg1, arg2,
				join, 1.0, 0.0, dx, dy, mwidth))
			return -1;
	}
	else if (vips_isprefix("#LRROTSCALE ", line) ||
		vips_isprefix("#TBROTSCALE ", line)) {
		/* Rot + scale. Format is eg.

			#LRROTSCALE <left> <right> <out> \
				<a> <b> <x> <y> [<mwidth>]

		 */
		char *item[MAX_ITEMS];
		int nitems;
		JoinType type;
		JoinNode *arg1, *arg2, *join;
		double a, b, dx, dy;
		int mwidth;

		if ((nitems = break_items(line, item)) < 0)
			return -1;
		if (nitems != 7 && nitems != 8) {
			vips_error("global_balance",
				"%s", _("bad number of args in join1 line"));
			return -1;
		}

		if (!(arg1 = add_node(st, item[0])) ||
			!(arg2 = add_node(st, item[1])) ||
			!(join = add_node(st, item[2])))
			return -1;
		a = g_ascii_strtod(item[3], NULL);
		b = g_ascii_strtod(item[4], NULL);
		dx = g_ascii_strtod(item[5], NULL);
		dy = g_ascii_strtod(item[6], NULL);
		if (nitems == 8)
			mwidth = atoi(item[7]);
		else
			mwidth = -1;
		if (vips_isprefix("#LRROTSCALE ", line))
			type = JOIN_LRROTSCALE;
		else
			type = JOIN_TBROTSCALE;

		if (make_join(st, type, arg1, arg2,
				join, a, b, dx, dy, mwidth))
			return -1;
	}
	else if (vips_isprefix("copy ", line)) {
		/* vips_copy() call ... make a JOIN_CP node.
		 */
		char *item[MAX_ITEMS];
		int nitems;
		JoinNode *before, *after;

		if ((nitems = break_items(line, item)) < 0)
			return -1;
		if (nitems != 2) {
			vips_error("global_balance",
				"%s", _("bad number of args in copy line"));
			return -1;
		}

		if (!(before = add_node(st, item[0])) ||
			!(after = add_node(st, item[1])) ||
			make_copy(st, before, after))
			return -1;
	}

	return 0;
}

/* Set the dirty flag on any nodes we reference.
 */
static void *
set_referenced(JoinNode *node, void *a, void *b)
{
	if (node->arg1)
		node->arg1->dirty = 1;
	if (node->arg2)
		node->arg2->dirty = 1;

	return NULL;
}

/* Is this a root node? Should be clean.
 */
static void *
is_root(JoinNode *node, void *a, void *b)
{
	if (!node->dirty)
		return (void *) node;
	else
		return NULL;
}

/* Scan the symbol table, looking for a node which no node references.
 */
static JoinNode *
find_root(SymbolTable *st)
{
	JoinNode *root;

	/* Clean the table, then scan it, setting all pointed-to nodes dirty.
	 */
	clean_table(st);
	vips__map_table(st, (VipsSListMap2Fn) set_referenced, NULL, NULL);

	/* Look for the first clean symbol.
	 */
	root = (JoinNode *) vips__map_table(st,
		(VipsSListMap2Fn) is_root, NULL, NULL);

	/* No root? Hot dang!
	 */
	if (!root) {
		vips_error("vips_global_balance",
			"%s", _("mosaic root not found in desc file\n"
					"is this really a mosaiced image?"));
		return NULL;
	}

	/* Now dirty that - then if there are any more clean symbols, we have
	 * more than one root.
	 */
	root->dirty = 1;
	if (vips__map_table(st, (VipsSListMap2Fn) is_root, NULL, NULL)) {
		vips_error("vips_global_balance",
			"%s", _("more than one root"));
		return NULL;
	}

	return root;
}

/* Walk history_list and parse each line.
 */
int
vips__parse_desc(SymbolTable *st, VipsImage *in)
{
	GSList *p;

	for (p = in->history_list; p; p = p->next) {
		GValue *value = (GValue *) p->data;

		g_assert(G_VALUE_TYPE(value) == VIPS_TYPE_REF_STRING);

		if (process_line(st, vips_value_get_ref_string(value, NULL)))
			return -1;
	}

	/* Find root.
	 */
	if (!(st->root = find_root(st)))
		return -1;

	return 0;
}

/* Count and index all leaf images.
 */
static void *
count_leaves(JoinNode *node, void *a, void *b)
{
	if (node->type == JOIN_LEAF) {
		node->index = node->st->nim;
		node->st->nim++;
	}

	return NULL;
}

#ifdef DEBUG
/* Print a JoinNode.
 */
static void
print_node(JoinNode *node)
{
	char *basename = g_path_get_basename(node->name);
	printf("%s, position %dx%d, size %dx%d, index %d\n",
		basename,
		node->cumtrn.oarea.left, node->cumtrn.oarea.top,
		node->cumtrn.oarea.width, node->cumtrn.oarea.height,
		node->index);
	g_free(basename);
}
#endif /*DEBUG*/

#ifdef DEBUG
/* Print a leaf.
 */
static void *
print_leaf(JoinNode *node, void *a, void *b)
{
	if (node->type == JOIN_LEAF)
		print_node(node);

	return NULL;
}
#endif /*DEBUG*/

/* Count all join nodes.
 */
static void *
count_joins(JoinNode *node, void *a, void *b)
{
	if (node->type == JOIN_TB ||
		node->type == JOIN_LR ||
		node->type == JOIN_LRROTSCALE ||
		node->type == JOIN_TBROTSCALE)
		node->st->njoin++;

	return NULL;
}

#ifdef DEBUG
/* Print a few spaces.
 */
static void
spc(int n)
{
	int i;

	for (i = 0; i < n; i++)
		printf(" ");
}
#endif /*DEBUG*/

#ifdef DEBUG
static char *
JoinType2char(JoinType type)
{
	switch (type) {
	case JOIN_LR:
		return "JOIN_LR";
	case JOIN_TB:
		return "JOIN_TB";
	case JOIN_LRROTSCALE:
		return "JOIN_LRROTSCALE";
	case JOIN_TBROTSCALE:
		return "JOIN_TBROTSCALE";
	case JOIN_CP:
		return "JOIN_CP";
	case JOIN_LEAF:
		return "JOIN_LEAF";

	default:
		vips_error_exit("internal error #9275");
		/*NOTEACHED*/

		return NULL;
	}
}
#endif /*DEBUG*/

#ifdef DEBUG
/* Print a join node.
 */
static void *
print_joins(JoinNode *node, int indent)
{
	char *basename = g_path_get_basename(node->name);

	switch (node->type) {
	case JOIN_TB:
	case JOIN_LR:
	case JOIN_TBROTSCALE:
	case JOIN_LRROTSCALE:
		spc(indent);
		printf("%s to make %s, size %dx%d, pos. %dx%d, of:\n",
			JoinType2char(node->type),
			basename,
			node->cumtrn.oarea.width, node->cumtrn.oarea.height,
			node->cumtrn.oarea.left, node->cumtrn.oarea.top);
		spc(indent);
		printf("reference:\n");
		print_joins(node->arg1, indent + 2);
		spc(indent);
		printf("secondary:\n");
		print_joins(node->arg2, indent + 2);
		break;

	case JOIN_CP:
		spc(indent);
		printf("copy to make %s of:\n", basename);
		print_joins(node->arg1, indent + 2);
		break;

	case JOIN_LEAF:
		spc(indent);
		printf("input image %s\n", basename);
		break;
	}

	g_free(basename);

	return NULL;
}
#endif /*DEBUG*/

#ifdef DEBUG
/* Print an overlap.
 */
static void *
print_overlap(OverlapInfo *lap, void *a, void *b)
{
	char *basename_node = g_path_get_basename(lap->node->name);
	char *basename_other = g_path_get_basename(lap->other->name);

	printf("-> %s overlaps with %s; (this, other) = (%.4G, %.4G)\n",
		basename_node,
		basename_other,
		*VIPS_MATRIX(lap->nstats, 4, 0),
		*VIPS_MATRIX(lap->ostats, 4, 0));

	g_free(basename_node);
	g_free(basename_other);

	return NULL;
}
#endif /*DEBUG*/

#ifdef DEBUG
/* Print the overlaps on a leaf.
 */
static void *
print_overlaps(JoinNode *node, void *a, void *b)
{
	char *basename;

	if (node->type == JOIN_LEAF && g_slist_length(node->overlaps) > 0) {
		basename = g_path_get_basename(node->name);
		printf("overlap of %s with:\n", basename);
		g_free(basename);
		vips_slist_map2(node->overlaps,
			(VipsSListMap2Fn) print_overlap, NULL, NULL);
	}

	return NULL;
}
#endif /*DEBUG*/

#ifdef DEBUG
/* Print and accumulate the error on an overlap.
 */
static void *
print_overlap_error(OverlapInfo *lap, double *fac, double *total)
{
	char *basename_other = g_path_get_basename(lap->other->name);
	double na = *VIPS_MATRIX(lap->nstats, 4, 0);
	double oa = *VIPS_MATRIX(lap->ostats, 4, 0);
	double err;

	if (fac) {
		na *= fac[lap->node->index];
		oa *= fac[lap->other->index];
	}

	err = na - oa;

	printf("-> file %s, error = %g\n",
		basename_other, err);
	*total += err * err;

	g_free(basename_other);

	return NULL;
}
#endif /*DEBUG*/

#ifdef DEBUG
/* Print and accumulate the overlap errors on a leaf.
 */
static void *
print_overlap_errors(JoinNode *node, double *fac, double *total)
{
	char *basename;

	if (node->type == JOIN_LEAF && g_slist_length(node->overlaps) > 0) {
		basename = g_path_get_basename(node->name);
		printf("overlap of %s (index %d) with:\n", basename,
			node->index);
		g_free(basename);
		vips_slist_map2(node->overlaps,
			(VipsSListMap2Fn) print_overlap_error, fac, total);
	}

	return NULL;
}
#endif /*DEBUG*/

/* Extract a rect.
 */
static int
extract_rect(VipsImage *in, VipsImage **out, VipsRect *r)
{
	return vips_extract_area(in, out,
		r->left, r->top, r->width, r->height, NULL);
}

/* Two images overlap in an area ... make a mask the size of the area, which
 * has 255 for every pixel where both images are non-zero.
 */
static int
make_overlap_mask(VipsImage *mem,
	VipsImage *ref, VipsImage *sec, VipsImage **mask,
	VipsRect *rarea, VipsRect *sarea)
{
	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(mem), 6);

	if (extract_rect(ref, &t[0], rarea) ||
		extract_rect(sec, &t[1], sarea) ||
		vips_extract_band(t[0], &t[2], 0, NULL) ||
		vips_extract_band(t[1], &t[3], 0, NULL) ||
		vips_notequal_const1(t[2], &t[4], 0.0, NULL) ||
		vips_notequal_const1(t[3], &t[5], 0.0, NULL) ||
		vips_andimage(t[4], t[5], mask, NULL))
		return -1;

	return 0;
}

/* Find the number of non-zero pixels in a mask image.
 */
static int
count_nonzero(VipsImage *in, gint64 *count)
{
	double avg;

	if (vips_avg(in, &avg, NULL))
		return -1;
	*count = (avg * VIPS_IMAGE_N_PELS(in)) / 255.0;

	return 0;
}

/* Find stats on an area of an IMAGE ... consider only pixels for which the
 * mask is true.
 */
static VipsImage *
find_image_stats(VipsImage *mem,
	VipsImage *in, VipsImage *mask, VipsRect *area)
{
	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(mem), 5);

	gint64 count;

	/* Extract area, build black image, mask out pixels we want.
	 */
	if (extract_rect(in, &t[0], area) ||
		vips_black(&t[1], t[0]->Xsize, t[0]->Ysize,
			"bands", t[0]->Bands,
			NULL) ||
		vips_cast(t[1], &t[2], t[0]->BandFmt, NULL) ||
		vips_ifthenelse(mask, t[0], t[2], &t[3], NULL))
		return NULL;

	/* Get stats from masked image.
	 */
	if (vips_stats(t[3], &t[4], NULL))
		return NULL;

	/* Number of non-zero pixels in mask.
	 */
	if (count_nonzero(mask, &count))
		return NULL;

	/* And scale masked average to match.
	 */
	*VIPS_MATRIX(t[4], 4, 0) *=
		(double) count / VIPS_IMAGE_N_PELS(mask);

	/* Yuk! Zap the deviation column with the pixel count. Used later to
	 * determine if this is likely to be a significant overlap.
	 */
	*VIPS_MATRIX(t[4], 5, 0) = count;

#ifdef DEBUG
	if (count == 0)
		g_warning("global_balance empty overlap");
#endif /*DEBUG*/

	return t[4];
}

/* Find the stats for an overlap struct.
 */
static int
find_overlap_stats(OverlapInfo *lap)
{
	VipsImage *mem = lap->node->st->im;
	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(mem), 1);

	VipsRect rarea, sarea;

	/* Translate the overlap area into the coordinate scheme for the main
	 * node.
	 */
	rarea = lap->overlap;
	rarea.left -= lap->node->cumtrn.oarea.left;
	rarea.top -= lap->node->cumtrn.oarea.top;

	/* Translate the overlap area into the coordinate scheme for the other
	 * node.
	 */
	sarea = lap->overlap;
	sarea.left -= lap->other->cumtrn.oarea.left;
	sarea.top -= lap->other->cumtrn.oarea.top;

	/* Make a mask for the overlap.
	 */
	if (make_overlap_mask(mem,
			lap->node->trnim, lap->other->trnim, &t[0], &rarea, &sarea))
		return -1;

	/* Find stats for that area.
	 */
	if (!(lap->nstats = find_image_stats(mem,
			  lap->node->trnim, t[0], &rarea)))
		return -1;
	if (!(lap->ostats = find_image_stats(mem,
			  lap->other->trnim, t[0], &sarea)))
		return -1;

	return 0;
}

/* Sub-fn. of below.
 */
static void *
overlap_eq(OverlapInfo *this, JoinNode *node, void *b)
{
	if (this->other == node)
		return this;
	else
		return NULL;
}

/* Is this an overlapping leaf? If yes, add to overlap list.
 */
static void *
test_overlap(JoinNode *other, JoinNode *node, void *b)
{
	VipsRect overlap;
	OverlapInfo *lap;

	/* Is other a suitable leaf to overlap with node?
	 */
	if (other->type != JOIN_LEAF || node == other)
		return NULL;

	/* Is there an overlap?
	 */
	vips_rect_intersectrect(&node->cumtrn.oarea, &other->cumtrn.oarea,
		&overlap);
	if (vips_rect_isempty(&overlap))
		return NULL;

	/* Is this a trivial overlap? Ignore it if it is.
	 */
	if (overlap.width * overlap.height < TRIVIAL)
		/* Too few pixels.
		 */
		return NULL;

	/* Have we already added this overlap the other way around? ie. is
	 * node on other's overlap list?
	 */
	if (vips_slist_map2(other->overlaps,
			(VipsSListMap2Fn) overlap_eq, node, NULL))
		return NULL;

	/* A new overlap - add to overlap list.
	 */
	if (!(lap = build_overlap(node, other, &overlap)))
		return node;

	/* Calculate overlap statistics. Open stuff relative to this, and
	 * free quickly.
	 */
	if (find_overlap_stats(lap))
		return node;

	/* If the pixel count either masked overlap is trivial, ignore this
	 * overlap.
	 */
	if (*VIPS_MATRIX(lap->nstats, 5, 0) < TRIVIAL ||
		*VIPS_MATRIX(lap->ostats, 5, 0) < TRIVIAL) {
#ifdef DEBUG
		printf("trivial overlap ... junking\n");
		printf("nstats count = %g, ostats count = %g\n",
			*VIPS_MATRIX(lap->nstats, 5, 0), *VIPS_MATRIX(lap->ostats, 5, 0));
		print_overlap(lap, NULL, NULL);
#endif /*DEBUG*/
		overlap_destroy(lap);
	}

	return NULL;
}

/* If this is a leaf, look at all other joins for a leaf that overlaps. Aside:
 * If this is a leaf, there should be an IMAGE. Flag an error if there is
 * not.
 */
static void *
find_overlaps(JoinNode *node, SymbolTable *st, void *b)
{
	if (node->type == JOIN_LEAF) {
		/* Check for image.
		 */
		if (!node->im) {
			vips_error("vips_global_balance",
				_("unable to open \"%s\""), node->name);
			return node;
		}
		if (!node->trnim)
			vips_error_exit("global_balance: sanity failure #9834");

		return vips__map_table(st,
			(VipsSListMap2Fn) test_overlap, node, NULL);
	}

	return NULL;
}

/* Bundle of variables for matrix creation.
 */
typedef struct {
	SymbolTable *st; /* Main table */
	JoinNode *leaf;	 /* Leaf to be 1.000 */
	VipsImage *K;	 /* LHS */
	VipsImage *M;	 /* RHS */
	int row;		 /* Current row */
} MatrixBundle;

/* Add a new row for the nominated overlap to the matrices.
 */
static void *
add_nominated(OverlapInfo *ovl, MatrixBundle *bun, double *gamma)
{
	double ns = pow(*VIPS_MATRIX(ovl->nstats, 4, 0), 1.0 / (*gamma));
	double os = pow(*VIPS_MATRIX(ovl->ostats, 4, 0), 1.0 / (*gamma));

	*VIPS_MATRIX(bun->K, 0, bun->row) = ns;
	*VIPS_MATRIX(bun->M, ovl->other->index - 1, bun->row) = os;

	bun->row++;

	return NULL;
}

/* Add a new row for an ordinary overlap to the matrices.
 */
static void *
add_other(OverlapInfo *ovl, MatrixBundle *bun, double *gamma)
{
	double ns = -pow(*VIPS_MATRIX(ovl->nstats, 4, 0), 1.0 / (*gamma));
	double os = pow(*VIPS_MATRIX(ovl->ostats, 4, 0), 1.0 / (*gamma));

	*VIPS_MATRIX(bun->M, ovl->node->index - 1, bun->row) = ns;
	*VIPS_MATRIX(bun->M, ovl->other->index - 1, bun->row) = os;

	bun->row++;

	return NULL;
}

/* Add stuff for node to matrix.
 */
static void *
add_row(JoinNode *node, MatrixBundle *bun, double *gamma)
{
	if (node == bun->leaf)
		vips_slist_map2(node->overlaps,
			(VipsSListMap2Fn) add_nominated, bun, gamma);
	else
		vips_slist_map2(node->overlaps,
			(VipsSListMap2Fn) add_other, bun, gamma);

	return NULL;
}

/* Fill K and M. leaf is image selected to have factor of 1.000.
 */
static void
fill_matrices(SymbolTable *st, double gamma, VipsImage *K, VipsImage *M)
{
	MatrixBundle bun;

	bun.st = st;
	bun.leaf = st->leaf;
	bun.K = K;
	bun.M = M;
	bun.row = 0;

	/* Build matrices.
	 */
	vips__map_table(st, (VipsSListMap2Fn) add_row, &bun, &gamma);
}

/* Used to select the leaf whose coefficient we set to 1.
 */
static void *
choose_leaf(JoinNode *node, void *a, void *b)
{
	if (node->type == JOIN_LEAF)
		return node;

	return NULL;
}

/* Make an image from a node.
 */
static VipsImage *
make_mos_image(SymbolTable *st, JoinNode *node, transform_fn tfn, void *a)
{
	VipsImage *im1, *im2, *out;

	switch (node->type) {
	case JOIN_LR:
	case JOIN_TB:
		if (!(im1 = make_mos_image(st, node->arg1, tfn, a)) ||
			!(im2 = make_mos_image(st, node->arg2, tfn, a)))
			return NULL;

		if (vips_merge(im1, im2, &out,
				node->type == JOIN_LR
					? VIPS_DIRECTION_HORIZONTAL
					: VIPS_DIRECTION_VERTICAL,
				-node->dx, -node->dy,
				"mblend", node->mwidth,
				NULL))
			return NULL;
		vips_object_local(st->im, out);
		vips_image_set_string(out, "mosaic-name", node->name);

		break;

	case JOIN_LRROTSCALE:
	case JOIN_TBROTSCALE:
		if (!(im1 = make_mos_image(st, node->arg1, tfn, a)) ||
			!(im2 = make_mos_image(st, node->arg2, tfn, a)))
			return NULL;

		out = vips_image_new();
		vips_object_local(st->im, out);

		vips_image_set_string(out, "mosaic-name", node->name);

		if (node->type == JOIN_LRROTSCALE) {
			if (vips__lrmerge1(im1, im2, out,
					node->a, node->b, node->dx, node->dy,
					node->mwidth))
				return NULL;
		}
		else {
			if (vips__tbmerge1(im1, im2, out,
					node->a, node->b, node->dx, node->dy,
					node->mwidth))
				return NULL;
		}

		break;

	case JOIN_LEAF:
		/* Trivial case!
		 */
		if (!(out = tfn(node, a)))
			return NULL;

		break;

	case JOIN_CP:
		/* Very trivial case.
		 */
		out = make_mos_image(st, node->arg1, tfn, a);

		break;

	default:
		vips_error_exit("internal error #982369824375987");
		/*NOTEACHED*/
		return NULL;
	}

	return out;
}

/* Re-build mosaic.
 */
int
vips__build_mosaic(SymbolTable *st, VipsImage *out, transform_fn tfn, void *a)
{
	JoinNode *root = st->root;
	VipsImage *im1, *im2;
	VipsImage *x;

	switch (root->type) {
	case JOIN_LR:
	case JOIN_TB:
		if (!(im1 = make_mos_image(st, root->arg1, tfn, a)) ||
			!(im2 = make_mos_image(st, root->arg2, tfn, a)))
			return -1;

		if (vips_merge(im1, im2, &x,
				root->type == JOIN_LR
					? VIPS_DIRECTION_HORIZONTAL
					: VIPS_DIRECTION_VERTICAL,
				-root->dx, -root->dy,
				"mblend", root->mwidth,
				NULL))
			return -1;
		if (vips_image_write(x, out)) {
			g_object_unref(x);
			return -1;
		}
		g_object_unref(x);

		break;

	case JOIN_LRROTSCALE:
	case JOIN_TBROTSCALE:
		if (!(im1 = make_mos_image(st, root->arg1, tfn, a)) ||
			!(im2 = make_mos_image(st, root->arg2, tfn, a)))
			return -1;

		if (root->type == JOIN_LRROTSCALE) {
			if (vips__lrmerge1(im1, im2, out,
					root->a, root->b, root->dx, root->dy,
					root->mwidth))
				return -1;
		}
		else {
			if (vips__tbmerge1(im1, im2, out,
					root->a, root->b, root->dx, root->dy,
					root->mwidth))
				return -1;
		}

		break;

	case JOIN_LEAF:
		/* Trivial case! Just one file in our mosaic.
		 */
		if (!(im1 = tfn(root, a)) ||
			vips_image_write(im1, out))
			return -1;

		break;

	case JOIN_CP:
		/* Very trivial case.
		 */
		if (!(im1 = make_mos_image(st, root->arg1, tfn, a)) ||
			vips_image_write(im1, out))
			return -1;

		break;

	default:
		vips_error_exit("internal error #982369824375987");
		/*NOTEACHED*/
	}

	return 0;
}

static int
vips__matrixtranspose(VipsImage *in, VipsImage **out)
{
	if (!(*out = vips_image_new_matrix(in->Ysize, in->Xsize)))
		return -1;

	for (int y = 0; y < (*out)->Ysize; y++)
		for (int x = 0; x < (*out)->Xsize; x++)
			*VIPS_MATRIX(*out, x, y) = *VIPS_MATRIX(in, y, x);

	return 0;
}

static int
vips__matrixmultiply(VipsImage *in1, VipsImage *in2, VipsImage **out)
{
	int xc, yc, col;
	double sum;
	double *mat, *a, *b;
	double *s1, *s2;

	/* Check matrix sizes.
	 */
	if (in1->Xsize != in2->Ysize) {
		vips_error("vips__matrixmultiply", "%s", _("bad sizes"));
		return -1;
	}

	/* Allocate output matrix.
	 */
	if (!(*out = vips_image_new_matrix(in2->Xsize, in1->Ysize)))
		return -1;

	/* Multiply.
	 */
	mat = VIPS_MATRIX(*out, 0, 0);
	s1 = VIPS_MATRIX(in1, 0, 0);

	for (yc = 0; yc < in1->Ysize; yc++) {
		s2 = VIPS_MATRIX(in2, 0, 0);

		for (col = 0; col < in2->Xsize; col++) {
			/* Get ready to sweep a row.
			 */
			a = s1;
			b = s2;

			for (sum = 0.0, xc = 0; xc < in1->Xsize; xc++) {
				sum += *a++ * *b;
				b += in2->Xsize;
			}

			*mat++ = sum;
			s2++;
		}

		s1 += in1->Xsize;
	}

	return 0;
}

/* Find correction factors.
 */
static int
find_factors(SymbolTable *st, double gamma)
{
	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(st->im), 7);

	double total;
	double avg;
	int i;

	/* Make output matrices.
	 */
	if (!(t[0] = vips_image_new_matrix(1, st->novl)) ||
		!(t[1] = vips_image_new_matrix(st->nim - 1, st->novl)))
		return -1;

	fill_matrices(st, gamma, t[0], t[1]);

#ifdef DEBUG
	vips_image_write_to_file(t[0], "K.mat", NULL);
	vips_image_write_to_file(t[1], "M.mat", NULL);
#endif /*DEBUG*/

	/* Calculate LMS.
	 */
	if (vips__matrixtranspose(t[1], &t[2]) ||
		vips__matrixmultiply(t[2], t[1], &t[3]) ||
		vips_matrixinvert(t[3], &t[4], NULL) ||
		vips__matrixmultiply(t[4], t[2], &t[5]) ||
		vips__matrixmultiply(t[5], t[0], &t[6]))
		return -1;

	/* Make array of correction factors.
	 */
	if (!(st->fac = VIPS_ARRAY(st->im, st->nim, double)))
		return -1;
	for (i = 0; i < t[6]->Ysize; i++)
		st->fac[i + 1] = *VIPS_MATRIX(t[6], 0, i);
	st->fac[0] = 1.0;

	/* Find average balance factor, normalise to that average.
	 */
	total = 0.0;
	for (i = 0; i < st->nim; i++)
		total += st->fac[i];
	avg = total / st->nim;
	for (i = 0; i < st->nim; i++)
		st->fac[i] /= avg;

#ifdef DEBUG
	/* Diagnostics!
	 */
	printf("debugging output for vips_globalbalance():\n");
	for (i = 0; i < st->nim; i++)
		printf("balance factor %d = %g\n", i, st->fac[i]);
	total = 0.0;
	printf("Overlap errors:\n");
	vips__map_table(st,
		(VipsSListMap2Fn) print_overlap_errors, NULL, &total);
	printf("RMS error = %g\n", sqrt(total / st->novl));

	total = 0.0;
	printf("Overlap errors after adjustment:\n");
	vips__map_table(st,
		(VipsSListMap2Fn) print_overlap_errors, st->fac, &total);
	printf("RMS error = %g\n", sqrt(total / st->novl));
#endif /*DEBUG*/

	return 0;
}

/* TODO(kleisauke): Copied from im__affinei */
/* Shared with vips_mosaic1(), so not static. */
int
vips__affinei(VipsImage *in, VipsImage *out, VipsTransformation *trn)
{
	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(out), 2);
	VipsArrayInt *oarea;
	gboolean repack;

	oarea = vips_array_int_newv(4,
		trn->oarea.left, trn->oarea.top,
		trn->oarea.width, trn->oarea.height);

	/* vips7 affine would repack labq and im_benchmark() depends upon
	 * this.
	 */
	repack = in->Coding == VIPS_CODING_LABQ;

	if (vips_affine(in, &t[0],
			trn->a, trn->b, trn->c, trn->d,
			"oarea", oarea,
			"odx", trn->odx,
			"ody", trn->ody,
			NULL)) {
		vips_area_unref(VIPS_AREA(oarea));
		return -1;
	}
	vips_area_unref(VIPS_AREA(oarea));
	in = t[0];

	if (repack) {
		if (vips_colourspace(in, &t[1],
				VIPS_INTERPRETATION_LABQ, NULL))
			return -1;
		in = t[1];
	}

	if (vips_image_write(in, out))
		return -1;

	return 0;
}

/* Look for all leaves, make sure we have a transformed version of each.
 */
static void *
generate_trn_leaves(JoinNode *node, SymbolTable *st, void *b)
{
	if (node->type == JOIN_LEAF) {
		/* Check for image.
		 */
		if (!node->im) {
			vips_error("vips_global_balance",
				_("unable to open \"%s\""), node->name);
			return node;
		}
		if (node->trnim)
			vips_error_exit("global_balance: sanity failure #765");

		/* Special case: if this is an untransformed leaf (there will
		 * always be at least one), then skip the affine.
		 */
		if (vips__transform_isidentity(&node->cumtrn))
			node->trnim = node->im;
		else {
			node->trnim = vips_image_new();
			vips_object_local(node->st->im, node->trnim);

			if (vips__affinei(node->im, node->trnim, &node->cumtrn))
				return node;
		}
	}

	return NULL;
}

/* Analyse mosaic.
 */
static int
analyse_mosaic(SymbolTable *st, VipsImage *in)
{
	/* Parse Hist on in.
	 */
	if (vips__parse_desc(st, in))
		return -1;

		/* Print parsed data.
		 */
#ifdef DEBUG
	printf("Input files:\n");
	vips__map_table(st, (VipsSListMap2Fn) print_leaf, NULL, NULL);
	printf("\nOutput file:\n");
	print_node(st->root);
	printf("\nJoin commands:\n");
	print_joins(st->root, 0);
#endif /*DEBUG*/

	/* Generate transformed leaves.
	 */
	if (vips__map_table(st,
			(VipsSListMap2Fn) generate_trn_leaves, st, NULL))
		return -1;

	/* Find overlaps.
	 */
	if (vips__map_table(st, (VipsSListMap2Fn) find_overlaps, st, NULL))
		return -1;

	/* Scan table, counting and indexing input images and joins.
	 */
	vips__map_table(st, (VipsSListMap2Fn) count_leaves, NULL, NULL);
	vips__map_table(st, (VipsSListMap2Fn) count_joins, NULL, NULL);

	/* Select leaf to be 1.000.
	 * This must be index == 0, unless you change stuff above!
	 */
	st->leaf = vips__map_table(st,
		(VipsSListMap2Fn) choose_leaf, NULL, NULL);

	/* And print overlaps.
	 */
#ifdef DEBUG
	printf("\nLeaf to be 1.000:\n");
	print_node(st->leaf);
	printf("\nOverlaps:\n");
	vips__map_table(st, (VipsSListMap2Fn) print_overlaps, NULL, NULL);
	printf("\n%d input files, %d unique overlaps, %d joins\n",
		st->nim, st->novl, st->njoin);
#endif /*DEBUG*/

	return 0;
}

/* Scale im by fac -- if it's uchar/ushort, use a lut. If we can use a lut,
 * transform in linear space. If we can't, don't bother for efficiency.
 */
static VipsImage *
transform(JoinNode *node, double *gamma)
{
	SymbolTable *st = node->st;
	VipsImage *in = node->im;
	double fac = st->fac[node->index];
	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(st->im), 8);

	VipsImage *out;

	if (fac == 1.0) {
		/* Easy!
		 */
		out = in;
	}
	/* TODO(kleisauke): Could we call vips_gamma instead?
	 */
	else if (in->BandFmt == VIPS_FORMAT_UCHAR ||
		in->BandFmt == VIPS_FORMAT_USHORT) {
		if (vips_identity(&t[0],
				"bands", 1,
				"ushort", in->BandFmt == VIPS_FORMAT_USHORT,
				//"size", 65535,
				NULL) ||
			vips_pow_const1(t[0], &t[1],
				1.0 / (*gamma), NULL) ||
			vips_linear1(t[1], &t[2], fac, 0.0, NULL) ||
			vips_pow_const1(t[2], &t[3], *gamma, NULL) ||
			vips_cast(t[3], &t[4], in->BandFmt, NULL) ||
			vips_maplut(in, &t[5], t[4], NULL))
			return NULL;
		out = t[5];
	}
	else {
		/* Just vips_linear1 it.
		 */
		if (vips_linear1(in, &t[6], fac, 0.0, NULL) ||
			vips_cast(t[6], &t[7], in->BandFmt, NULL))
			return NULL;
		out = t[7];
	}

	vips_image_set_string(out, "mosaic-name", node->name);

	return out;
}

/* As above, but output as float, not matched to input.
 */
static VipsImage *
transformf(JoinNode *node, double *gamma)
{
	SymbolTable *st = node->st;
	VipsImage *in = node->im;
	double fac = node->st->fac[node->index];
	VipsImage **t = (VipsImage **)
		vips_object_local_array(VIPS_OBJECT(st->im), 6);

	VipsImage *out;

	if (fac == 1.0) {
		/* Easy!
		 */
		out = in;
	}
	else if (in->BandFmt == VIPS_FORMAT_UCHAR ||
		in->BandFmt == VIPS_FORMAT_USHORT) {
		if (vips_identity(&t[0],
				"bands", 1,
				"ushort", in->BandFmt == VIPS_FORMAT_USHORT,
				//"size", 65535,
				NULL) ||
			vips_pow_const1(t[0], &t[1],
				1.0 / (*gamma), NULL) ||
			vips_linear1(t[1], &t[2], fac, 0.0, NULL) ||
			vips_pow_const1(t[2], &t[3], *gamma, NULL) ||
			vips_maplut(in, &t[4], t[3], NULL))
			return NULL;
		out = t[4];
	}
	else {
		/* Just vips_linear1 it.
		 */
		if (vips_linear1(in, &t[5], fac, 0.0, NULL))
			return NULL;
		out = t[5];
	}

	vips_image_set_string(out, "mosaic-name", node->name);

	return out;
}

typedef struct {
	VipsOperation parent_instance;

	VipsImage *in;
	VipsImage *out;

	gboolean int_output;
	double gamma;

} VipsGlobalbalance;

typedef VipsOperationClass VipsGlobalbalanceClass;

G_DEFINE_TYPE(VipsGlobalbalance, vips_globalbalance, VIPS_TYPE_OPERATION);

static int
vips_globalbalance_build(VipsObject *object)
{
	VipsGlobalbalance *globalbalance = (VipsGlobalbalance *) object;

	SymbolTable *st;
	transform_fn trn;

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

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

	if (!(st = vips__build_symtab(globalbalance->out, SYM_TAB_SIZE)) ||
		analyse_mosaic(st, globalbalance->in) ||
		find_factors(st, globalbalance->gamma))
		return -1;

	trn = globalbalance->int_output
		? (transform_fn) transform
		: (transform_fn) transformf;
	if (vips__build_mosaic(st, globalbalance->out,
			trn, &globalbalance->gamma))
		return -1;

	return 0;
}

static void
vips_globalbalance_class_init(VipsGlobalbalanceClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;

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

	object_class->nickname = "globalbalance";
	object_class->description = _("global balance an image mosaic");
	object_class->build = vips_globalbalance_build;

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

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

	VIPS_ARG_DOUBLE(class, "gamma", 5,
		_("Gamma"),
		_("Image gamma"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsGlobalbalance, gamma),
		0.00001, 10, 1.6);

	VIPS_ARG_BOOL(class, "int_output", 7,
		_("Int output"),
		_("Integer output"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsGlobalbalance, int_output),
		FALSE);
}

static void
vips_globalbalance_init(VipsGlobalbalance *globalbalance)
{
	globalbalance->gamma = 1.6;
}

/**
 * vips_globalbalance: (method)
 * @in: mosaic to rebuild
 * @out: (out): output image
 * @...: `NULL`-terminated list of optional named arguments
 *
 * [method@Image.globalbalance] can be used to remove contrast differences in
 * an assembled mosaic.
 *
 * It reads the History field attached to @in and builds a list of the source
 * images that were used to make the mosaic and the position that each ended
 * up at in the final image.
 *
 * It opens each of the source images in turn and extracts all parts which
 * overlap with any of the other images. It finds the average values in the
 * overlap areas and uses least-mean-square to find a set of correction
 * factors which will minimise overlap differences. It uses @gamma to
 * gamma-correct the source images before calculating the factors. A value of
 * 1.0 will stop this.
 *
 * Each of the source images is transformed with the appropriate correction
 * factor, then the mosaic is reassembled. @out is
 * [enum@Vips.BandFormat.FLOAT], but if @int_output is set, the output image
 * is the same format as the input images.
 *
 * There are some conditions that must be met before this operation can work:
 * the source images must all be present under the filenames recorded in the
 * history on @in, and the mosaic must have been built using only operations in
 * this package.
 *
 * ::: tip "Optional arguments"
 *     * @gamma: `gdouble`, gamma of source images
 *     * @int_output: `gboolean`, `TRUE` for integer image output
 *
 * ::: seealso
 *     [method@Image.mosaic].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_globalbalance(VipsImage *in, VipsImage **out, ...)
{
	va_list ap;
	int result;

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

	return result;
}
