/* A list of things. Do automatic iteration of unary and binary operators on
 * us. 
 *	List [1, 2] + [2, 3] -> List [3, 5]
 *	hd (List [2, 3]) -> 2
 *	List [] == [] -> true
 */
List value = class
	_Object {
	_check_args = [
		[value, "value", check_list]
	];

	// methods
    oo_binary_table op x = [
		[apply2 op value x',
			op.op_name == "subscript" || op.op_name == "subscript'" ||
			op.op_name == "equal" || op.op_name == "equal'"],
		[this.List (apply2 op value x'),
			op.op_name == "join" || op.op_name == "join'"],
		[this.List (map2 (apply2 op) value x'),
			is_list x'],
		[this.List (map (apply2 op' x) value),
			true]
	]
	{
		op' = oo_converse op;

		// strip the List wrapper, if any
		x'
			= x.value, is_List x
			= x;

		apply2 op x1 x2
			= oo_binary_function op x1 x2, is_class x1 
			= oo_binary'_function op x1 x2, is_class x2
			= op.fn x1 x2;
	};

	oo_unary_table op = [
		[apply value,
			op.op_name == "hd" || op.op_name == "tl"],
		[this.List (map apply value),
			true]
	]
	{
		apply x
			= oo_unary_function op x, is_class x
			= op.fn x;
	}
}

/* A group of things. Loop the operation over the group.
 */
Group value = class 
	_Object {
	_check_args = [
		[value, "value", check_list]
	];

	// methods
    oo_binary_table op x = [
		// if_then_else is really a trinary operator
		[map_trinary ite this x?0 x?1,
			op.op_name == "if_then_else"],
		[map_binary op.fn this x,
			is_Group x],
		[map_unary (\a op.fn a x) this,
			true]
	]
	{
		// need ite as a true trinary
		ite a b c = if a then b else c;

		// we can't call map_trinary directly, since it uses Group and we
		// don't support mutually recursive top-level functions :-(
		// copy-paste it here, keep in sync with the version in _stdenv
		map_nary fn args
			= fn args, groups == []
			= Group (map process [0, 1 .. shortest - 1])
		{
			groups = filter is_Group args;

			shortest = foldr1 min_pair (map (len @ get_value) groups);

			process n
				= [], any (map (is_noval n) args)
				= map_nary fn (map (extract n) args)
			{
				extract n arg
					= arg.value?n, is_Group arg
					= arg;

				is_noval n arg = is_Group arg && arg.value?n == [];
			}
		}

		map_unary fn a = map_nary (list_1ary fn) [a];
		map_binary fn a b = map_nary (list_2ary fn) [a, b];
		map_trinary fn a b c = map_nary (list_3ary fn) [a, b, c];
	}

	oo_unary_table op = [
		[map_unary op.fn this,
			true]
	]
	{
		// we can't call map_trinary directly, since it uses Group and we
		// don't support mutually recursive top-level functions :-(
		// copy-paste it here, keep in sync with the version in _stdenv
		map_nary fn args
			= fn args, groups == []
			= Group (map process [0, 1 .. shortest - 1])
		{
			groups = filter is_Group args;

			shortest = foldr1 min_pair (map (len @ get_value) groups);

			process n
				= [], any (map (is_noval n) args)
				= map_nary fn (map (extract n) args)
			{
				extract n arg
					= arg.value?n, is_Group arg
					= arg;

				is_noval n arg = is_Group arg && arg.value?n == [];
			}
		}

		map_unary fn a = map_nary (list_1ary fn) [a];
	}
}

/* Single real number ... eg slider.
 */
Real value = class 
	_Object {
	_check_args = [
		[value, "value", check_real]
	];

	// methods
	oo_binary_table op x = [
		[this.Real (op.fn this.value x.value), 
			is_Real x &&
			op.type == Operator_type.ARITHMETIC],
		[this.Real (op.fn this.value x), 
			is_real x && 
			op.type == Operator_type.ARITHMETIC],
		[op.fn this.value x.value, 
			is_Real x && 
			op.type == Operator_type.RELATIONAL],
		[op.fn this.value x, 
			!is_class x]
	] ++ super.oo_binary_table op x;

	oo_unary_table op = [
		[this.Real (op.fn this.value),
			op.type == Operator_type.ARITHMETIC],
		[op.fn this.value,
			true]
	];
}

/* Single bool ... eg Toggle.
 */
Bool value = class 
	_Object {
	_check_args = [
		[value, "value", check_bool]
	];

	// methods
	oo_binary_table op x = [
		[op.fn this.value x, 
			op.op_name == "if_then_else"],
		[this.Bool (op.fn this.value x.value), 
			is_Bool x],
		[this.Bool (op.fn this.value x), 
			is_bool x]
	] ++ super.oo_binary_table op x;

	oo_unary_table op = [
		[this.Bool (op.fn this.value),
			op.type == Operator_type.ARITHMETIC ||
			op.type == Operator_type.RELATIONAL],
		[op.fn this.value,
			true]
	];
}

/* An editable string.
 */
String caption value = class 
	_Object {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_string]
	];
}

/* An editable real number.
 */
Number caption value = class 
	scope.Real value {
	_check_args = [
		[caption, "caption", check_string]
	];

	Real x = this.Number caption x;
}

/* An editable expression.
 */
Expression caption expr = class 
	(if is_class expr then expr else _Object) {
	_check_args = [
		[caption, "caption", check_string],
		[expr, "expr", check_any]
	];
}

/* A ticking clock.
 */
Clock interval value = class 
	scope.Real value {
	_check_args = [
		[interval, "interval", check_real]
	];

	Real x = this.Clock interval x;
}

/* An editable filename.
 */
Pathname caption value = class 
	_Object {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_string]
	];
}

/* An editable fontname.
 */
Fontname caption value = class 
	_Object {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_string]
	];
}

/* Vector type ... just a finite list of real. Handy for wrapping an
 * argument to eg. im_lintra_vec. Make it behave like a single pixel image.
 */
Vector value = class
	_Object {
	_check_args = [
		[value, "value", check_real_list]
	];

	bands = len value;

	// methods
	oo_binary_table op x = [
		// Vector ++ Vector means bandwise join
		[this.Vector (op.fn this.value x.value), 
			is_Vector x &&
			(op.op_name == "join" || op.op_name == "join'")],
		[this.Vector (op.fn this.value [get_number x]), 
			has_number x &&
			(op.op_name == "join" || op.op_name == "join'")],
		// Vector ? number means extract element
		[op.fn this.value (get_real x), 
			has_real x &&
			(op.op_name == "subscript" || 
				op.op_name == "subscript'")],
		// extra check for lengths equal
		[this.Vector (map_binaryl op.fn this.value x.value), 
			is_Vector x &&
			len value == len x.value &&
			op.type == Operator_type.ARITHMETIC],
		[this.Vector (map_binaryl op.fn this.value (get_real x)), 
			has_real x &&
			op.type == Operator_type.ARITHMETIC],

		// need extra length check
		[this.Vector (map bool_to_real 
			(map_binaryl op.fn this.value x.value)), 
			is_Vector x &&
			len value == len x.value &&
			op.type == Operator_type.RELATIONAL],
		[this.Vector (map bool_to_real 
			(map_binaryl op.fn this.value (get_real x))), 
			has_real x &&
			op.type == Operator_type.RELATIONAL],
		[this.Vector (op.fn this.value x.value),
			is_Vector x &&
			len value == len x.value &&
			op.type == Operator_type.COMPOUND_REWRAP],
		[x.Image (vec op'.op_name x.value value),
			is_Image x],
		[vec op'.op_name x value,
			is_image x],
		[op.fn this.value x, 
			is_real x]
	] ++ super.oo_binary_table op x
	{
		op' = oo_converse op;
	};

	oo_unary_table op = [
		[this.Vector (map_unaryl op.fn this.value),
			op.type == Operator_type.ARITHMETIC],
		[this.Vector (map bool_to_real
			(map_unaryl op.fn this.value)),
			op.type == Operator_type.RELATIONAL],
		[this.Vector (op.fn this.value),
			op.type == Operator_type.COMPOUND_REWRAP],
		[op.fn this.value,
			true]
	];

	// turn an ip bool (or a number, for Vector) into VIPSs 255/0
	bool_to_real x
		= 255, is_bool x && x
		= 255, is_number x && x != 0
		= 0;
}

/* A rectangular array of real.
 */
Matrix_base value = class 
	_Object {
	_check_args = [
		[value, "value", check_matrix]
	];

	// calculate these from value
	width = len value?0;
	height = len value;

	// extract a rectanguar area
	extract left top width height
		= this.Matrix_base 
			((map (take width) @ map (drop left) @
				take height @ drop top) value);

	// methods
	oo_binary_table op x = [
		// mat multiply is special
		[this.Matrix_base mul.value,
			is_Matrix x &&
			op.op_name == "multiply"],
		[this.Matrix_base mul'.value,
			is_Matrix x &&
			op.op_name == "multiply'"],

		// mat divide is also special
		[this.Matrix_base div.value,
			is_Matrix x &&
			op.op_name == "divide"],
		[this.Matrix_base div'.value,
			is_Matrix x &&
			op.op_name == "divide'"],

		// power -1 means invert
		[this.Matrix_base inv.value,
			is_real x && x == -1 &&
			op.op_name == "power"],
		[this.Matrix_base sq.value,
			is_real x && x == 2 &&
			op.op_name == "power"],
		[error "matrix **-1 and **2 only",
			op.op_name == "power" ||
			op.op_name == "power'"],

		// matrix op vector ... treat a vector as a 1 row matrix
		[this.Matrix_base (map (map_binaryl op'.fn x.value) this.value),
			is_Vector x &&
			op.type == Operator_type.ARITHMETIC],
		[this.Matrix_base (map_binaryl op.fn this.value x.value),
			(is_Matrix x || is_Real x) && 
			op.type == Operator_type.ARITHMETIC],

		[this.Matrix_base (map_binaryl op.fn this.value x),
			is_real x && 
			op.type == Operator_type.ARITHMETIC],

		// compound ... don't do iteration
		[this.Matrix_base (op.fn this.value x.value),
			(is_Matrix x || is_Real x || is_Vector x) &&
			op.type == Operator_type.COMPOUND_REWRAP]
	] ++ super.oo_binary_table op x
	{
		mul = im_matmul this x;
		mul' = im_matmul x this;
		div = im_matmul this (im_matinv x);
		div' = im_matmul x (im_matinv this);
		inv = im_matinv this;
		sq = im_matmul this this;
		op' = oo_converse op;
	}

	oo_unary_table op = [
		[this.Matrix_base (map_unaryl op.fn this.value),
			op.type == Operator_type.ARITHMETIC],
		[this.Matrix_base (op.fn this.value),
			op.type == Operator_type.COMPOUND_REWRAP],
		[op.fn this.value,
			true]
	];
}

/* How to display a matrix: text, sliders, toggles, or text plus scale/offset.
 */
Matrix_display = class {
        text = 0;
        slider = 1;
        toggle = 2;
        text_scale_offset = 3;

        is_display = member [text, slider, toggle, text_scale_offset];
}

/* A matrix as VIPS sees them ... add scale, offset and filename. For nip, add
 * a display type as well to control how the widget renders.
 */
Matrix_vips value scale offset filename display = class 
	scope.Matrix_base value {
	_check_args = [
		[scale, "scale", check_real],
		[offset, "offset", check_real],
		[filename, "filename", check_string],
		[display, "display", check_matrix_display]
	];

	Matrix_base x = this.Matrix_vips x scale offset filename display;
}

/* A plain 'ol matrix which can be passed to VIPS.
 */
Matrix value = class 
	Matrix_vips value 1 0 "" Matrix_display.text {}

/* Specialised constructors ... for convolutions, recombinations and
 * morphologies.
 */
Matrix_con scale offset value = class
	Matrix_vips value scale offset "" Matrix_display.text_scale_offset {};

Matrix_rec value = class
	Matrix_vips value 1 0 "" Matrix_display.slider {};

Matrix_mor value = class
	Matrix_vips value 1 0 "" Matrix_display.toggle {};

Matrix_file filename = (im_read_dmask @ expand @ search) filename;

/* A CIE colour ... a triple, plus a format (eg XYZ, Lab etc)
 */
Colour colour_space value = class
	scope.Vector value {
	_check_args = [
		[colour_space, "colour_space", check_colour_space]
	];
	_check_all = [
		[is_list_len 3 value, "len value == 3"]
	];

	Vector x = this.Colour colour_space x;

	// make a colour-ish thing from an image
	// back to Colour if we have another 3 band image
	// to a vector if bands > 1
	// to a number otherwise
	itoc im
		= this.Colour nip_type (to_matrix im).value?0, 
			bands == 3
		= scope.Vector (map mean (bandsplit im)),
			bands > 1
		= mean im
	{
		type = get_header "Type" im;
		bands = get_header "Bands" im;
		nip_type = Image_type.colour_spaces.lookup 1 0 type;
	}

	// methods
	oo_binary_table op x = [
		[itoc (op.fn 
			((float) (to_image this).value) 
			((float) (to_image x).value)),
			// here REWRAP means go via image
			op.type == Operator_type.COMPOUND_REWRAP]
	] ++ super.oo_binary_table op x;

	oo_unary_table op = [
		[itoc (op.fn ((float) (to_image this).value)),
			op.type == Operator_type.COMPOUND_REWRAP]
	] ++ super.oo_unary_table op;
}

// a subclass with widgets for picking a space and value
Colour_picker default_colour default_value = class 
	Colour space.value_name colour.expr {
	_vislevel = 3;

	space = Option_enum Image_type.colour_spaces "Colour space" default_colour;
	colour = Expression "Colour value" default_value;

	Colour_edit colour_space value = 
		Colour_picker colour_space value;
}

/* Base scale type.
 */
Scale caption from to value = class 
	scope.Real value {
	_check_args = [
		[caption, "caption", check_string],
		[from, "from", check_real],
		[to, "to", check_real]
	];
	_check_all = [
		[from < to, "from < to"]
	];

	Real x = this.Scale caption from to x;

	// methods
	oo_binary_table op x = [
		[this.Scale caption (op.fn this.from x.from) (op.fn this.to x.to)
			(op.fn this.value x.value), 
			is_Scale x &&
			op.type == Operator_type.ARITHMETIC],
		[this.Scale caption (op.fn this.from x) (op.fn this.to x)
			(op.fn this.value x), 
			is_real x && 
			op.type == Operator_type.ARITHMETIC]
	] ++ super.oo_binary_table op x;
}

/* Base toggle type.
 */
Toggle caption value = class 
	scope.Bool value {
	_check_args = [
		[caption, "caption", check_string],
		[value, "value", check_bool]
	];

	Bool x = this.Toggle caption x;
}

/* Base option type.
 */
Option caption labels value = class 
	scope.Real value {
	_check_args = [
		[caption, "caption", check_string],
		[labels, "labels", check_string_list],
		[value, "value", check_uint]
	];
}

Option_enum enum caption value_name = class
	Option caption enum.names (index (equal value_name) enum.names) {
	// corresponding thing
	value_thing = enum.get_thing value_name;

	Option_edit caption labels value 
		= this.Option_enum enum caption (enum.names ? value);
}

/* A rectangle. width and height can be -ve.
 */
Rect left top width height = class 
	_Object {
	_check_args = [
		[left, "left", check_real],
		[top, "top", check_real],
		[width, "width", check_real],
		[height, "height", check_real]
	];

	// derived
	right = left + width;
	bottom = top + height;

	oo_binary_table op x = [
		[equal x, 
			is_Rect x &&
			(op.op_name == "equal" || op.op_name == "equal'")],
		[!equal x, 
			is_Rect x &&
			(op.op_name == "not_equal" || 
				op.op_name == "not_equal'")],

		// binops with a complex are the same as (comp op comp)
		[oo_binary_function op this (Rect (re x) (im x) 0 0),
			is_complex x],

		// all others are just pairwise
		[this.Rect left' top' width' height',
			is_Rect x && 
			op.type == Operator_type.ARITHMETIC],
		[this.Rect left'' top'' width'' height'',
			has_number x && 
			op.type == Operator_type.ARITHMETIC]
	] ++ super.oo_binary_table op x
	{
		left' = op.fn left x.left;
		top' = op.fn top x.top;
		width' = op.fn width x.width;
		height' = op.fn height x.height;

		left'' = op.fn left x';
		top'' = op.fn top x';
		width'' = op.fn width x';
		height'' = op.fn height x';
		x' = get_number x;
	}

	oo_unary_table op = [
		// arithmetic uops just map
		[this.Rect left' top' width' height',
			op.type == Operator_type.ARITHMETIC],

		// compound uops are just like ops on complex
		// do (width, height) so thing like abs(Arrow) work as you'd expect
		[op.fn (width, height),
			op.type == Operator_type.COMPOUND]
	] ++ super.oo_unary_table op
	{
		left' = op.fn left;
		top' = op.fn top;
		width' = op.fn width;
		height' = op.fn height;
	}

	// empty? ie. contains no pixels
	is_empty = width == 0 || height == 0;

	// normalised version, ie. make width/height +ve and flip the origin
	nleft
		= left + width, width < 0
		= left;
	ntop
		= top + height, height < 0
		= top;
	nwidth = abs width;
	nheight = abs height;
	nright = nleft + nwidth;
	nbottom = ntop + nheight;

	equal x = left == x.left && top == x.top &&
		  width == x.width && height == x.height;

	// contains a point?
	includes_point x y
		= nleft <= x && x <= nright && ntop <= y && y <= nbottom;

	// contains a rect? just test top left and bottom right points
	includes_rect r
		= includes_point r.nleft r.ntop &&
			includes_point r.nright r.nbottom;

	// bounding box of two rects
	// if either is empty, can just return the other
	union r
		= r, is_empty
		= this, r.is_empty
		= Rect left' top' width' height'
	{
		left' = min_pair nleft r.nleft;
		top' = min_pair ntop r.ntop;
		width' = max_pair nright r.nright - left';
		height' = max_pair nbottom r.nbottom - top';
	}

	// intersection of two rects ... empty rect if no intersection
	intersect r
		= Rect left' top' width'' height''
	{
		left' = max_pair nleft r.nleft;
		top' = max_pair ntop r.ntop;
		width' = min_pair nright r.nright - left';
		height' = min_pair nbottom r.nbottom - top';
		width''
			= width', width > 0
			= 0;
		height''
			= height', height > 0
			= 0;
	}

	// expand/collapse by n pixels
	margin_adjust n
		= Rect (left - n) (top - n) (width + 2 * n) (height + 2 * n);
}

/* Values for Compression field in image.
 */
Image_compression = class {
	NONE = 0;
	NO_COMPRESSION = 0;
	TCSF_COMPRESSION = 1;
	JPEG_COMPRESSION = 2;
	LABPACK_COMPRESSED = 3;
	RGB_COMPRESSED = 4;
	LUM_COMPRESSED = 5;
}

/* Values for Coding field in image.
 */
Image_coding = class {
	NONE = 0;
	NOCODING = 0;
	COLQUANT = 1;
	LABPACK = 2;
}

/* Values for BandFmt field in image.
 */
Image_format = class {
	UCHAR = 0;
	CHAR = 1;
	USHORT = 2;
	SHORT = 3;
	UINT = 4;
	INT = 5;
	FLOAT = 6;
	COMPLEX = 7;
	DOUBLE = 8;
	DPCOMPLEX = 9;
	NOTSET = -1;

	maxval fmt 
		= [
			255,		// UCHAR
			127,		// CHAR
			65535,		// USHORT
			32767,		// SHORT
			4294967295,	// UINT
			2147483647,	// INT
			255,		// FLOAT
			255,		// COMPLEX
			255,		// DOUBLE
			255			// DPCOMPLEX
		] ? fmt, fmt >= 0 && fmt <= DPCOMPLEX
		= error (_ "bad value for BandFmt");
}

/* A lookup table.
 */
Table value = class 
	_Object {
	_check_args = [
		[value, "value", check_rectangular]
	];

	/* present col x: is there an x in column col
	 */
	present col x = member (map (extract col) value) x;

	/* Look on column from, return matching item in column to.
	 */
	lookup from to x
		= value?n?to, n >= 0
		= error (_ "item" ++ " " ++ print x ++ " " ++ _ "not in table")
	{
		n = index (equal x) (map (extract from) value);
	}
}

/* A two column lookup table with the first column a string and the second a
 * thing. Used for representing various enums. Option_enum makes a selector
 * from one of these.
 */
Enum value = class 
	Table value {
	_check_args = [
		[value, "value", check_enum]
	]
	{
		check_enum = [is_enum, _ "is [[char, *]]"];
		is_enum x = 
			is_rectangular x && 
			is_listof is_string (map (extract 0) x);
	}

	// handy ... all the names and things as lists
	names = map (extract 0) value;
	things = map (extract 1) value;

	// is a legal name or thing
	has_name x = this.present 1 x;
	has_thing x = this.present 0 x;

	// map things to strings and back
	get_name x = this.lookup 1 0 x;
	get_thing x = this.lookup 0 1 x;
}

/* Type field.
 */
Image_type = class {
	MULTIBAND = 0;
	B_W = 1;
	LUMINANCE = 2;
	XRAY = 3;
	IR = 4;
	YUV = 5;
	RED_ONLY = 6;
	GREEN_ONLY = 7;
	BLUE_ONLY = 8;
	POWER_SPECTRUM = 9;
	HISTOGRAM = 10;
	LUT = 11;
	XYZ = 12;
	LAB = 13;
	CMC = 14;
	CMYK = 15;
	LABQ = 16;
	RGB = 17;
	UCS = 18;
	LCH = 19;
	LABS = 21;
	sRGB = 22;
	YXY = 23;
	FOURIER = 24;
	RGB16 = 25;
	GREY16 = 26;

	/* Table to get names <-> numbers.
	 */
	type_names = Enum [
		["MULTIBAND", MULTIBAND],
		["B_W", B_W],
		["LUMINANCE", LUMINANCE],
		["XRAY", XRAY],
		["IR", IR],
		["YUV", YUV],
		["RED_ONLY", RED_ONLY],
		["GREEN_ONLY", GREEN_ONLY],
		["BLUE_ONLY", BLUE_ONLY],
		["POWER_SPECTRUM", POWER_SPECTRUM],
		["HISTOGRAM", HISTOGRAM],
		["LUT", LUT],
		["XYZ", XYZ],
		["LAB", LAB],
		["CMC", CMC],
		["CMYK", CMYK],
		["LABQ", LABQ],
		["RGB", RGB],
		["UCS", UCS],
		["LCH", LCH],
		["LABS", LABS],
		["sRGB", sRGB],
		["YXY", YXY],
		["FOURIER", FOURIER],
		["RGB16", RGB16],
		["GREY16", GREY16]
	];

	/* Table relating nip's colour space names and VIPS's Type numbers.
	 * Options generated from this, so match the order to the order in the
	 * Colour menu.
	 */
	colour_spaces = Enum [
		["sRGB", sRGB],
		["Lab", LAB],
		["LCh", LCH],
		["XYZ", XYZ],
		["Yxy", YXY],
		["UCS", UCS]
	];

	/* A slightly larger table ... the types of colorimetric image we can
	 * have. Add mono, and the S and Q forms of LAB.
	 */
	image_colour_spaces = Enum [
		["Mono", B_W],
		["sRGB", sRGB],
		["RGB16", RGB16],
		["GREY16", GREY16],
		["Lab", LAB],
		["LabQ", LABQ],
		["LabS", LABS],
		["LCh", LCH],
		["XYZ", XYZ],
		["Yxy", YXY],
		["UCS", UCS]
	];
}

/* Base image type. Simple layer over vips_image.
 */
Image value = class 
	_Object {
	_check_args = [
		[value, "value", check_image]
	];

	// fields from VIPS header
	width = get_width value;
	height = get_height value;
	bands = get_bands value;
	format = get_format value;
	bits = get_bits value;
	coding = get_coding value;
	type = get_type value;
	xres = get_header "Xres" value;
	yres = get_header "Yres" value;
	xoffset = get_header "Xoffset" value;
	yoffset = get_header "Yoffset" value;
	filename = get_header "filename" value;
	
	// convenience ... the area our pixels occupy, as a rect
	rect = Rect 0 0 width height;

	// operator overloading
	// (op Image Vector) done in Vector class
	oo_binary_table op x = [
		// handle image ++ constant here
		[wrap join_result_image,
			(has_real x || is_Vector x) &&
			(op.op_name == "join" || op.op_name == "join'")],
		// image ++ image is slightly different ... we want to
		// sizealike, but we must not bandalike
		[wrap 
			(op.fn (get_image resized?0) (get_image resized?1)),
			has_image x &&
			(op.op_name == "join" || op.op_name == "join'")],
		[wrap ite_result_image,
			op.op_name == "if_then_else"],
		// arithmetic and reational binops between image resize
		// and band_alike images to match
		[wrap 
			(op.fn (get_image rebanded?0) (get_image rebanded?1)),
			has_image x &&
			(op.type == Operator_type.ARITHMETIC ||
				op.type == Operator_type.RELATIONAL)],
		// other op types don't resize
		[wrap (op.fn this.value (get_image x)),
			has_image x],
		[wrap (op.fn this.value (get_number x)),
			has_number x],
		// if it's not a class on the RHS, handle here ... just apply and
		// rewrap
		[wrap (op.fn this.value x),
			!is_class x]
		// all other cases handled by other classes
	] ++ super.oo_binary_table op x
	{
		// wrap the result with this 
		// x can be a non-image, eg. compare "Image v == []" vs. "Image v ==
		// 12"
		wrap x
			= x, op.type == Operator_type.COMPOUND ||
				!is_image x
			= this.Image x;

		join_result_image 
			= value ++ new_stuff, op.op_name == "join"
			= new_stuff ++ value
		{
			new_stuff = image_new width height new_bands
				format
				coding
				Image_type.B_W x xoffset yoffset;
			new_bands
				= get_bands x, has_bands x
				= 1;
		}

		[then_part, else_part] = x;

		// get things about our output from inputs in this order
		objects = [then_part, else_part, this];

		// properties of our output image
		target_bands = get_member_list has_bands get_bands objects;
		target_type = get_member_list has_type get_type objects;

		// if one of then/else is an image, get the target format from that
		// otherwise, let the non-image objects set the target
		target_format 
			= get_member_list has_format get_format x, 
				has_member_list has_format x
			= [];

		to_image x
			= x, is_image x
			= x.value, is_Image x
			= clip2fmt target_format im, target_format != []
			= im
		{
			im = im_black width height target_bands + x;
		}

		[if_size, then_size, else_size] = 
			size_alike (value : formats_alike (map to_image x));

		ite_result_image = image_set_type target_type
			(if if_size then then_size else else_size);

		resized = size_alike [this, x];
		rebanded = bands_alike resized;
	}

	// FIXME ... yuk ... don't use operator hints, just always rewrap if
	// we have an image result
	// forced on us by things like abs:
	// 	abs Vector -> real
	//	abs Image -> Image
	// does not fit well with COMPOUND/whatever scheme
	oo_unary_table op = [
		[this.Image result,
			is_image result],
		[result,
			true]
	]
	{
		result = op.fn this.value;
	}
}

/* Construct an image from a file.
 */
Image_file filename = class 
	Image value {
	_check_args = [
		[filename, "filename", check_string]
	];

	value = vips_image filename;
}

Region image left top width height = class 
	Image value {
	_check_args = [
		[image, "Image", check_Image],
		[left, "left", check_real],
		[top, "top", check_real],
		[width, "width", check_preal],
		[height, "height", check_preal]
	];

	// a rect for our coordinates
	// region.rect gets the rect for the extracted image
	region_rect = Rect left top width height;

	// we need to always succeed ... value is our enclosing image if we're
	// out of bounds
	value 
		= extract_area left top width height image.value,
			image.rect.includes_rect region_rect
		= image.value;
}

Area image left top width height = class
	scope.Region image left top width height {
	Region image left top width height 
		= this.Area image left top width height;
}

Arrow image left top width height = class 
	scope.Rect left top width height {
	_check_args = [
		[image, "Image", check_Image],
		[left, "left", check_real],
		[top, "top", check_real],
		[width, "width", check_real],
		[height, "height", check_real]
	];

	Rect l t w h = this.Arrow image l t w h;
}

HGuide image top = class 
	scope.Arrow image image.rect.left top image.width 0 {
	Arrow image left top width height = this.HGuide image top;
}

VGuide image left = class 
	scope.Arrow image left image.rect.top 0 image.height {
	Arrow image left top width height = this.VGuide image left;
}

Mark image left top = class 
	scope.Arrow image left top 0 0 {
	Arrow image left top width height = this.Mark image left top;
}

// convenience functions: ... specify position as [0 .. 1)

Region_relative image u v w h
	= Region image 
		(image.width * u)
		(image.height * v)
		(image.width * w)
		(image.height * h);

Area_relative image u v w h
	= Area image 
		(image.width * u)
		(image.height * v)
		(image.width * w)
		(image.height * h);

Arrow_relative image u v w h
	= Arrow image 
		(image.width * u)
		(image.height * v)
		(image.width * w)
		(image.height * h);

VGuide_relative image v 
	= VGuide image (image.height * v);

HGuide_relative image u 
	= HGuide image (image.width * u);

Mark_relative image u v
	= Mark image 
		(image.width * u)
		(image.height * v);

Interpolate = class {
	NEAREST_NEIGHBOUR = 0;
	BILINEAR = 1;
	BICUBIC = 2;

	/* Table to map interpol numbers to descriptive strings
	 */
	names = Enum [
		[_ "Nearest neighbour", NEAREST_NEIGHBOUR],
		[_ "Bilinear", BILINEAR],
		[_ "Bicubic", BICUBIC]
	];
}

Render_intent = class {
	PERCEPTUAL = 0;
	RELATIVE = 1;
	SATURATION = 2;
	ABSOLUTE = 3;

	/* Table to get names <-> numbers.
	 */
	names = Enum [
		[_ "Perceptual", PERCEPTUAL],
		[_ "Relative", RELATIVE],
		[_ "Saturation", SATURATION],
		[_ "Absolute", ABSOLUTE]
	];
}

// abstract base class for toolkit menus
Menu = class {}

// a "----" line in a menu
Menuseparator = class Menu {}

// abstract base class for items in menus
Menuitem label tooltip = class Menu {}

Menupullright label tooltip = class Menuitem label tooltip {}

Menuaction label tooltip = class Menuitem label tooltip {}

/* Plots.
 */

Plot_style = class {
	POINT = 0;
	LINE = 1;
	SPLINE = 2;
	BAR = 3;

	names = Enum [
		[_ "Point", POINT],
		[_ "Line", LINE],
		[_ "Spline", SPLINE],
		[_ "Bar", BAR]
	];
}

Plot_format = class {
	YYYY = 0;
	XYYY = 1;
	XYXY = 2;

	names = Enum [
		[_ "YYYY", YYYY],
		[_ "XYYY", XYXY],
		[_ "XYXY", XYXY]
	];
}

Plot_type = class {
	/* Lots of Ys (ie. multiple line plots).
	 */
	YYYY = 0;

	/* First column of matrix is X position, others are Ys (ie. multiple XY 
	 * line plots, all with the same Xes).
	 */
	XYYY = 1;

	/* Many independent XY plots.
	 */
	XYXY = 2;
}

/* "options" is a list of ["key", value] pairs.
 */
Plot options value = class 
	scope.Image value {
	Image value = this.Plot options value;
}

Plot_matrix options value = class 
	Plot options (to_image value).value {
}

Plot_histogram value = class
	scope.Plot [] value {
}

Plot_xy value = class
	scope.Plot [["format", Plot_format.XYYY]] value {
}
