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

/*

    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 <vips/intl.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 "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, '>' )) ) {
			im_error( "break_files", "%s", _( "no matching '>'" ) );
			return( -1 );
		}

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

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

	return( i );
}

/* Try to open a file. If full path fails, try the current directory.
 */
IMAGE *
im__global_open_image( SymbolTable *st, char *name )
{
	IMAGE *im;

	if( (im = im_open_local( st->im, name, "r" )) || 
		(im = im_open_local( st->im, im_skip_dir( name ), "r" )) ) 
		return( im );

	return( NULL );
}

static int
junk_node( JoinNode *node )
{
	IM_FREEF( g_slist_free, node->overlaps );

	return( 0 );
}

/* 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 = IM_NEW( st->im, JoinNode );
	int n = hash( name );

	/* Fill fields.
	 */
	if( !node || !(node->name = im_strdup( 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;

        if( im_add_close_callback( st->im, 
		(im_callback_fn) junk_node, node, NULL ) ) 
                return( NULL );

	/* Try to open.
	 */
	if( (node->im = im__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.
		 */
		im_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, Rect *overlap )
{
	OverlapInfo *lap = IM_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 int
junk_table( SymbolTable *st )
{
	int i;

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

	return( 0 );
}

/* Build a new symbol table.
 */
SymbolTable *
im__build_symtab( IMAGE *out, int sz )
{
	SymbolTable *st = IM_NEW( out, SymbolTable );
	int i;

	if( !st ||
		!(st->table = IM_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;

        if( im_add_close_callback( out, 
		(im_callback_fn) junk_table, st, NULL ) ) 
                return( NULL );

	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 )
{
	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( im_slist_map2( st->table[hash( name )],
		(VSListMap2Fn) 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 *
im__map_table( SymbolTable *st, void *(*fn)(), void *a, void *b )
{
	int i;
	void *r;
	
	for( i = 0; i < st->sz; i++ )
		if( (r = im_slist_map2( st->table[i], 
			(VSListMap2Fn) fn, a, b )) )
			return( r );
	
	return( NULL );
}

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

	return( NULL );
}

/* Clean the whole table.
 */
static void
clean_table( SymbolTable *st )
{
	(void) im__map_table( st, 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 )
{
	Rect um;

	switch( node->type ) {
	case JOIN_LR:
	case JOIN_TB:
	case JOIN_LRROTSCALE:
	case JOIN_TBROTSCALE:
		/* Join two areas.
		 */
		im_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:
		error_exit( "internal error #98356" );
		/*NOTREACHED*/
	}
}

/* Propogate 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
propogate_transform( JoinNode *node, VipsTransformation *trn )
{
	if( !node )
		return( 0 );

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

	/* Transform our children.
	 */
	if( propogate_transform( node->arg1, trn ) ||
		propogate_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. Propogate 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 ) {
		im_error( "im_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 propogate the transform down the RHS of the
	 * graph.
	 */
	clean_table( st );
	if( propogate_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( propogate_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 ) {
		im_error( "im_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.
	 */
	im_strncpy( line, text, 1024 );

	if( im_isprefix( "#LRJOIN ", line ) || 
		im_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 ) {
			im_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( im_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( im_isprefix( "#LRROTSCALE ", line ) ||
		im_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 ) {
			im_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( im_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( im_isprefix( "copy ", line ) ) {
		/* im_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 ) {
			im_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 )
{
	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 )
{
	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 );
	im__map_table( st, set_referenced, NULL, NULL );

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

	/* No root? Hot dang!
	 */
	if( !root ) {
		im_error( "im_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( im__map_table( st, is_root, NULL, NULL ) ) {
		im_error( "im_global_balance", 
			"%s", _( "more than one root" ) );
		return( NULL );
	}

	return( root );
}

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

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

		g_assert( G_VALUE_TYPE( value ) == IM_TYPE_REF_STRING );

		if( process_line( st, im_ref_string_get( value ) ) )
			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 )
{
	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 )
{
	printf( "%s, position %dx%d, size %dx%d, index %d\n",
		im_skip_dir( node->name ),
		node->cumtrn.oarea.left, node->cumtrn.oarea.top,
		node->cumtrn.oarea.width, node->cumtrn.oarea.height,
		node->index );
}
#endif /*DEBUG*/

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

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

/* Count all join nodes.
 */
static void *
count_joins( JoinNode *node )
{
	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:
		error_exit( "internal error #9275" );
		/*NOTEACHED*/

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

#ifdef DEBUG
/* Print a join node.
 */
static void *
print_joins( JoinNode *node, int indent )
{
	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 ), 
			im_skip_dir( node->name ),
			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", im_skip_dir( node->name ) );
		print_joins( node->arg1, indent+2 );
		break;

	case JOIN_LEAF:
		spc( indent );
		printf( "input image %s\n", im_skip_dir( node->name ) );
		break;
	}

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

#ifdef DEBUG
/* Print an overlap.
 */
static void *
print_overlap( OverlapInfo *lap )
{
	printf( "-> %s overlaps with %s; (this, other) = (%.4G, %.4G)\n",
		im_skip_dir( lap->node->name ),
		im_skip_dir( lap->other->name ),
		lap->nstats->coeff[4],
		lap->ostats->coeff[4] );

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

#ifdef DEBUG
/* Print the overlaps on a leaf.
 */
static void *
print_overlaps( JoinNode *node )
{
	if( node->type == JOIN_LEAF && g_slist_length( node->overlaps ) > 0 ) {
		printf( "overlap of %s with:\n", im_skip_dir( node->name ) );
		im_slist_map2( node->overlaps, 
			(VSListMap2Fn) 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 )
{
	double na = lap->nstats->coeff[4];
	double oa = lap->ostats->coeff[4];
	double err;

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

	err = na - oa;

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

	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 )
{
	if( node->type == JOIN_LEAF && g_slist_length( node->overlaps ) > 0 ) {
		printf( "overlap of %s (index %d) with:\n", 
			im_skip_dir( node->name ), node->index );
		im_slist_map2( node->overlaps, 
			(VSListMap2Fn) print_overlap_error, fac, total );
	}

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

/* Extract a rect.
 */
static int
extract_rect( IMAGE *in, IMAGE *out, Rect *r )
{
	return( im_extract_area( in, out, 
		r->left, r->top, r->width, r->height ) );
}

/* 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( IMAGE *mem, IMAGE *ref, IMAGE *sec, IMAGE *mask, 
	Rect *rarea, Rect *sarea )
{
	IMAGE *t[6];

	if( im_open_local_array( mem, t, 6, "mytemps", "p" ) ||
		extract_rect( ref, t[0], rarea ) ||
		extract_rect( sec, t[1], sarea ) ||
		im_extract_band( t[0], t[2], 0 ) ||
		im_extract_band( t[1], t[3], 0 ) ||
		im_notequalconst( t[2], t[4], 0.0 ) || 
		im_notequalconst( t[3], t[5], 0.0 ) || 
		im_andimage( t[4], t[5], mask ) ) 
		return( -1 );

	return( 0 );
}

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

	if( im_avg( in, &avg ) )
		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 DOUBLEMASK *
find_image_stats( IMAGE *mem, IMAGE *in, IMAGE *mask, Rect *area )
{
	DOUBLEMASK *stats;
	IMAGE *t[4];
	gint64 count;

	/* Extract area, build black image, mask out pixels we want.
	 */
	if( im_open_local_array( mem, t, 4, "find_image_stats", "p" ) ||
		extract_rect( in, t[0], area ) ||
		im_black( t[1], t[0]->Xsize, t[0]->Ysize, t[0]->Bands ) ||
		im_clip2fmt( t[1], t[2], t[0]->BandFmt ) ||
		im_ifthenelse( mask, t[0], t[2], t[3] ) )
		return( NULL );

	/* Get stats from masked image.
	 */
	if( !(stats = im_local_dmask( mem, im_stats( t[3] ) )) ) 
		return( NULL );

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

	/* And scale masked average to match.
	 */
	stats->coeff[4] *= (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.
	 */
	stats->coeff[5] = count;

#ifdef DEBUG
	if( count == 0 )
		im_warn( "global_balance", "%s", _( "empty overlap!" ) );
#endif /*DEBUG*/

	return( stats );
}

/* Find the stats for an overlap struct.
 */
static int
find_overlap_stats( OverlapInfo *lap )
{
	IMAGE *mem = lap->node->st->im; 
	IMAGE *t1 = im_open_local( mem, "find_overlap_stats:1", "p" );
	Rect 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, t1, &rarea, &sarea ) )
		return( -1 );

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

	return( 0 );
}

/* Sub-fn. of below.
 */
static void *
overlap_eq( OverlapInfo *this, JoinNode *node )
{
	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 )
{
	Rect 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?
	 */
	im_rect_intersectrect( &node->cumtrn.oarea, &other->cumtrn.oarea, 
		&overlap );
	if( im_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( im_slist_map2( other->overlaps, 
		(VSListMap2Fn) 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( lap->nstats->coeff[5] < TRIVIAL || 
		lap->ostats->coeff[5] < TRIVIAL ) {
#ifdef DEBUG
		printf( "trivial overlap ... junking\n" );
		printf( "nstats count = %g, ostats count = %g\n",
			lap->nstats->coeff[5], lap->ostats->coeff[5] );
		print_overlap( lap );
#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 )
{
	if( node->type == JOIN_LEAF ) {
		/* Check for image.
		 */
		if( !node->im ) {
			im_error( "im_global_balance", 
				_( "unable to open \"%s\"" ), node->name );
			return( node );
		}
		if( !node->trnim ) 
			error_exit( "global_balance: sanity failure #9834" );

		return( im__map_table( st, 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 */
	DOUBLEMASK *K;			/* LHS */
	DOUBLEMASK *M;			/* RHS */
	int row;			/* Current row */
} MatrixBundle;

/* Add a new row for the nominated overlap to the matricies.
 */
static void *
add_nominated( OverlapInfo *ovl, MatrixBundle *bun, double *gamma )
{
	double *Kp = bun->K->coeff + bun->row;
	double *Mp = bun->M->coeff + bun->row*bun->M->xsize;
	double ns = pow( ovl->nstats->coeff[4], 1/(*gamma) );
	double os = pow( ovl->ostats->coeff[4], 1/(*gamma) );

	Kp[0] = ns;
	Mp[ovl->other->index - 1] = os;

	bun->row++;

	return( NULL );
}

/* Add a new row for an ordinary overlap to the matricies.
 */
static void *
add_other( OverlapInfo *ovl, MatrixBundle *bun, double *gamma )
{
	double *Mp = bun->M->coeff + bun->row*bun->M->xsize;
	double ns = -pow( ovl->nstats->coeff[4], 1/(*gamma) );
	double os = pow( ovl->ostats->coeff[4], 1/(*gamma) );

	Mp[ovl->node->index - 1] = ns;
	Mp[ovl->other->index - 1] = 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 )
		im_slist_map2( node->overlaps, 
			(VSListMap2Fn) add_nominated, bun, gamma );
	else
		im_slist_map2( node->overlaps, 
			(VSListMap2Fn) add_other, bun, gamma );
	
	return( NULL );
}

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

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

	/* Build matricies.
	 */
	im__map_table( st, add_row, &bun, &gamma );
}

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

/* Make an image from a node.
 */
static IMAGE *
make_mos_image( SymbolTable *st, JoinNode *node, transform_fn tfn, void *a )
{
	IMAGE *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 )) ||
			!(out = im_open_local( st->im, node->name, "p" )) )
			return( NULL );

		if( node->type == JOIN_LR ) {
			if( im_lrmerge( im1, im2, out, 
				-node->dx, -node->dy, node->mwidth ) )
				return( NULL );
		}
		else {
			if( im_tbmerge( im1, im2, out, 
				-node->dx, -node->dy, node->mwidth ) )
				return( NULL );
		}

		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 )) ||
			!(out = im_open_local( st->im, node->name, "p" )) )
			return( NULL );

		if( node->type == JOIN_LRROTSCALE ) {
			if( im__lrmerge1( im1, im2, out, 
				node->a, node->b, node->dx, node->dy,
				node->mwidth ) )
				return( NULL );
		}
		else {
			if( im__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:
		error_exit( "internal error #982369824375987" );
		/*NOTEACHED*/
		return( NULL );
	}

	return( out );
}

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

	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( root->type == JOIN_LR ) {
			if( im_lrmerge( im1, im2, out, 
				-root->dx, -root->dy, root->mwidth ) )
				return( -1 );
		}
		else {
			if( im_tbmerge( im1, im2, out, 
				-root->dx, -root->dy, root->mwidth ) )
				return( -1 );
		}

		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( im__lrmerge1( im1, im2, out, 
				root->a, root->b, root->dx, root->dy,
				root->mwidth ) )
				return( -1 );
		}
		else {
			if( im__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 )) || im_copy( im1, out ) )
			return( -1 );

		break;

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

		break;

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

	return( 0 );
}

/* Find correction factors.
 */
static int
find_factors( SymbolTable *st, double gamma )
{
	DOUBLEMASK *K;
	DOUBLEMASK *M;
	DOUBLEMASK *m1, *m2, *m3, *m4, *m5;
	double total;
	double avg;
	int i;

	/* Make output matricies.
	 */
	if( !(K = im_local_dmask( st->im, 
			im_create_dmask( "K", 1, st->novl ) )) ||
		!(M = im_local_dmask( st->im, 
			im_create_dmask( "M", st->nim-1, st->novl ) )) ) 
		return( -1 );
	fill_matricies( st, gamma, K, M );
#ifdef DEBUG
	im_write_dmask( K );
	im_write_dmask( M );
#endif /*DEBUG*/

	/* Calculate LMS.
	 */
	if( !(m1 = im_local_dmask( st->im, im_mattrn( M, "lms:1" ) )) )
		return( -1 );
	if( !(m2 = im_local_dmask( st->im, im_matmul( m1, M, "lms:2" ) )) )
		return( -1 );
	if( !(m3 = im_local_dmask( st->im, im_matinv( m2, "lms:3" ) )) )
		return( -1 );
	if( !(m4 = im_local_dmask( st->im, im_matmul( m3, m1, "lms:4" ) )) )
		return( -1 );
	if( !(m5 = im_local_dmask( st->im, im_matmul( m4, K, "lms:5" ) )) )
		return( -1 );

	/* Make array of correction factors.
	 */
	if( !(st->fac = IM_ARRAY( st->im, st->nim, double )) )
		return( -1 );
	for( i = 0; i < m5->ysize; i++ )
		st->fac[i + 1] = m5->coeff[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 im_global_balance():\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" );
	im__map_table( st, print_overlap_errors, NULL, &total );
	printf( "RMS error = %g\n", sqrt( total / st->novl ) );

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

	return( 0 );
}

/* Look for all leaves, make sure we have a transformed version of each.
 */
static void *
generate_trn_leaves( JoinNode *node, SymbolTable *st )
{
	if( node->type == JOIN_LEAF ) {
		/* Check for image.
		 */
		if( !node->im ) {
			im_error( "im_global_balance", 
				_( "unable to open \"%s\"" ), node->name );
			return( node );
		}
		if( node->trnim ) 
			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
			if( !(node->trnim = 
				im_open_local( node->st->im, 
					"trnleaf:1", "p" )) ||
				vips__affine( node->im, node->trnim, 
					&node->cumtrn ) ) 
				return( node );
	}

	return( NULL );
}

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

	/* Print parsed data.
	 */
#ifdef DEBUG
	printf( "Input files:\n" );
	im__map_table( st, 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( im__map_table( st, generate_trn_leaves, st, NULL ) )
		return( -1 );

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

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

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

	/* And print overlaps.
	 */
#ifdef DEBUG
	printf( "\nLeaf to be 1.000:\n" );
	print_node( st->leaf );
	printf( "\nOverlaps:\n" );
	im__map_table( st, 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 IMAGE *
transform( JoinNode *node, double *gamma )
{
	SymbolTable *st = node->st;
	IMAGE *in = node->im;
	double fac = st->fac[node->index];

	IMAGE *out = im_open_local( st->im, node->name, "p" );

	IMAGE *t1 = im_open_local( out, "transform:1", "p" );
	IMAGE *t2 = im_open_local( out, "transform:2", "p" );
	IMAGE *t3 = im_open_local( out, "transform:3", "p" );
	IMAGE *t4 = im_open_local( out, "transform:4", "p" );
	IMAGE *t5 = im_open_local( out, "transform:5", "p" );

	if( !out || !t1 || !t2 || !t3 || !t4 || !t5 )
		return( NULL );

	if( fac == 1.0 ) {
		/* Easy!
		 */
		out = in;
	}
	else if( in->BandFmt == IM_BANDFMT_UCHAR ) {
		if( im_identity( t1, 1 ) || 
			im_powtra( t1, t2, 1.0 / (*gamma) ) ||
			im_lintra( fac, t2, 0.0, t3 ) ||
			im_powtra( t3, t4, *gamma ) ||
			im_clip2fmt( t4, t5, IM_BANDFMT_UCHAR ) ||
			im_maplut( in, out, t5 ) )
			return( NULL );
	}
	else if( in->BandFmt == IM_BANDFMT_USHORT ) {
		if( im_identity_ushort( t1, 1, 65535 ) || 
			im_powtra( t1, t2, 1.0 / (*gamma) ) ||
			im_lintra( fac, t2, 0.0, t3 ) ||
			im_powtra( t3, t4, *gamma ) ||
			im_clip2fmt( t4, t5, IM_BANDFMT_USHORT ) ||
			im_maplut( in, out, t5 ) )
			return( NULL );
	}
	else {
		/* Just im_lintra it.
		 */
		if( im_lintra( fac, in, 0.0, t1 ) ||
			im_clip2fmt( t1, out, in->BandFmt ) )
			return( NULL );
	}

	return( out );
}

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

	IMAGE *out = im_open_local( st->im, node->name, "p" );
	IMAGE *t1 = im_open_local( out, "transform:1", "p" );
	IMAGE *t2 = im_open_local( out, "transform:2", "p" );
	IMAGE *t3 = im_open_local( out, "transform:3", "p" );
	IMAGE *t4 = im_open_local( out, "transform:4", "p" );

	if( !out || !t1 || !t2 || !t3 || !t4 )
		return( NULL );

	if( fac == 1.0 ) {
		/* Easy!
		 */
		out = in;
	}
	else if( in->BandFmt == IM_BANDFMT_UCHAR ) {
		if( im_identity( t1, 1 ) || 
			im_powtra( t1, t2, 1/(*gamma) ) ||
			im_lintra( fac, t2, 0.0, t3 ) ||
			im_powtra( t3, t4, *gamma ) ||
			im_maplut( in, out, t4 ) )
			return( NULL );
	}
	else if( in->BandFmt == IM_BANDFMT_USHORT ) {
		if( im_identity_ushort( t1, 1, 65535 ) || 
			im_powtra( t1, t2, 1/(*gamma) ) ||
			im_lintra( fac, t2, 0.0, t3 ) ||
			im_powtra( t3, t4, *gamma ) ||
			im_maplut( in, out, t4 ) )
			return( NULL );
	}
	else {
		/* Just im_lintra it.
		 */
		if( im_lintra( fac, in, 0.0, out ) )
			return( NULL );
	}

	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 = im__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( im__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:
 * @in: mosaic to rebuild
 * @out: output image
 * @...: %NULL-terminated list of optional named arguments
 * 
 * Optional arguments:
 *
 * @gamma: gamma of source images
 * @int_output: %TRUE for integer image output
 *
 * vips_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 #VIPS_FORMAT_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.
 *
 * See also: vips_remosaic().
 *
 * 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 );
}
