Filter_conv_item = class
	Menupullright "_Convolution" "various spatial convolution filters" {
	/* Some useful masks.
	 */
	filter_blur = Matrix_con 9 0 [[1, 1, 1], [1, 1, 1], [1, 1, 1]];
	filter_sharp = Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]];
	filter_emboss = Matrix_con 1 128 [[-1, 0], [0, 1]];
	filter_laplacian = Matrix_con 1 128 
		[[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]];
	filter_sobel = Matrix_con 1 128 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]];
	filter_lindet = Matrix_con 1 0 [[1, 1, 1], [-2, -2, -2], [1, 1, 1]];

	Blur_item = class
		Menuaction "_Blur" "3x3 blur of image" {
		action x = map_unary (conv filter_blur) x;
	}

	Sharpen_item = class
		Menuaction "_Sharpen" "3x3 sharpen of image" {
		action x = map_unary (conv filter_sharp) x;
	}

	Emboss_item = class
		Menuaction "_Emboss" "1 pixel displace emboss" {
		action x = map_unary (conv filter_emboss) x;
	}

	Laplacian_item = class
		Menuaction "_Laplacian" "3x3 laplacian edge detect" {
		action x = map_unary (conv filter_laplacian) x;
	}

	Sobel_item = class
		Menuaction "So_bel" "3x3 Sobel edge detect" {
		action x 
			= map_unary sobel x
		{
			sobel im
				= abs (a - 128) + abs (b - 128)
			{
				a = conv filter_sobel im;
				b = conv (rot270 filter_sobel) im;
			}
		}
	}

/* 3x3 line detect of image
diagonals should be scaled down by root(2) I guess
Kirk 
*/
	Linedet_item = class
		Menuaction "Li_ne Detect" "3x3 line detect" {
		action x 
			= map_unary lindet x
		{
			lindet im
				= foldr1 max_pair images
			{
				masks = take 4 (iterate rot45 filter_lindet);
				images = map (converse conv im) masks;
			}
		}
	}

	Usharp_item = class
		Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
		action x = class
			_result {
			_vislevel = 3;
	
			size = Option "Radius" [
				"3 pixels",
				"5 pixels",
				"7 pixels",
				"9 pixels",
				"11 pixels",
				"51 pixels"
			] 0;
	
			st = Scale "Smoothness threshold" 0 5 1.5;
			bm = Scale "Brighten by at most" 1 50 10;
			dm = Scale "Darken by at most" 1 50 50;
			fs = Scale "Sharpen flat areas by" (-2) 5 1;
			js = Scale "Sharpen jaggy areas by" (-2) 5 2;
	
			_result
				= map_unary process x
			{
				process in
					= Image in'''
				{
					in' = colour_transform_to Image_type.LABS in.value;
					in'' = sharpen [3, 5, 7, 9, 11, 51]?size st bm dm fs js in';
					in''' = colour_transform_to (get_type in) in'';
				}
			}
		}
	}

	sep1 = Menuseparator;

	Custom_blur_item = class
		Menuaction "Custom B_lur / Sharpen" 
			"blur or sharpen with tuneable parameters" {
		action x = class 
			_result {
			_vislevel = 3;

			type = Option "Type" ["Blur", "Sharpen"] 0;
			r = Scale "Radius" 1 100 1;
			fac = Scale "Amount" 0 1 1;
			layers = Scale "Layers" 1 100 10;
			shape = Option "Mask shape" [
				"Square",
				"Gaussian"
			] 0;
			prec = Option "Precision" ["Int", "Float", "Approximate"] 0;

			_result 
				= map_unary process x
			{
				process in
					= clip2fmt blur.format proc
				{
					mask
						= matrix_blur r.value, shape.value == 0
						= matrix_gaussian_blur r.value;
					blur = [convsep, convsepf, aconvsep layers]?prec mask in;
					proc
						= in + fac * (in - blur), type == 1
						= blur * fac + in * (1 - fac);
				}
			}
		}
	}

	Custom_conv_item = class
		Menuaction "Custom C_onvolution" 
			"convolution filter with tuneable parameters" {
		action x = class 
			_result {
			_vislevel = 3;

			matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
			separable 
				= Toggle "Seperable convolution" false, 
					matrix.width == 1 || matrix.height == 1
				= false;
			type = Option "Convolution type" ["Int", "Float"] 0;
			rotate = Option "Rotate" [
				"Don't rotate", 
				"4 x 45 degrees", 
				"8 x 45 degrees", 
				"2 x 90 degrees"
			] 0;

			_result 
				= map_unary process x
			{
				process in
					= in.Image in'
				{
					conv_fn 
						= im_lindetect, !separable && type == 0 && rotate == 1
						= im_compass, !separable && type == 0 && rotate == 2
						= im_gradient, !separable && type == 0 && rotate == 3
						= im_conv, !separable && type == 0
						= im_convsep, separable && type == 0
						= im_conv_f, !separable && type == 1
						= im_convsep_f, separable && type == 1
						= error "boink!";
					in' = conv_fn in.value matrix;
				}
			}
		}
	}
}

Filter_rank_item = class
	Menupullright "_Rank" "various rank filters" {
	Median_item = class
		Menuaction "_Median" "3x3 median rank filter" {
		action x = map_unary (rank 3 3 4) x;
	}

	Image_rank_item = class
		Menuaction "_Image Rank" "pixelwise rank a list or group of images" {
		action x = class 
			_result {
			_vislevel = 3;

			select 
				= Expression "Rank" ((int) (guess_size / 2))
			{
				guess_size
					= len x, is_list x
					= len x.value, is_Group x
					= 0;
			}

			// can't really iterate over groups ... since we allow a group
			// argument
			_result = rank_image select x;
		}
	}

	Custom_rank_item = class
		Menuaction "Custom _Rank" "rank filter with tuneable parameters" {
		action x = class 
			_result {
			_vislevel = 3;

			window_width = Expression "Window width" 3;
			window_height = Expression "Window height" 3;
			select = Expression "Rank" 
				((int) ((to_real window_width * to_real window_height) / 2));

			_result 
				= map_unary process x
			{
				process in
					= rank window_width window_height select in;
			}
		}
	}
}

Filter_morphology_item = class
	Menupullright "_Morphology" "various morphological filters" {
	/* Some useful masks.
	 */
	mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]];
	mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]];
	mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
	thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]];

	Threshold_item = Select_item.Threshold_item;

	sep1 = Menuseparator;

	Dilate_item = class
		Menupullright "_Dilate" "morphological dilate" {
		Dilate8_item = class
			Menuaction "_8-connected" "dilate with an 8-connected mask" {
			action x = map_unary (dilate mask8) x;
		}

		Dilate4_item = class
			Menuaction "_4-connected" "dilate with a 4-connected mask" {
			action x = map_unary (dilate mask4) x;
		}
	}

	Erode_item = class
		Menupullright "_Erode" "morphological erode" {
		Erode8_item = class
			Menuaction "_8-connected" "erode with an 8-connected mask" {
			action x = map_unary (erode mask8) x;
		}

		Erode4_item = class
			Menuaction "_4-connected" "erode with a 4-connected mask" {
			action x = map_unary (erode mask4) x;
		}
	}

	Custom_morph_item = class
		Menuaction "Custom _Morphology" 
			"convolution morphological operator" {
		action x = class 
			_result {
			_vislevel = 3;

			mask = mask4;
			type = Option "Operation" ["Erode", "Dilate"] 1;
			apply = Expression "Number of times to apply mask" 1;

			_result 
				= map_unary morph x
			{
				morph image
					= Image value'
				{
					fatmask = (iterate (dilate mask) mask)?(to_real apply - 1);

					value'
						= im_erode image.value fatmask, type.value == 0
						= im_dilate image.value fatmask;
				}
			}
		}
	}

	sep2 = Menuseparator;

	Open_item = class
		Menuaction "_Open" "open with an 8-connected mask" {
		action x = map_unary (dilate mask8 @ erode mask8) x;
	}

	Close_item = class
		Menuaction "_Close" "close with an 8-connected mask" {
		action x = map_unary (erode mask8 @ dilate mask8) x;
	}

	Clean_item = class
		Menuaction "C_lean" "remove 8-connected isolated points" {
		action x 
			= map_unary clean x
		{
			clean x = x ^ erode mask1 x;
		}
	}

	Thin_item = class
		Menuaction "_Thin" "thin once" {
		action x 
			= map_unary thinall x
		{
			masks = take 8 (iterate rot45 thin);
			thin1 m x = x ^ erode m x;
			thinall x = foldr thin1 x masks;
		}
	}

	sep3 = Menuseparator;

	Segment_item = class
		Menuaction "_Segment" "break image into disjoint regions" {
		action x = class 
			_result {
			_vislevel = 3;

			segments
				= Expression "Number of disjoint regions" 
					(map_unary (get_header "n-segments") _result);

			_result = map_unary segment x;
		}
	}
}

Filter_fourier_item = class
	Menupullright "_Fourier" "various Fourier filters" {
	preview_size = 64;

	sense_option = Option "Sense" [
		"Pass", 
		"Reject"
	] 0;

	// make a visualisation image 
	make_vis fn = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
		(im_create_fmask preview_size preview_size);

	// make the process function
	process fn in
		= (Image @ fn) (im_flt_image_freq in.value);

	New_ideal_item = class 
		Menupullright "_Ideal" "various ideal Fourier filters" {
		High_low_item = class 
			Menuaction "_High or Low Pass" 
				"highpass/lowpass ideal Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;

				// call a freq func with our parameters
				_params f = f sense.value fc.value 0 0 0 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}

		Ring_item = class 
			Menuaction "_Ring Pass or Ring Reject"
				"ring pass/reject ideal Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				rw = Scale "Ring width" 0.01 0.99 0.5;

				// call a freq func with our parameters
				_params f = f (sense.value + 6) fc.value 
					rw.value 0 0 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}

		Band_item = class 
			Menuaction "_Band Pass or Band Reject"
				"band pass/reject ideal Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
				r = Scale "Radius" 0.01 0.99 0.5;

				// call a freq func with our parameters
				_params f = f (sense.value + 12) fcx.value fcy.value
					r.value 0 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}
	}

	New_gaussian_item = class 
		Menupullright "_Gaussian" "various Gaussian Fourier filters" {
		High_low_item = class 
			Menuaction "_High or Low Pass" 
				"highpass/lowpass Gaussian Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;

				// call a freq func with our parameters
				_params f = f (sense.value + 4) fc.value 
					ac.value 0 0 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}

		Ring_item = class 
			Menuaction "_Ring Pass or Ring Reject"
				"ring pass/reject Gaussian Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
				rw = Scale "Ring width" 0.01 0.99 0.5;

				// call a freq func with our parameters
				_params f = f (sense.value + 10) fc.value 
					rw.value ac.value 0 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}

		Band_item = class 
			Menuaction "_Band Pass or Band Reject"
				"band pass/reject Gaussian Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
				r = Scale "Radius" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;

				// call a freq func with our parameters
				_params f = f (sense.value + 16) fcx.value fcy.value 
					r.value ac.value 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}
	}

	New_butterworth_item = class 
		Menupullright "_Butterworth" 
			"various Butterworth Fourier filters" {
		High_low_item = class 
			Menuaction "_High or Low Pass" 
				"highpass/lowpass Butterworth Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
				o = Scale "Order" 1 10 2;

				// call a freq func with our parameters
				_params f = f (sense.value + 2) o.value fc.value ac.value
						0 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}

		Ring_item = class 
			Menuaction "_Ring Pass or Ring Reject"
				"ring pass/reject Butterworth Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
				rw = Scale "Ring width" 0.01 0.99 0.5;
				o = Scale "Order" 1 10 2;

				// call a freq func with our parameters
				_params f = f (sense.value + 8) o.value fc.value rw.value 
					ac.value 0;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}

		Band_item = class 
			Menuaction "_Band Pass or Band Reject"
				"band pass/reject Butterworth Fourier filter" {
			action x = class
				_result {
				_vislevel = 3;

				sense = sense_option;
				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
				r = Scale "Radius" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
				o = Scale "Order" 1 10 2;

				// call a freq func with our parameters
				_params f = f (sense.value + 14) o.value fcx.value fcy.value
						r.value ac.value;

				visualize_mask = make_vis _params;

				_result = map_unary (process _params) x;
			}
		}
	}
}

Filter_enhance_item = class
	Menupullright "_Enhance" "various enhancement filters" {
	Falsecolour_item = class
		Menuaction "_False Colour" "false colour a mono image" {
		action x = class
			_result {
			_vislevel = 3;
	
			o = Scale "Offset" (-255) 255 0;
			clip = Toggle "Clip colour range" false;
	
			_result 
				= map_unary process x
			{
				process im
					= falsecolour mono''
				{
					mono = colour_transform_to Image_type.B_W im;
					mono' = mono + o;
					mono'' 
						= (unsigned char) mono', clip
						= (unsigned char) (mono' & 0xff);
				}
			}
		}
	}

	Statistical_diff_item = class
		Menuaction "_Statistical Difference" 
			"statistical difference of an image" {
		action x = class
			_result {
			_vislevel = 3;
	
			wsize = Expression "Window size" 11;
			tmean = Expression "Target mean" 128;
			mean_weight = Scale "Mean weight" 0 1 0.8;
			tdev = Expression "Target deviation" 50;
			dev_weight = Scale "Deviation weight" 0 1 0.8;
			border = Toggle "Output image matches input image in size" true;
	
			_result 
				= map_unary process x
			{
				process in 
					= Image in''
				{
					in' = colour_transform_to Image_type.B_W in.value;
					fn
						= im_stdif, border
						= im_stdif_raw;
					in'' = fn in'
						mean_weight.value tmean.expr
						dev_weight.value tdev.expr
						wsize.expr wsize.expr;
				}
			}
		}
	}

	Hist_equal_item = class
		Menupullright "_Equalise Histogram" "equalise contrast" {
		Global_item = class
			Menuaction "_Global" "equalise contrast globally" {
			action x = map_unary hist_equalize x;
		}

		Local_item = class
			Menuaction "_Local" "equalise contrast within a roving window" {
			action x = class 
				_result {
				_vislevel = 3;
	
				window_width = Expression "Window width" 20;
				window_height = Expression "Window height" 20;
	
				_result 
					= map_unary process x
				{
					process in 
						= hist_equalize_local 
							window_width.expr window_height.expr in;
				}
			}
		}
	}
}

Filter_correlate_item = class
	Menupullright "Spatial _Correlation" "calculate correlation surfaces" {
	Correlate_item = class
		Menuaction "_Correlate" "calculate correlation coefficient" {
		action a b 
			= map_binary corr a b
		{
			corr a b
				= correlate a b, 
					a.width <= b.width && a.height <= b.height
				= correlate b a;
		}
	}

	Correlate_fast_item = class
		Menuaction "_Simple Difference" 
			"calculate sum of squares of differences" {
		action a b 
			= map_binary corr a b
		{
			corr a b
				= correlate_fast a b, 
					a.width <= b.width && a.height <= b.height
				= correlate_fast b a;
		}
	}
}

Filter_greyc_item = class 
	Menupullright "_GREYCstoration" "noise-removing filter" {
	Denoise_item = class 
		Menuaction "Denoise" "Noise-removing filter" {
		action x = class 
			_result {
			_vislevel = 3;

			iterations = Scale "Iterations" 1 5 1;
			amplitude = Scale "Amplitude" 1 100 40;
			sharpness = Scale "Sharpness" 0 3 0.9;
			anisotropy = Scale "Anisotropy" 0 1 0.15;
			alpha = Scale "Noise scale" 0 5 0.6;
			sigma = Scale "Geometry regularity" 0 2 1.1;
			dl = Scale "Spatial integration step" 0 1 0.8;
			da = Scale "Angular integration step" 0 90 30;
			gauss_prec = Scale "Precision" 1 10 2;
			interpolation = Option "Interpolation" 
				["Nearest-neighbour", "Bilinear", "Runge-Kutta"] 0;
			fast_approx = Toggle "Fast approximation" true;

			_result = greyc 
				(to_real iterations) (to_real amplitude) (to_real sharpness)
				(to_real anisotropy) (to_real alpha) (to_real sigma) 
				(to_real dl) (to_real da) 
				(to_real gauss_prec) (to_real interpolation) 
				(to_real fast_approx) x;
		}
	}

	Enlarge_item = class
		Menuaction "Enlarge" "Enlarge image" {
		action x = class 
			_result {
			_vislevel = 3;

			scale = Scale "Enlarge" 1 10 3;
			iterations = Scale "Iterations" 1 5 3;
			amplitude = Scale "Amplitude" 1 100 20;
			sharpness = Scale "Sharpness" 0 3 0.2;
			anisotropy = Scale "Anisotropy" 0 1 0.9;
			alpha = Scale "Noise scale" 0 5 0.1;
			sigma = Scale "Geometry regularity" 0 2 1.5;
			dl = Scale "Spatial integration step" 0 1 0.8;
			da = Scale "Angular integration step" 0 90 30;
			gauss_prec = Scale "Precision" 1 10 2;
			interpolation = Option "Interpolation" 
				["Nearest-neighbour", "Bilinear", "Runge-Kutta"] 0;
			fast_approx = Toggle "Fast approximation" true;

			_result = greyc 
				(to_real iterations) (to_real amplitude) (to_real sharpness)
				(to_real anisotropy) (to_real alpha) (to_real sigma) 
				(to_real dl) (to_real da) 
				(to_real gauss_prec) (to_real interpolation) 
				(to_real fast_approx) 
					(resize Interpolate_bilinear
						(to_real scale) (to_real scale) x);
		}
	}
}

Filter_magick_item = class
	Menupullright "Magic_k" "various Image/Graphics Magick filters" {
	system command x = map_unary (system_image command) x;
	radius_widget = Scale "Radius" 1 100 10;
	sigma_widget = Scale "Sigma" 0.1 10 1;
	angle_widget = Scale "Angle" (-360) 360 0;
	text_widget = String "Text to draw" "AaBbCcDdEe";

	print_colour triple
		= concat ["\"#", concat (map fmt triple), "\""]
	{
		fmt x = reverse (take 2 (reverse (print_base 16 (x + 256))));
	}

	Foreground triple = class
		Colour "sRGB" triple {

		_flag = "-fill " ++ print_colour triple;

		Colour_edit space triple = this.Foreground triple;
	}
	foreground_widget = Foreground [0, 0, 0];

	Background triple = class
		Colour "sRGB" triple {

		_flag = "-background " ++ print_colour triple;

		Colour_edit space triple = this.Background triple;
	}
	background_widget = Background [255, 255, 255];

	Antialias value = class
		Toggle "Antialias" value {

		_flag
			= "-antialias", value
			= "+antialias";

		Toggle_edit caption value = this.Antialias value;
	}
	antialias_widget = Antialias true;

	Gravity gravity = class
		Option_string "Gravity" [
			"None",
			"Center",
			"East",
			"Forget",
			"NorthEast",
			"North",
			"NorthWest",
			"SouthEast",
			"South",
			"SouthWest",
			"West",
			"Static"
		] gravity {

		_flag = "-gravity " ++ gravity;

		Option_edit caption labels value = this.Gravity labels?value;
	}
	gravity_widget = Gravity "Center";

	Alpha alpha = class
		Option_string "Alpha" [
			"On", 
			"Off", 
			"Set", 
			"Opaque", 
			"Transparent", 
			"Extract", 
			"Copy", 
			"Shape", 
			"Background"
		] alpha {

		_flag = "-alpha " ++ alpha;

		Option_edit caption labels value = this.Alpha labels?value;
	}
	alpha_widget = Alpha "On";

	Geometry_widget = class {
		_vislevel = 3;

		x = Expression "Y" 0;
		y = Expression "X" 0;
		hoffset = Expression "Horizontal offset" 10;
		voffset = Expression "Vertical offset" 10;

		_flag 
			= concat [print x.expr, "x", print y.expr, 
				format hoffset, format voffset]
		{
			// print an offset ... we want '+' in front of +ve strings
			format offset 
				= concat ["+", print offset.expr], offset.expr >= 0
				= print offset.expr;
		}
	};

	Font_widget = class {
		_vislevel = 3;

		family = Option_string "Family" [
			"Arial",
			"ArialBlack",
			"AvantGarde",
			"BitstreamCharter",
			"Bookman",
			"CenturySchoolbook",
			"ComicSansMS",
			"Courier",
			"CourierNew",
			"DejaVuSans",
			"DejaVuSansMono",
			"DejaVuSerif",
			"Dingbats",
			"FreeMono",
			"FreeSans",
			"FreeSerif",
			"Garuda",
			"Georgia",
			"Helvetica",
			"HelveticaNarrow",
			"Impact",
			"LiberationMono",
			"LiberationSans",
			"LiberationSerif",
			"NewCenturySchlbk",
			"Palatino",
			"Purisa",
			"Symbol",
			"Times",
			"TimesNewRoman",
			"Ubuntu",
			"Verdana",
			"Webdings"
		] "Arial";
		style = Option_string "Style" [
			"Any", "Italic", "Normal", "Oblique"
		] "Normal";
		weight = Scale "Weight" 1 800 400;
		size = Scale "Point size" 1 100 12;
		stretch = Option_string "Stretch" [
			"Any", "Condensed", "Expanded", "ExtraCondensed", "ExtraExpanded", 
			"Normal", "SemiCondensed", "SemiExpanded", "UltraCondensed", 
			"UltraExpanded"
		] "Normal";

		_flag = join_sep " " [
				"-family", family.item,
				"-weight", print weight.value,
				"-pointsize", print size.value,
				"-style", style.item,
				"-stretch", stretch.item];
	}

	Adaptive_blur_item = class
		Menuaction "_Adaptive Blur" "blur less near edges" {
		action x = class 
			_result {
			_vislevel = 3;

			radius = radius_widget; 
			sigma = sigma_widget;
			command = magick_command (concat ["-adaptive-blur ",
				print radius.value, "x", print sigma.value]);

			_result = system command x; 
		}
	}

	Adaptive_sharpen_item = class
		Menuaction "_Adaptive Sharpen" "sharpen more near edges" {
		action x = class 
			_result {
			_vislevel = 3;

			radius = radius_widget; 
			sigma = sigma_widget;
			command = magick_command (concat ["-adaptive-sharpen ",
				print radius.value, "x", print sigma.value]);

			_result = system command x; 
		}
	}

	Alpha_item = class
		Menuaction "_Alpha" "add/remove alpha channel" {
		action x = class 
			_result {
			_vislevel = 3;

			alpha = alpha_widget; 
			command = magick_command alpha._flag; 

			_result = system command x; 
		}
	}

	Annotate_item = class
		Menuaction "_Annotate" "add text annotation" {
		action x = class 
			_result {
			_vislevel = 3;

			text = text_widget;
			font = Font_widget;
			geometry = Geometry_widget; 
			gravity = gravity_widget; 
			foreground = foreground_widget;
			antialias = antialias_widget;
			command = magick_command (join_sep " " [
				font._flag,
				antialias._flag,
				gravity._flag,
				foreground._flag,
				"-annotate", geometry._flag, "\"" ++ text.value ++ "\""]);

			_result = system command x; 
		}
	}

	Swirl_item = class
		Menuaction "_Swirl" "swirl around the centre" {
		action x = class 
			_result {
			_vislevel = 3;

			angle = angle_widget;
			command = magick_command ("-swirl " ++ print angle.value);

			_result = system command x; 
		}
	}
}

#separator

Filter_tilt_item = class
	Menupullright "Ti_lt Brightness" "tilt brightness" {
	Left_right_item = class
		Menuaction "_Left to Right" "linear left-right brighten" {
		action x = class
			_result {
			_vislevel = 3;

			tilt = Scale "Left-right tilt" (-1) 1 0;

			_result
					= map_unary tilt_lr x
			{
				tilt_lr image
					= image * scale
				{
					ramp = im_fgrey image.width image.height;
					scale = (ramp - 0.5) * tilt + 1;
				}
			}
		}
	}

	Top_bottom_item = class
		Menuaction "_Top to Bottom" "linear top-bottom brighten" {
		action x = class
			_result {
			_vislevel = 3;

			tilt = Scale "Top-bottom tilt" (-1) 1 0;

			_result
				= map_unary tilt_tb x
			{
				tilt_tb image 
					= image * scale
				{
					ramp = rot90 
						(im_fgrey image.height image.width);
					scale = (ramp - 0.5) * tilt + 1;
				}
			}
		}
	}

	sep1 = Menuseparator;

	Left_right_cos_item = class
		Menuaction "Cosine Left-_right" "cosine left-right brighten" {
		action x = class
			_result {
			_vislevel = 3;

			tilt = Scale "Left-right tilt" (-1) 1 0;
			shift = Scale "Shift by" (-1) 1 0;

			_result
					= map_unary tilt_lr x
			{
				tilt_lr image
					= image * scale
				{
					ramp = im_fgrey image.width image.height - 0.5 -
							shift.value;
					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
				}
			}
		}
	}

	Top_bottom_cos_item = class
		Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
		action x = class
			_result {
			_vislevel = 3;

			tilt = Scale "Top-bottom tilt" (-1) 1 0;
			shift = Scale "Shift by" (-1) 1 0;

			_result
				= map_unary tilt_tb x
			{
				tilt_tb image 
					= image * scale
				{
					ramp = rot90 (im_fgrey image.height image.width) - 0.5 -
							shift.value;
					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
				}
			}
		}
	}

	sep2 = Menuseparator;

	Circular_item = class
		Menuaction "_Circular" "circular brighten" {
		action x = class
			_result {
			_vislevel = 3;

			tilt = Scale "Tilt" (-1) 1 0;
			hshift = Scale "Horizontal shift by" (-1) 1 0;
			vshift = Scale "Vertical shift by" (-1) 1 0;

			_result
				= map_unary tilt_tb x
			{
				tilt_tb image 
					= image * scale
				{
					hramp = im_fgrey image.width image.height - 0.5 -
						hshift.value;
					vramp = rot90 (im_fgrey image.height image.width) - 0.5 -
						vshift.value;
					ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
				}
			}
		}
	}
}

Filter_blend_item = class
	Menupullright "_Blend" "blend objects together" {
	Scale_blend_item = class
		Menuaction "_Scale" "blend two objects together with a scale" {
		action a b = class
			_result {
			_vislevel = 3;

			p = Scale "Blend position" 0 1 0.5;

			_result
				= map_binary process a b
			{
				process im1 im2 = im1 * (1 - p.value) + im2 * p.value;
			}
		}
	}

	Image_blend_item = class
		Menuaction "_Image" "use an image to blend two objects" {
		action a b c = class
			_result {
			_vislevel = 3;

			i = Toggle "Invert mask" false;

			_result 
				= map_trinary process a b c
			{
				process a b c 
					= blend condition in1 in2, !i
					= blend (invert condition) in1 in2
				{
					compare a b
						// prefer image as the condition
						= false, 
							!has_image a && has_image b
						// prefer mono images as the condition
						= false, 
							has_bands a && has_bands b && 
							get_bands a > 1 && get_bands b == 1
						// prefer uchar as the condition
						= false,
							has_format a && has_format b && 
							get_format a > Image_format.UCHAR && 
								get_format b == Image_format.UCHAR
						= true;
					[condition, in1, in2] = sortc compare [a, b, c];
				}
			}
		}
	}

	Line_blend_item = class
		Menuaction "_Along Line" 
			"blend between image a and image b along a line" {
		action a b = class
			_result {
			_vislevel = 3;

			orientation = Option "Orientation" [
				"Left to Right",
				"Top to Bottom"
			] 0;
			blend_position = Scale "Blend position" 0 1 0.5;
			blend_width = Scale "Blend width" 0 1 0.05;

			_result
				= map_binary process a b
            {
                process a b 
					= blend (Image condition) b a
                {
					output_width = max_pair a.width b.width;
					output_height = max_pair a.height b.height;
					range
						= output_width, orientation == 0
						= output_height;
					blend_position' 
						= floor (range * blend_position.value);
					blend_width' 
						= 1, blend_width.value == 0
						= floor (range * blend_width.value);
                   	start = blend_position' - blend_width' / 2;

					background = (make_xy output_width output_height) >= 
						blend_position';
                   	ramp 
						= im_grey blend_width' output_height, orientation == 0
                        = rot90 (im_grey blend_width' output_width);
					condition 
						= insert_noexpand start 0 ramp background?0, 
							orientation == 0
						= insert_noexpand 0 start ramp background?1;
               	}
			}
		}
	} 

	Blend_alpha_item = class
		Menuaction "_Alpha" "blend images with optional alpha channels" {
		// usage: layerit foreground background
		// input images must be either 1 or 3 bands, optionally + 1 band 
		// which is used as the alpha channel
		// rich lott
	
		scale_mask im opacity
			= (unsigned char) (to_real opacity / 255 * im);
	
		// to mono
		intensity = colour_transform_to Image_type.B_W;
	
		// All the blend functions
		// I am grateful to this page
		// http://www.pegtop.net/delphi/blendmodes/
		// for most of the formulae.
	
		blend_normal mask opacity fg bg
			= blend (scale_mask mask opacity) fg bg;
	
		blend_iflighter mask opacity fg bg
			= blend (if fg' > bg' then mask' else 0) fg bg
		{ 
			fg' = intensity fg;
			bg' = intensity bg;
			mask' = scale_mask mask opacity ;
		}
	
		blend_ifdarker mask opacity fg bg
			= blend (if fg' < bg' then mask' else 0) fg bg
		{ 
			fg' = intensity fg ;
			bg' = intensity bg ;	
			mask' = scale_mask mask opacity ;
		}
	
		blend_multiply mask opacity fg bg
			= blend (scale_mask mask opacity) fg' bg
		{
			fg' = fg / 255 * bg;
		}
	
		blend_add mask opacity fg bg
			= blend mask fg' bg		
		{
			fg' = opacity / 255 * fg + bg;
		}
	
		blend_subtract mask opacity fg bg
			= blend mask fg' bg 
		{
			fg' = bg - opacity / 255 * fg;
		}
	
		blend_screen mask opacity fg bg
			= blend mask fg' bg 
		{
			fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255;
		}
	
		blend_burn mask opacity fg bg
			= blend mask fg'' bg
		{
			// fades to white which has no effect.
			fg' = (255 - opacity) + opacity * fg / 255;
			fg'' = 255 - 255 * (255 - bg) / fg';
		}
	
		blend_softlight mask opacity fg bg
			= blend mask' fg' bg
		{
			mask' = scale_mask mask opacity;
			fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255;
		}
	
		blend_hardlight mask opacity fg bg
			= blend mask' fg' bg		
		{
			mask' = scale_mask mask opacity;
			fg'
				= 2 / 255 * fg * bg, bg < 129
				= 255 - 2 * (255 - bg) * (255 - fg) / 255;
		}
	
		blend_lighten mask opacity fg bg
			= blend mask' fg' bg 		
		{
			mask' = scale_mask mask opacity;
			fg' = if bg < fg then fg else bg;
		}
	
		blend_darken mask opacity fg bg
			= blend mask' fg' bg		
		{
			mask' = scale_mask mask opacity;
			fg' = if bg > fg then fg else bg;
		}
	
		blend_dodge mask opacity fg bg
			= blend mask fg'' bg 
		{
			// one added to avoid divide by zero
			fg' = 1 + 255 - (opacity / 255 * fg); 
			fg'' = bg * 255 / fg';
		}
	
		blend_reflect mask opacity fg bg
			= blend mask' fg' bg 
		{
			mask' = scale_mask mask opacity;
			fg' = bg * bg / (255 - fg);
		}
	
		blend_freeze mask opacity fg bg
			= blend mask' fg' bg
		{
			mask' = scale_mask mask opacity;
			fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg);
		}
	
		blend_or mask opacity fg bg
			= bg | (unsigned char) fg'
		{
			mask' = scale_mask mask opacity;
			fg' = fg * mask' / 255;
		}
	
		blend_and mask opacity fg bg
			= bg & (unsigned char) fg'
		{
			mask' = scale_mask mask opacity;
			fg' = fg * mask' / 255;
		}
	
		// blend types
		NORMAL = 0;
		IFLIGHTER = 1;
		IFDARKER = 2;
		MULTIPLY = 3;
		ADD = 4;
		SUBTRACT = 5;
		SCREEN = 6;
		BURN = 7;
		DODGE = 8;
		HARDLIGHT = 9;
		SOFTLIGHT = 10;
		LIGHTEN = 11;
		DARKEN = 12;
		REFLECT = 13;
		FREEZE = 14;
		OR = 15;
		AND = 16;

		// names we show the user for blend types
		names = Enum [
			_ "Normal" => NORMAL,
			_ "If Lighter" => IFLIGHTER,
			_ "If Darker" => IFDARKER,
			_ "Multiply" => MULTIPLY,
			_ "Add" => ADD,
			_ "Subtract" => SUBTRACT,
			_ "Screen" => SCREEN,
			_ "Burn" => BURN,
			_ "Soft Light" => SOFTLIGHT,
			_ "Hard Light" => HARDLIGHT,
			_ "Lighten" => LIGHTEN,
			_ "Darken" => DARKEN,
			_ "Dodge" => DODGE,
			_ "Reflect" => REFLECT,
			_ "Freeze" => FREEZE,
			_ "Bitwise OR" => OR,
			_ "Bitwise AND" => AND
		];

		// functions we call for each blend type
		actions = Table [
			[NORMAL, blend_normal],
			[IFLIGHTER, blend_iflighter],
			[IFDARKER, blend_ifdarker],
			[MULTIPLY, blend_multiply],
			[ADD, blend_add],
			[SUBTRACT, blend_subtract],
			[SCREEN, blend_screen],
			[BURN, blend_burn],
			[SOFTLIGHT, blend_softlight],
			[HARDLIGHT, blend_hardlight],
			[LIGHTEN, blend_lighten],
			[DARKEN, blend_darken],
			[DODGE, blend_dodge],
			[REFLECT, blend_reflect],
			[FREEZE, blend_freeze],
			[OR, blend_or],
			[AND, blend_and]
		];
	
		// make sure im has an alpha channel (set opaque if it hasn't)
		put_alpha im
			= im, im.bands == 4 || im.bands == 2 
			= im ++ 255;
	
		// make sure im has no alpha channel 
		lose_alpha im
			= extract_bands 0 3 im, im.bands == 4 
			= im?0, im.bands == 2 
			= im;
	
		// does im have al alpha channel?
		has_alpha im = im.bands == 2 || im.bands == 4;
	
		// get the alpha (set opaque if no alpha)
		get_alpha img
			= img'?3, img.bands == 4 
			= img'?1
		{
			img' = put_alpha img;
		}
	
		// add an alpha ... cast the alpha image to match the main image
		append_alpha im alpha
			= im ++ clip2fmt im.format alpha;
	
		// makes fg the same size as bg, displaced with u, v pixel offset
		moveit fg bg u v
			= insert_noexpand u v fg bg'
		{
			bg' = image_new bg.width bg.height 
				fg.bands fg.format fg.coding fg.type 0 0 0;
		}
	
		action bg fg = class 
			_value {
			_vislevel = 3;
	
			method = Option_enum "Blend mode" names "Normal";
			opacity = Scale "Opacity" 0 255 255;
			hmove = Scale "Horizontal move by" (-bg.width) (bg.width) 0; 
			vmove = Scale "Vertical move by" (-bg.height) (bg.height) 0; 		
	
			_value
				= append_alpha blended merged_alpha, has_alpha bg
				= blended 
			{
				// displace and resize fg (need to displace alpha too)
				fg' = moveit (put_alpha fg) bg hmove vmove;
	
				// transform to sRGB
				fg'' = colour_transform_to Image_type.sRGB (lose_alpha fg');
				bg' = colour_transform_to Image_type.sRGB (lose_alpha bg);
	
				// alphas merged
				merged_alpha = get_alpha bg | get_alpha fg';
	
				// blend together
				blended = (actions.lookup 0 1 method.value_thing) 
					(get_alpha fg') opacity.value fg'' bg';
			}
		}
	}
}

Filter_overlay_header_item = class
	Menuaction "_Overlay" 
		"make a colour overlay of two monochrome images" {
	action  a b = class 
		_result {
		_vislevel = 3;

		colour = Option "Colour overlay as" [
			_ "Green over Red",
			_ "Blue over Red",
			_ "Red over Green",
			_ "Red over Blue",
			_ "Blue over Green",
			_ "Green over Blue"
		] 0;

		_result
			= map_binary overlay a b
		{
			overlay a b
				= image_set_type Image_type.sRGB 
					[(a' ++ b' ++ 0), 
						(a' ++ 0 ++ b'), 
						(b' ++ a' ++ 0), 
						(b' ++ 0 ++ a'), 
						(0 ++ a' ++ b'), 
						(0 ++ b' ++ a')]?colour
			{
				a' = colour_transform_to Image_type.B_W a;
				b' = colour_transform_to Image_type.B_W b;
			}
		}
	}
}

Filter_colourize_item = class 
	Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
	action a b = class
		_result {
		_vislevel = 3;

		tint = Scale "Tint" 0 1 0.6;

		_result
			= map_binary tintit a b
		{
			tintit a b
				= colour_transform_to (get_type colour) colourized'
			{
				// get the mono thing first
				[mono, colour] = 
					sortc (const (is_colour_type @ get_type)) [a, b];

				colour' = tint * colour_transform_to Image_type.LAB colour;
				mono' = colour_transform_to Image_type.B_W mono;
				colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2;
				colourized' = image_set_type Image_type.LAB colourized;
			}
		}
	}
}

Filter_browse_multiband_item = class 
	Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" {
	Bandwise_item = class
		Menuaction "B_andwise" "browse through the bands of a multiband image" {
		action image = class  
        	_result {
			_vislevel = 3;

        	band = Scale "Band" 0 (image.bands - 1) 0;
       		display = Option "Display as" [
			_ "Grey",
			_ "Green over Red",
			_ "Blue over Red",
			_ "Red over Green",
			_ "Red over Blue",
			_ "Blue over Green",
			_ "Green over Blue"
		] 0;

        	_result 
				= output
			{
				down = (int) band.value;
				up = down + 1;
				remainder = band.value - down;

				fade x a
					= Vector [0], x == 0
					= a * x;

				a = fade remainder image?up;
				b = fade (1 - remainder) image?down;

				output = [
					a + b, 
					a ++ b ++ 0, 
					a ++ 0 ++ b, 
					b ++ a ++ 0,
					b ++ 0 ++ a, 
					0 ++ a ++ b, 
					0 ++ b ++ a
				] ? display;
			}
		}
	}

	Bitwise_item = class
		Menuaction "Bi_twise" "browse through the bits of an image" {
		action x = class  
        	_result {
			_vislevel = 3;

        	bit 
				= Islider "Bit" 0 (nbits - 1) (nbits - 1)
			{
				nbits 
					= x.bits, is_Image x
					= 8;
				Islider c f t v = class 
					scope.Scale c f t ((int) v) {
					Scale = Islider;
				}
			}

        	_result 
				= map_unary process x
			{
				process im = (im & (0x1 << bit.value)) != 0;
			}
		}
	}
}

#separator

Filter_negative_item = class
	Menuaction "Photographic _Negative" "swap black and white" {
	action x 
		= map_unary invert x
	{
		invert in
			= clip2fmt in.format (colour_transform_to (get_type in) rgb')
		{
			rgb = colour_transform_to Image_type.sRGB in;
			rgb' = 255 - rgb;
		}
	}
}

Filter_solarize_item = class
	Menuaction "_Solarise" "invert colours above a threshold" {
	action x = class
		_result {
		_vislevel = 3;
		
		kink = Scale "Kink" 0 1 0.5;

		_result
			= map_unary process x
		{
			process image
				= hist_map tab'''' image
			{
				// max pixel value for this format
				mx = Image_format.maxval image.format;

				// make a LUT ... just 8 and 16 bit
				tab 
					= im_identity_ushort image.bands mx,
						image.format == 
							Image_format.USHORT
					= im_identity image.bands;
				tab' = Image tab;

				// make basic ^ shape
				tab'' 
					= tab' * (1 / kink), tab' < mx * kink
					= (mx - tab') / (1 - kink);
				tab''' = clip2fmt image.format tab'';

				// smooth a bit
				mask = matrix_blur (tab'''.width / 8);
				tab'''' = convsep mask tab''';
			}
		}
	}
}

Filter_diffuse_glow_item = class
	Menuaction "_Diffuse Glow" "add a halo to highlights" {
	action x = class
		_result {
		_vislevel = 3;
		
		r = Scale "Radius" 0 50 5;
		highlights = Scale "Highlights" 0 100 95;
		glow = Scale "Glow" 0 1 0.5;
		colour = Colour_new_item.Widget_colour_item.action;

		_result
			= map_unary process x
		{
			process image
				= image'
			{
				mono = (unsigned char) (colour_transform_to 
					Image_type.B_W image);
				thresh = hist_thresh (highlights.value / 100) mono;
				mask = mono > thresh;
				blur = convsep (matrix_gaussian_blur r.value) mask;
				colour' = colour_transform_to image.type colour;
				image' = image + colour' * glow * (blur / 255);
			}
		}
	}
}

Filter_drop_shadow_item = class
	Menuaction "Drop S_hadow" "add a drop shadow to an image" {
	action x = class
        _result {
        _vislevel = 3;

        sx = Scale "Horizontal shadow" (-50) 50 5;
        sy = Scale "Vertical shadow" (-50) 50 5;
        ss = Scale "Shadow softness" 0 20 5;
        bg_colour = Expression "Background colour" 255;
        sd_colour = Expression "Shadow colour" 128;
        alpha = Toggle "Shadow in alpha channel" false;
        transparent = Toggle "Zero pixels are transparent" false;

        _result
            = map_unary shadow x
        {
            shadow image
            	= Image final
            {
                blur_size = ss.value * 2 + 1;

                // matrix we blur with to soften shadows
                blur_matrix = matrix_gaussian_blur blur_size;
                matrix_size = blur_matrix.width;
                matrix_radius = (int) (matrix_size / 2) + 1;

                // position and size of shadow image in input cods
                // before and after fuzzing
                shadow_rect = Rect sx.value sy.value 
					image.width image.height;
                fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius;

                // size and pos of final image, in input cods
                final_rect = image.rect.union fuzzy_shadow_rect;

                // hard part of shadow in output cods
                shadow_rect' = Rect 
                    (shadow_rect.left - final_rect.left)
                    (shadow_rect.top - final_rect.top)
                    shadow_rect.width shadow_rect.height;

                // make the shadow mask ... true for parts which cast
                // a shadow
                mask 
                    = (foldr1 bitwise_and @ bandsplit) (image.value != 0), 
                		transparent
                    = image_new image.width image.height 1 Image_format.UCHAR
                        Image_coding.NOCODING Image_type.B_W 255 0 0;
                mask' = embed 0 shadow_rect'.left shadow_rect'.top
                        final_rect.width final_rect.height mask;
				mask'' = convsep blur_matrix mask';

                // use mask to fade between bg and shadow colour
                mk_background colour = image_new 
                	final_rect.width final_rect.height
                    image.bands image.format image.coding image.type
                    colour 0 0;

                bg_image = mk_background bg_colour.expr;
                shadow_image = mk_background sd_colour.expr;
                bg = blend mask'' shadow_image bg_image;

                // make a full size mask 
                fg_mask = embed 0
                    (image.rect.left - final_rect.left)
                    (image.rect.top - final_rect.top)
                    final_rect.width final_rect.height mask;

                // wrap up the input image ... put the shadow colour
                // around it, so if we are outputting a separate
                // alpha the shadow colour will be set correctly
                fg = insert (image.rect.left - final_rect.left)
                    (image.rect.top - final_rect.top)
                    image.value shadow_image;

                final
                    // make a separate alpha
                    = fg ++ mask'', alpha

                    // paste image over shadow
                    = if fg_mask then fg else bg;
            }
        }
    }
}

Filter_paint_text_item = class 
	Menuaction "_Paint Text" "paint text into an image" {
	action x 
		= paint_position, is_Group x
		= paint_area
	{
		paint_area = class
			_result {
			_check_args = [
				[x, "x", check_Image]
			];
			_vislevel = 3;
		
			text = String "Text to paint" "<i>Hello</i> world!";
			font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
			align = Option "Alignment" ["Left", "Centre", "Right"] 0;
			dpi = Expression "DPI" 300;
			colour = Expression "Text colour" 255;
			place = Region x (x.width / 4) (x.height / 4) 
				(x.width / 2) (x.height / 2);
	
			_result
				= insert_noexpand place.left place.top (blend txt' fg place) x
			{
				fg = image_new place.width place.height x.bands x.format 
					x.coding x.type colour.expr 0 0;
				txt = Image (im_text text.value font.value 
					place.width align.value (to_real dpi));
	            bg = im_black place.width place.height 1;
				txt' = insert_noexpand 0 0 txt bg;
			}
		}
	
		paint_position = class
			_result {
			_vislevel = 3;
	
			text = Pattern_images_item.Text_item.action;
			colour = Expression "Text colour" 255;
			position = Option "Position" [
				_ "North-west",
				_ "North",
				_ "North-east",
				_ "West",
				_ "Centre",
				_ "East",
				_ "South-west",
				_ "South",
				_ "South-east",
				_ "Specify in pixels"
			] 4;
			left = Expression "Pixels from left" 0;
			top = Expression "Pixels from top" 0;
	
			_result
				= map_unary paint x
			{
				paint image
					= insert_noexpand x' y' place' image
				{
					xr = image.width - text.width;
					yr = image.height - text.height;
					x 
						= left.expr, position == 9
						= [0, xr / 2, xr]?(position % 3);
					y
						= top.expr, position == 9
						= [0, yr / 2, yr]?(position / 3);
					x' = range 0 x (image.width - 1);
					y' = range 0 y (image.height - 1);
					w' = range 1 text.width (image.width - x');
					h' = range 1 text.height (image.height - y');
	
					place = extract_area x' y' w' h' image;
					text' = insert_noexpand 0 0 text (im_black w' h' 1);
					fg = image_new w' h' image.bands image.format 
						image.coding image.type colour.expr 0 0;
					place' = blend text' fg place;
				}
			}
		}
	}
}
