Image_new_item = class 
	Menupullright "_New" "make new things" {
	Image_black_item = class 
		Menuaction "_Image" "make a new image" {
		format_names = [
			"8-bit unsigned int - UCHAR", 		// 0
			"8-bit signed int - CHAR", 			// 1
			"16-bit unsigned int - USHORT", 	// 2
			"16-bit signed int - SHORT", 		// 3
			"32-bit unsigned int - UINT", 		// 4
			"32-bit signed int - INT", 			// 5
			"32-bit float - FLOAT", 			// 6
			"64-bit complex - COMPLEX", 		// 7
			"64-bit float - DOUBLE", 			// 8
			"128-bit complex - DPCOMPLEX" 		// 9
		];

		action = class 
			Image _result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			nheight = Expression "Image height (pixels)" 64;
			nbands = Expression "Image bands" 1;
			format_option = Option "Image format" format_names 0;
			type_option = Option_enum 
				Image_type.type_names "Image type" "B_W";
			pixel = Expression "Pixel value" 0;

			_result
				= image_new (to_real nwidth) (to_real nheight) (to_real nbands) 
					(to_real format_option) Image_coding.NOCODING 
					type_option.value_thing pixel.expr 0 0;
		}
	}

	Image_new_from_image_item = class
		Menuaction "_From Image" "make a new image based on image x" {
		action x = class
			Image _result {
				_vislevel = 3;

				pixel = Expression "Pixel value" 0;

				_result
					= image_new x.width x.height x.bands
						x.format x.coding x.type pixel.expr x.xoffset x.yoffset;
		}
	} 

	Image_region_item = class 
		Menupullright "_Region on Image" "make a new region on an image" {
		Region_item = class 
			Menuaction "_Region" "make a region on an image" {
			action image = scope.Region_relative image 0.25 0.25 0.5 0.5;
		}
	
		Mark_item = class 
			Menuaction "_Point" "make a point on an image" {
			action image = scope.Mark_relative image 0.5 0.5;
		}
	
		Arrow_item = class 
			Menuaction "_Arrow" "make an arrow on an image" {
			action image = scope.Arrow_relative image 0.25 0.25 0.5 0.5;
		}
	
		HGuide_item = class 
			Menuaction "_Horizontal Guide" 
				"make a horizontal guide on an image" {
			action image = scope.HGuide image 0.5;
		}
	
		VGuide_item = class 
			Menuaction "_Vertical Guide" "make a vertical guide on an image" {
			action image = scope.VGuide image 0.5;
		}

    	sep1 = Menuseparator;

		Move_item = class
			Menuaction "From Region" 
				"new region on image using existing region as a guide" {
			action a b 
				= map_binary process a b
			{
				process a b 
					= x.Region target x.left x.top x.width x.height,
							is_Region x
					= x.Arrow target x.left x.top x.width x.height,
							is_Arrow x
					= error "bad arguments to region-from-region"
				{
					// prefer image then region
					compare a b
						= false, 
							!is_Image a && is_Image b
						= false, 
							is_Region a && !is_Region b
						= true;

					[target, x] = sortc compare [a, b];
				}
			}
		}
	}
}

Image_convert_to_image_item = class 
	Menuaction "Con_vert to Image" "convert anything to an image" {
	action x = to_image x;
}

Image_number_format_item = class
	Menupullright "_Format" "convert numeric format" {

	U8_item = class
		Menuaction "_8 bit unsigned" "convert to unsigned 8 bit [0, 255]" {
		action x = map_unary cast_unsigned_char x;
	}

	U16_item = class
		Menuaction "1_6 bit unsigned" 
			"convert to unsigned 16 bit [0, 65535]" {
		action x = map_unary cast_unsigned_short x;
	}

	U32_item = class
		Menuaction "_32 bit unsigned" 
			"convert to unsigned 32 bit [0, 4294967295]" {
		action x = map_unary cast_unsigned_int x;
	}

    sep1 = Menuseparator;

	S8_item = class
		Menuaction "8 _bit signed" "convert to signed 8 bit [-128, 127]" {
		action x = map_unary cast_signed_char x;
	}

	S16_item = class
		Menuaction "16 b_it signed" 
			"convert to signed 16 bit [-32768, 32767]" {
		action x = map_unary cast_signed_short x;
	}

	S32_item = class
		Menuaction "32 bi_t signed" 
			"convert to signed 32 bit [-2147483648, 2147483647]" {
		action x = map_unary cast_signed_int x;
	}

    sep2 = Menuseparator;

	Float_item = class
		Menuaction "_Single precision float" 
			"convert to IEEE 32 bit float" {
		action x = map_unary cast_float x;
	}

	Double_item = class
		Menuaction "_Double precision float" 
			"convert to IEEE 64 bit float" {
		action x = map_unary cast_double x;
	}

    sep3 = Menuseparator;

	Scmplxitem = class
		Menuaction "Single _precision complex" 
			"convert to 2 x IEEE 32 bit float" {
		action x = map_unary cast_complex x;
	}

	Dcmplx_item = class
		Menuaction "Double p_recision complex" 
			"convert to 2 x IEEE 64 bit float" {
		action x = map_unary cast_double_complex x;
	}
}

Image_header_item = class 
	Menupullright "_Header" "do stuff to the image header" {

	Image_get_item = class
		Menupullright "_Get" "get header fields" {

		// the header fields we can get
		fields = class {
			type = 0;
			width = 1;
			height = 2;
			format = 3;
			bands = 4;
			xres = 5;
			yres = 6;
			xoffset = 7;
			yoffset = 8;
			coding = 9;

			field_names = Enum [
				$width => width,
				$height => height,
				$bands => bands,
				$format => format,
				$type => type,
				$xres => xres,
				$yres => yres,
				$xoffset => xoffset,
				$yoffset => yoffset,
				$coding => coding
			];

			field_option name = Option_enum field_names (_ "Field") name;

			field_funcs = Table [
				[type, get_type],
				[width, get_width],
				[height, get_height],
				[format, get_format],
				[bands, get_bands],
				[xres, get_xres],
				[yres, get_yres],
				[xoffset, get_xoffset],
				[yoffset, get_yoffset],
				[coding, get_coding]
			];
		}

		get_field field_name x = class
			_result {
			_vislevel = 3;

			field = fields.field_option field_name;

			_result 
				= map_unary (Real @ 
					fields.field_funcs.lookup 0 1 field.value_thing) x;
		}

		Width_item = class 
			Menuaction "_Width" "get width" {
			action x = get_field "width" x;
		}

		Height_item = class 
			Menuaction "_Height" "get height" {
			action x = get_field "height" x;
		}

		Bands_item = class 
			Menuaction "_Bands" "get bands" {
			action x = get_field "bands" x;
		}

		Format_item = class 
			Menuaction "_Format" "get format" {
			action x = get_field "format" x;
		}

		Type_item = class 
			Menuaction "_Type" "get type" {
			action x = get_field "type" x;
		}

		Xres_item = class 
			Menuaction "_Xres" "get X resolution" {
			action x = get_field "xres" x;
		}

		Yres_item = class 
			Menuaction "_Yres" "get Y resolution" {
			action x = get_field "yres" x;
		}

		Xoffset_item = class 
			Menuaction "X_offset" "get X offset" {
			action x = get_field "xoffset" x;
		}

		Yoffset_item = class 
			Menuaction "Yo_ffset" "get Y offset" {
			action x = get_field "yoffset" x;
		}

		Coding_item = class 
			Menuaction "_Coding" "get coding" {
			action x = get_field "coding" x;
		}
	}

    sep1 = Menuseparator;

	Image_set_meta_item = class
		Menuaction "_Set" "set image metadata" {
		action x = class
			_result {
			_vislevel = 3;

			fname = String "Field" "field-name";
			val = Expression "Value" 42;

			_result
				= map_unary process x
			{
				process image
					= set_header fname.value val.expr image;
			}
		}
	}

	Image_edit_header_item = class
		Menuaction "_Edit" "change advisory header fields of image" {
		type_names = Image_type.type_names;
		all_names = sort (map (extract 0) type_names.value);

		get_prop has get def x
			= get x, has x
			= def;

		action x = class
			_result {
			_vislevel = 3;

			nxres = Expression "Xres" (get_prop has_xres get_xres 1 x);
			nyres = Expression "Yres" (get_prop has_yres get_yres 1 x);
			nxoff = Expression "Xoffset" (get_prop has_xoffset get_xoffset 0 x);
			nyoff = Expression "Yoffset" (get_prop has_yoffset get_yoffset 0 x);

			type_option 
				= Option_enum Image_type.type_names "Image type"
					(Image_type.type_names.get_name type)
			{
				type 
					= x.type, is_Image x
					= Image_type.MULTIBAND;
			}

			_result
				= map_unary process x
			{
				process image
					= Image (im_copy_set image.value type_option.value_thing
						(to_real nxres) (to_real nyres) 
						(to_real nxoff) (to_real nyoff));
			}
		}
	}
}

#separator

Image_levels_item = class 
	Menupullright "_Levels" "change image levels" {
	Scale_item = class
		Menuaction "_Scale to 0 - 255" "linear transform to fit 0 - 255 range" {
		action x = map_unary scale x;
	}

	Linear_item = class
		Menuaction "_Linear" "linear transform of image levels" {
		action x = class
			_result {
			_vislevel = 3;

			scale = Scale "Scale" 0.001 3 1;
			offset = Scale "Offset" (-128) 128 0;

			_result
				= map_unary adj x
			{
				adj x
					// only force back to input type if this is a thing
					// with a type ... so we work for Colour / Matrix etc.
					= clip2fmt x.format x', has_member "format" x
					= x'
				{
					x' = x * scale + offset;
				}
			}
		}
	}

	Gamma_item = class
		Menuaction "_Power" "power transform of image levels (gamma)" {
		action x = class
			_result {
			_vislevel = 3;

			gamma = Scale "Gamma" 0.001 4 1;
			image_maximum_hint = "You may need to change image_maximum if " ++
				"this is not an 8 bit image";
			im_mx 
				= Expression "Image maximum" mx
			{
				mx 
						= Image_format.maxval x.format, has_format x
						= 255;
			}

			_result
				= map_unary gam x
			{
				gam x
					= clip2fmt (get_format x) x', has_format x
					= x'
				{
					x' = (im_mx.expr / im_mx.expr ** gamma) * x ** gamma;
				}
			}
		}
	}

	Tone_item = class
		Menuaction "_Tone Curve" "adjust tone curve" {
		action x = class
			_result {
			_vislevel = 3;

			b = Scale "Black point"  0 100 0;
			w = Scale "White point"  0 100 100;

			sp = Scale "Shadow point" 0.1 0.3 0.2;
			mp = Scale "Mid-tone point" 0.4 0.6 0.5;
			hp = Scale "Highlight point" 0.7 0.9 0.8;

			sa = Scale "Shadow adjust" (-15) 15 0;
			ma = Scale "Mid-tone adjust" (-30) 30 0;
			ha = Scale "Highlight adjust" (-15) 15 0;

			curve = tone_build x.format b w sp mp hp sa ma ha;

			_result = map_unary (hist_map curve) x;
		}
	}
}

Image_transform_item = class 
	Menupullright "_Transform" "transform images" {
	Rotate_item = class 
		Menupullright "Ro_tate" "rotate image" {
		Fixed_item = class
			Menupullright "_Fixed" "clockwise rotation by fixed angles" {
	        rotate_widget default x = class
				_result {
				_vislevel = 3;
	
				angle = Option "Rotate by" [
					"Don't rotate", 
					"90 degrees clockwise",
					"180 degrees",
					"90 degrees anticlockwise"
				] default;
	
				_result 
					= map_unary process x
				{
					process in = [
						in, 
						rot90 in,
						rot180 in,
						rot270 in
					] ? angle;
				}
			}
	
			Rot90_item = class
				Menuaction "_90 Degrees" "clockwise rotation by 90 degrees" {
				action x = rotate_widget 1 x;
			}
	
			Rot180_item = class
				Menuaction "_180 Degrees" "clockwise rotation by 180 degrees" {
				action x = rotate_widget 2 x;
			}
	
			Rot270_item = class
				Menuaction "_270 Degrees" "clockwise rotation by 270 degrees" {
				action x = rotate_widget 3 x;
			}
		}

		Free_item = class
			Menuaction "_Free" "clockwise rotation by any angle" {
			action x = class
				_result {
				_vislevel = 3;
	
				angle = Scale "Angle" (-180) 180 0;
	
				_result
					= map_unary process x
				{
					process image 
						= rotate angle image;
				}
			}
		}
	
		Straighten_item = class
			Menuaction "_Straighten" 
				("smallest rotation that makes an arrow either horizontal " ++
				"or vertical") {
			action x 
				= map_unary straighten x
			{
				straighten arrow
					= rotate angle'' arrow.image
				{
					x = arrow.width;
					y = arrow.height;
	
					angle = im (polar (x, y));
	
					angle'
						= angle - 360, angle > 315
						= angle - 180, angle > 135
						= angle;
	
					angle''
						= -angle', angle' >= (-45) && angle' < 45
						= 90 - angle';
				}
			}
		}
	}

	Flip_item = class 
		Menupullright "_Flip" "mirror left/right or up/down" {
		Left_right_item = class
			Menuaction "_Left Right" "mirror object left/right" {
			action x = map_unary fliplr x;
		}
	
		Top_bottom_item = class
			Menuaction "_Top Bottom" "mirror object top/bottom" {
			action x = map_unary fliptb x;
		}
	}

	Resize_item = class 
		Menupullright "_Resize" "change image size" {
		_interp = Option_enum Interpolate.names "Interpolation" "Bilinear";

		Scale_item = class
			Menuaction "_Scale" "scale image size by a factor" {
			action x = class
				_result {
				_vislevel = 3;

				xfactor = Expression "Horizontal scale factor" 1;
				yfactor = Expression "Vertical scale factor" 1;
				interp = _interp;

				_result 
					= map_unary process x
				{
					process image 
						= resize xfactor yfactor interp.value_thing image;
				}
			}
		}

		Size_item = class
			Menuaction "_Size To" "resize to a fixed size" {
			action x = class 
				_result {
				_vislevel = 3;

				which = Option "Resize axis" [
					"Shortest",
					"Longest",
					"Horizontal",
					"Vertical"
				] 0;
				size = Expression "Resize to (pixels)" 128;
				interp = _interp;

				_result
					= map_unary process x
				{
					process image
						= resize fac fac interp.value_thing image
					{
						xfac = to_real size / image.width;
						yfac = to_real size / image.height;
						max_factor = max_pair xfac yfac;
						min_factor = min_pair xfac yfac;
						fac = [max_factor, min_factor, xfac, yfac]?which;
					}
				}
			}
		}

		Resize_canvas_item = class
			Menuaction "_Canvas" "change size of surrounding image" {
			action x = class
				_result {
				_vislevel = 3;
	
				// try to guess a sensible size for the new image 
				_guess_size
					= x.rect, is_Image x
					= Rect 0 0 100 100;
	
				nwidth = Expression "New width (pixels)" _guess_size.width;
				nheight = Expression "New height (pixels)" _guess_size.height;
				bgcolour = Expression "Background colour" 0;
	
				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 process x
				{
					process image 
						= insert_noexpand xp yp image background
					{
						width = image.width;
						height = image.height;
						coding = image.coding;
						bands 
							= 3, coding == Image_coding.LABPACK
							= image.bands;
						format 
							= Image_format.FLOAT, coding == Image_coding.LABPACK
							= image.format;
						type = image.type;
	
						// placement vectors ... left, centre, right
						xposv = [0, to_real nwidth / 2 - width / 2, 
							to_real nwidth - width];
						yposv = [0, to_real nheight / 2 - height / 2, 
							to_real nheight - height];
						xp 
							= left, position == 9
							= xposv?((int) (position % 3));
						yp 
							= top, position == 9
							= yposv?((int) (position / 3));
	
						background = image_new nwidth nheight
							bands format coding type bgcolour.expr 0 0;
					}
				}
			}
		}
	}

	Image_perspective_item = Perspective_item;

	Image_rubber_item = class 
		Menupullright "Ru_bber Sheet" 
			"automatically warp images to superposition" {
		rubber_interp = Option "Interpolation"
			(map (extract 0) Interpolate.names.value) Interpolate.BILINEAR;
		rubber_order = Option "Order" ["0", "1", "2", "3"] 1;
		rubber_wrap = Toggle "Wrap image edges" false;

		// a transform ... a matrix, plus the size of the image the
		// matrix was made for
		Transform matrix image_width image_height = class 
			matrix {
			// scale a transform ... if it worked for a m by n image, make
			// it work for a (m * xfac) by (y * yfac) image
			rescale xfac yfac 
				= Transform (Matrix (map2 (map2 multiply) matrix.value facs))
					(image_width * xfac) (image_height * yfac)
			{
				facs = [
					[xfac, yfac],
					[1, 1],
					[1, 1],
					[1 / xfac, 1 / yfac],
					[1 / xfac, 1 / yfac],
					[1 / xfac, 1 / yfac]
				];
			}
		}

		// yuk!!!! fix is_instanceof to not need absolute names
		is_Transform = is_instanceof 
			"Image_transform_item.Image_rubber_item.Transform";

		Find_item = class
			Menuaction "_Find" 
				("find a transform which will map sample image onto " ++
				"reference") {
			action reference sample = class
				_trn {
				_vislevel = 3;
	
				// controls
				order = rubber_order;
				interp = rubber_interp;
				wrap = rubber_wrap;
				max_err = Expression "Maximum error" 0.3;
				max_iter = Expression "Maximum iterations" 10;
	
				// transform
				[sample', trn, err] = transform_search 
					max_err max_iter order interp wrap
					sample reference;
				transformed_image = Image sample';
				_trn = Transform trn reference.width reference.height;
				final_error = err;
			}
		}

		Apply_item = class
			Menuaction "_Apply" "apply a transform to an image" {
			action a b = class
				_result {
				_vislevel = 3;
	
				// controls
				interp = rubber_interp;
				wrap = rubber_wrap;
	
				_result
					= map_binary trans a b
				{
					trans a b
						= transform interp wrap t' i
					{
						// get the transform arg first
						[i, t] = sortc (const is_Transform) [a, b];
						t' = t.rescale (i.width / t.image_width) 
							(i.height / t.image_height);
					}
				}
			}
		}
	}

    sep1 = Menuseparator;

	Match_item = class
		Menuaction "_Linear Match" 
			"rotate and scale one image to match another" {
		action x y = class 
			_result {
			_vislevel = 3;

			// try to find an image ... for a group, get the first item
			find_image x
				= x, is_Image x
				= find_image x?0, is_list x
				= find_image x.value, is_class x && has_value x
				= error "unable to find image";

			_a = find_image x;
			_b = find_image y;

			ap1 = Mark_relative _a 0.5 0.25;
			bp1 = Mark_relative _b 0.5 0.25;
			ap2 = Mark_relative _a 0.5 0.75;
			bp2 = Mark_relative _b 0.5 0.75;
		
			refine = Toggle "Refine selected tie-points" false;
			lock = Toggle "No resize" false;
		
			_result
				= map_binary process x y
			{
				process a b
					= Image b'''
				{
					_prefs = Workspaces.Preferences;
					window = _prefs.MOSAIC_WINDOW_SIZE;
					object = _prefs.MOSAIC_OBJECT_SIZE;
					
					a' = a.value;
					b' = b.value;
			
					b'' = clip2fmt a.format b';
			
					// return p2 ... if lock is set, return a p2 a standard
					// distance along the vector joining p1 and p2
					norm p1 p2
						= Rect left' top' 0 0, lock
						= p2
					{
						v = (p2.left - p1.left, p2.top - p1.top);
						// 100000 to give precision since we pass points as
						// ints to match
						n = 100000 * sign v;
						left' = p1.left + re n;
						top' = p1.top + im n;
					}
			
					ap2'' = norm ap1 ap2;
					bp2'' = norm bp1 bp2;
			
					b''' 
						= im_match_linear_search a' b''
							ap1.left ap1.top bp1.left bp1.top
							ap2''.left ap2''.top bp2''.left bp2''.top
							object window,
								// we can't search if lock is on
								refine && !lock
						= im_match_linear a' b''
							ap1.left ap1.top bp1.left bp1.top
							ap2''.left ap2''.top bp2''.left bp2''.top;
				}
			}
		}
	}

	Image_perspective_match_item = Perspective_match_item;
}

Image_band_item = class 
	Menupullright "_Band" "manipulate image bands" {
	// like extract_bands, but return [] for zero band image
	// makes compose a bit simpler
	exb b n x
		= [], to_real n == 0
		= extract_bands b n x;

	Extract_item = class Menuaction "_Extract" "extract bands from image" {
		action x = class
			_result {
			_vislevel = 3;

			first = Expression "Extract from band" 0;
			number = Expression "Extract this many bands" 1;

			_result = map_unary (exb first number) x;
		}
	}

	Insert_item = class Menuaction "_Insert" "insert bands into image" {
		action x y = class
			_result {
			_vislevel = 3;

			first = Expression "Insert at position" 0;

			_result 
				= map_binary process x y
			{
				process im1 im2
					= exb 0 f im1 ++ im2 ++ exb f (b - f) im1
				{
					f = to_real first;
					b = im1.bands;
				}
			}
		}
	}

	Delete_item = class Menuaction "_Delete" "delete bands from image" {
		action x = class
			_result {
			_vislevel = 3;

			first = Expression "Delete from band" 0;
			number = Expression "Delete this many bands" 1;

			_result 
				= map_unary process x
			{
				process im
					= exb 0 f im ++ exb (f + n) (b - (f + n)) im
				{
					f = to_real first;
					n = to_real number;
					b = im.bands;
				}
			}
		}
	}

	Bandwise_item = Image_join_item.Bandwise_item;

    sep1 = Menuseparator;

	To_dimension_item = class 
		Menuaction "To D_imension" "convert bands to width or height" {
		action x = class
			_result {
			_vislevel = 3;

			orientation = Option "Orientation" [
				"Horizontal", 
				"Vertical"
			] 0;

			_result 
				= map_unary process x
			{
				process im
					= foldr1 [join_lr, join_tb]?orientation (bandsplit im);
			}
		}
	}

	To_bands_item = class 
		Menuaction "To B_ands" "turn width or height to bands" {
		action x = class
			_result {
			_vislevel = 3;

			orientation = Option "Orientation" [
				"Horizontal", 
				"Vertical"
			] 0;

			_result 
				= map_unary process x
			{
				process im
					= bandjoin (map extract_column [0 .. im.width - 1]),
						orientation == 0
					= bandjoin (map extract_row [0 .. im.height - 1])
				{
					extract_column n
						= extract_area n 0 1 im.height im;
					extract_row n
						= extract_area 0 n im.width 1 im;
				}
			}
		}
	}
}

Image_crop_item = class 
	Menuaction "_Crop" "extract a rectangular area from an image" {
	action x = class
		_result {
		_vislevel = 3;

		// try to find the image geometry ... don't bother trying to look
		// inside groups though
		_geo
			= x.rect, is_Image x
			= Rect 0 0 100 100;

		l = Expression "Crop left" ((int) (_geo.left + _geo.width / 4));
		t = Expression "Crop top" ((int) (_geo.top + _geo.height / 4));
		w = Expression "Crop width" (max_pair 1 ((int) (_geo.width / 2)));
		h = Expression "Crop height" (max_pair 1 ((int) (_geo.height / 2)));

		_result 
			= map_nary (list_5ary extract) [x, l.expr, t.expr, w.expr, h.expr]
		{
			extract im l t w h
				= extract_area left' top' width' height' im
			{
				width' = min_pair (to_real w) im.width;
				height' = min_pair (to_real h) im.height;
				left' = range 0 (to_real l) (im.width - width');
				top' = range 0 (to_real t) (im.height - height');
			}
		}
	}
}

Image_insert_item = class
	Menuaction "_Insert" "insert a small image into a large image" {
	action a b 
		= insert_position, is_Group a || is_Group b
		= insert_area
	{
		insert_area = class
			_result {
			_check_args = [
				[a, "a", check_Image],
				[b, "b", check_Image]
			];
			_check_all = [
				[a.coding == b.coding && a.bands == b.bands, 
					"a.coding == b.coding && a.bands == b.bands"]
			];
			_vislevel = 3;

			// sort to get smallest first
			_pred x y = x.width * x.height < y.width * y.height;
			[_a', _b'] = sortc _pred [a, b];

			place 
				= Area _b' left top width height
			{
				// be careful in case b is smaller than a 
				left = max_pair 0 ((_b'.width - _a'.width) / 2);
				top = max_pair 0 ((_b'.height - _a'.height) / 2);
				width = min_pair _a'.width _b'.width;
				height = min_pair _a'.height _b'.height;
			}

			_result
				= insert_noexpand place.left place.top 
					(clip2fmt _b'.format a'') _b'
			{
				a'' = extract_area 0 0 place.width place.height _a';
			}
		}

		insert_position = class
			_result {
			_vislevel = 3;

			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_binary insert a b
			{
				insert a b 
					= insert_noexpand left top (clip2fmt b.format a) b, 
						position == 9
					= insert_noexpand xp yp (clip2fmt b.format a) b
				{
					xr = b.width - a.width;
					yr = b.height - a.height;
					xp = [0, xr / 2, xr]?((int) (position % 3));
					yp = [0, yr / 2, yr]?((int) (position / 3));
				}
			}
		}
	}
}

Image_select_item = Select_item;

Image_join_item = class 
	Menupullright "_Join" "join two or more images together" {
	Bandwise_item = class
		Menuaction "_Bandwise" "join two images bandwise" {
		action a b = join a b;
	}

    sep1 = Menuseparator;

	join_lr shim bg align a b
		= im2
	{
		w = a.width + b.width + shim;
		h = max_pair a.height b.height;
	
		back = image_new w h a.bands a.format a.coding a.type bg 0 0;
	
		ya = [0, max_pair 0 ((b.height - a.height)/2), 
			max_pair 0 (b.height - a.height)]; 
		yb = [0, max_pair 0 ((a.height - b.height)/2), 
			max_pair 0 (a.height - b.height)]; 
	
		im1 = insert_noexpand 0 ya?align a back;
		im2 = insert_noexpand (a.width + shim) yb?align b im1;
	}
	
	join_tb shim bg align a b
		= im2
	{
		w = max_pair a.width b.width;
		h = a.height + b.height + shim;
	
		back = image_new w h a.bands a.format a.coding a.type bg 0 0;
	
		xa = [0, max_pair 0 ((b.width - a.width)/2), 
			max_pair 0 (b.width - a.width)]; 
		xb = [0, max_pair 0 ((a.width - b.width)/2), 
			max_pair 0 (a.width - b.width)]; 
	
		im1 = insert_noexpand xa?align 0 a back;
		im2 = insert_noexpand xb?align (a.height + shim) b im1;
	}

	halign_names = ["Top", "Centre", "Bottom"];
	valign_names = ["Left", "Centre", "Right"];

	Left_right_item = class
		Menuaction "_Left to Right" "join two images left-right" {
		action a b = class 
			_result {
			_vislevel = 3;
	
			shim = Scale "Spacing" 0 100 0;
			bg_colour = Expression "Background colour" 0;
			align = Option "Alignment" halign_names 1;
	
			_result = map_binary 
				(join_lr shim.value bg_colour.expr align.value) a b;
		}
	}

	Top_bottom_item = class
		Menuaction "_Top to Bottom" "join two images top-bottom" {
		action a b = class 
			_result {
			_vislevel = 3;
	
			shim = Scale "Spacing" 0 100 0;
			bg_colour = Expression "Background colour" 0;
			align = Option "Alignment" valign_names 1;
	
			_result = map_binary 
				(join_tb shim.value bg_colour.expr align.value) a b;
		}
	}

    sep2 = Menuseparator;

	Array_item = class
		Menuaction "_Array" 
			"join a list of lists of images into a single image" {
		action x = class 
			_result {
			_vislevel = 3;

			hshim = Scale "Horizontal spacing" (-100) (100) 0;
			vshim = Scale "Vertical spacing" (-100) (100) 0;
			bg_colour = Expression "Background colour" 0;
			halign = Option "Horizontal alignment" valign_names 1;
			valign = Option "Vertical alignment" halign_names 1;

			_result 
				= (image_set_origin 0 0 @ 
					foldl1 (join_tb vshim.value bg_colour.expr halign.value) @
					map (foldl1 (join_lr hshim.value 
						bg_colour.expr valign.value))) (to_list (to_list x));
		}
	}
}

Image_tile_item = class 
	Menupullright "Til_e" "tile an image across and down" {
	tile_widget default_type x = class
		_result {
		_vislevel = 3;
		
		across = Expression "Tiles across" 2;
		down = Expression "Tiles down" 2;
		repeat = Option "Tile type" 
			["Replicate", "Four-way mirror"] default_type;

		_result
			= map_unary process x
		{
			process image
				= tile across down image, repeat == 0
				= tile across down image''
			{
				image' = insert image.width 0 (fliplr image) image;
				image'' = insert 0 image.height (fliptb image') image';
			}
		}
	}

	Replicate_item = class
		Menuaction "_Replicate" "replicate image across and down" {
		action x = tile_widget 0 x;
	}

	Fourway_item = class
		Menuaction "_Four-way Mirror" "four-way mirror across and down" {
		action x = tile_widget 1 x;
	}

	Chop_item = class
		Menuaction "_Chop Into Tiles" "slice an image into tiles" {
		action x = class 
			_result {
			_vislevel = 3;

			tile_width = Expression "Tile width" 100;
			tile_height = Expression "Tile height" 100;
			hoverlap = Expression "Horizontal overlap" 0;
			voverlap = Expression "Vertical overlap" 0;

			_result
				= map_unary (Group @ map Group @ process) x
			{
				process x
					= imagearray_chop tile_width tile_height
						hoverlap voverlap x;
			}
		}
	}
}

#separator

Pattern_images_item = class 
	Menupullright "_Patterns" "make a variety of useful patterns" {
	Grey_item = class 
		Menuaction "Grey _Ramp" "make a smooth grey ramp" {
		action = class
			_result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			nheight = Expression "Image height (pixels)" 64;
			orientation = Option "Orientation" [
				"Horizontal", 
				"Vertical"
			] 0;
			foption = Option "Format" ["8 bit", "float"] 0;

			_result 
				= Image im
			{
				gen 
					= im_grey, foption == 0
					= im_fgrey;
				w = to_real nwidth;
				h = to_real nheight;
				im 
					= gen w h, orientation == 0
					= rot90 (gen h w);
			}
		}
	}

	Xy_item = class 
		Menuaction "_XY Image" 
			"make a two band image whose pixel values are their coordinates" {
		action = class 
			_result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			nheight = Expression "Image height (pixels)" 64;

			_result = Image (make_xy nwidth nheight); 
		}
	}

	Gaussian_item = class 
		Menuaction "Gaussian _Noise" "make an image of gaussian noise" {
		action = class
			_result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			nheight = Expression "Image height (pixels)" 64;
			mean = Scale "Mean" 0 255 128;
			deviation = Scale "Deviation" 0 128 50;

			_result = Image (im_gaussnoise (to_real nwidth) (to_real nheight) 
				mean.value deviation.value);
		}
	}

	Fractal_item = class 
		Menuaction "_Fractal" "make a fractal image" {
		action = class
			_result {
			_vislevel = 3;

			nsize = Expression "Image size (pixels)" 64;
			dimension = Scale "Dimension" 2.001 2.999 2.001;

			_result = Image (im_fractsurf (to_real nsize) dimension.value); 
		}
	}

	Checkerboard_item = class 
		Menuaction "_Checkerboard" "make a checkerboard image" {
		action = class 
			_result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			nheight = Expression "Image height (pixels)" 64;
			hpsize = Expression "Horizontal patch size" 8;
			vpsize = Expression "Vertical patch size" 8;
			hpoffset = Expression "Horizontal patch offset" 0;
			vpoffset = Expression "Vertical patch offset" 0;

			_result
				= Image (xstripes ^ ystripes)
			{
				pixels = make_xy nwidth nheight;
				xpixels = pixels?0 + to_real hpoffset;
				ypixels = pixels?1 + to_real vpoffset;

				make_stripe pix swidth = pix % (swidth * 2) >= swidth;

				xstripes = make_stripe xpixels (to_real hpsize);
				ystripes = make_stripe ypixels (to_real vpsize);
			}
		}
	}

	Grid_item = class 
		Menuaction "Gri_d" "make a grid" {
		action = class
			_result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			nheight = Expression "Image height (pixels)" 64;
			hspace = Expression "Horizontal line spacing" 8;
			vspace = Expression "Vertical line spacing" 8;
			thick = Expression "Line thickness" 1;
			hoff = Expression "Horizontal grid offset" 4;
			voff = Expression "Vertical grid offset" 4;

			_result
				= Image (xstripes | ystripes)
			{
				pixels = make_xy nwidth nheight;
				xpixels = pixels?0 + to_real hoff;
				ypixels = pixels?1 + to_real voff;

				make_stripe pix swidth = pix % swidth < to_real thick;

				xstripes = make_stripe xpixels (to_real hspace);
				ystripes = make_stripe ypixels (to_real vspace);
			}
		}
	}

	Text_item = class 
		Menuaction "_Text" "make a bitmap of some text" {
		action = class 
			_result {
			_vislevel = 3;

			text = String "Text to paint" "<i>Hello</i> world!";
			font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
			wrap = Expression "Wrap text at" 500;
			align = Option "Alignment" [
				"Left",
				"Centre", 
				"Right" 
			] 0;
			dpi = Expression "DPI" 300;

			_result = Image (im_text text.value font.value 
				(to_real wrap) align.value (to_real dpi));
		}
	}

	New_CIELAB_slice_item = class
		Menuaction "CIELAB _Slice" "make a slice through CIELAB space" {
		action = class
			_result {
			_vislevel = 3;

			nsize = Expression "Image size (pixels)" 64;
			L = Scale "L value" 0 100 50;

			_result = Image (lab_slice (to_real nsize) L.value);
		}
	}

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

	build fn size
		= (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
			(im_create_fmask size size);

	New_ideal_item = class 
		Menupullright "_Ideal Fourier Mask" 
			"make various ideal Fourier filter masks" {
		High_low_item = class 
			Menuaction "_High or Low Pass" 
				("make a mask image for a highpass/lowpass " ++
					"ideal Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;

				_result 
					= build param (to_real nsize)
				{
						param f = f sense.value fc.value 0 0 0 0;
				}
			}
		}

		Ring_item = class 
			Menuaction "_Ring Pass or Ring Reject"
				("make a mask image for an ring pass/reject " ++
				"ideal Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				rw = Scale "Ring width" 0.01 0.99 0.5;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 6) fc.value rw.value 0 0 0;
				}
			}
		}

		Band_item = class 
			Menuaction "_Band Pass or Band Reject"
				("make a mask image for a band pass/reject " ++
				"ideal Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				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;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 12) fcx.value fcy.value
							r.value 0 0;
				}
			}
		}
	}

	New_gaussian_item = class 
		Menupullright "_Gaussian Fourier Mask" 
			"make various Gaussian Fourier filter masks" {
		High_low_item = class 
			Menuaction "_High or Low Pass" 
				("make a mask image for a highpass/lowpass " ++
					"Gaussian Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 4) fc.value ac.value 0 0 0;
				}
			}
		}

		Ring_item = class 
			Menuaction "_Ring Pass or Ring Reject"
				("make a mask image for an ring pass/reject " ++
				"Gaussian Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				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;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 10) fc.value rw.value 
							ac.value 0 0;
				}
			}
		}

		Band_item = class 
			Menuaction "_Band Pass or Band Reject"
				("make a mask image for a band pass/reject " ++
				"Gaussian Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				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;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 16) fcx.value fcy.value
							r.value ac.value 0;
				}
			}
		}
	}

	New_butterworth_item = class 
		Menupullright "_Butterworth Fourier Mask" 
			"make various Butterworth Fourier filter masks" {
		High_low_item = class 
			Menuaction "_High or Low Pass" 
				("make a mask image for a highpass/lowpass " ++ 
					"Butterworth Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				sense = sense_option;
				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
				order = Scale "Order" 1 10 2;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 2) order.value fc.value 
							ac.value 0 0;
				}
			}
		}

		Ring_item = class 
			Menuaction "_Ring Pass or Ring Reject"
				("make a mask image for an ring pass/reject " ++
				"Butterworth Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				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;
				order = Scale "Order" 1 10 2;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 8) order.value fc.value 
							rw.value ac.value 0;
				}
			}
		}

		Band_item = class 
			Menuaction "_Band Pass or Band Reject"
				("make a mask image for a band pass/reject " ++
				"Butterworth Fourier filter") {
			action = class
				_result {
				_vislevel = 3;

				nsize = Expression "Image size (pixels)" 64;
				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;
				order = Scale "Order" 1 10 2;

				_result 
					= build param (to_real nsize)
				{
						param f = f (sense.value + 14) order.value fcx.value
							fcy.value r.value ac.value;
				}
			}
		}
	}
}

Test_images_item = class 
	Menupullright "Test I_mages" "make a variety of test images" {
	Eye_item = class 
		Menuaction "_Spatial Response" 
			"image for testing the eye's spatial response" {
		action = class
			_result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			nheight = Expression "Image height (pixels)" 64;
			factor = Scale "Factor" 0.001 1 0.2;

			_result = Image (im_eye (to_real nwidth) (to_real nheight) 
				factor.value);
		}
	}

	Zone_plate = class 
		Menuaction "_Zone Plate" "make a zone plate" {
		action = class 
			_result {
			_vislevel = 3;

			nsize = Expression "Image size (pixels)" 64;

			_result = Image (im_zone (to_real nsize));
		}
	}

	Frequency_test_chart_item = class 
		Menuaction "_Frequency Testchart" 
			"make a black/white frequency test pattern" {
		action = class
			_result {
			_vislevel = 3;

			nwidth = Expression "Image width (pixels)" 64;
			sheight = Expression "Strip height (pixels)" 10;
			waves = Expression "Wavelengths" [64, 32, 16, 8, 4, 2];

			_result 
				= imagearray_assemble 0 0 (transpose [strips])
			{
				freq_slice wave = Image (sin (grey / wave) > 0);
				strips = map freq_slice waves.expr;
				grey = im_fgrey (to_real nwidth) (to_real sheight) * 
					360 * (to_real nwidth);
			}
		}
	}

	CRT_test_chart_item = class 
		Menuaction "CRT _Phosphor Chart" 
			"make an image for measuring phosphor colours" {
		action = class
			_result {
			_vislevel = 3;

			brightness = Scale "Brightness" 0 255 200;
			psize = Expression "Patch size (pixels)" 32;

			_result 
				= Image (imagearray_assemble 0 0 [[green, red], [blue, white]])
			{

				black = image_new (to_real psize) (to_real psize) 1
					Image_format.FLOAT Image_coding.NOCODING 
					Image_type.B_W 0 0 0;
				notblack = black + brightness;

				green = black ++ notblack ++ black;
				red = notblack ++ black ++ black;
				blue = black ++ black ++ notblack;
				white = notblack ++ notblack ++ notblack;
			}
		}
	}

	Greyscale_chart_item = class 
		Menuaction "_Greyscale" "make a greyscale" {
		action = class 
			_result {
			_vislevel = 3;

			pwidth = Expression "Patch width" 8;
			pheight = Expression "Patch height" 8;
			npatches = Expression "Number of patches" 16;

			_result
				= Image (image_set_type Image_type.B_W 
					(clip2fmt Image_format.UCHAR wedge))
			{
				wedge 
					= 255 / (to_real npatches - 1) * 
						(int) (strip?0 / to_real pwidth)
				{
					strip = make_xy (to_real pwidth * to_real npatches) pheight;
				}
			}
		}
	}

	CMYK_test_chart_item = class 
		Menuaction "_CMYK Wedges" "make a set of CMYK wedges" {
		action = class 
			_result {
			_vislevel = 3;

			pwidth = Expression "Patch width" 8;
			pheight = Expression "Patch height" 8;
			npatches = Expression "Number of patches" 16;

			_result
				= Image (image_set_type Image_type.CMYK 
					(clip2fmt Image_format.UCHAR strips))
			{
				wedge 
					= 255 / (to_real npatches - 1) * 
						(int) (strip?0 / to_real pwidth)
				{
					strip = make_xy (to_real pwidth * to_real npatches) pheight;
				}

				black = wedge * 0;

				C = wedge ++ black ++ black ++ black;
				M = black ++ wedge ++ black ++ black;
				Y = black ++ black ++ wedge ++ black;
				K = black ++ black ++ black ++ wedge;

				strips = imagearray_assemble 0 0 [[C],[M],[Y],[K]];
			}
		}
	}

    Colour_atlas_item = class
        Menuaction "_Colour Atlas"
            "make a grid of patches grouped around a colour" {
        action = class 
            _result {   
            _vislevel = 3;
       
            start = Colour_picker "Lab" [50,0,0];
            nstep = Expression "Number of steps" 2;
            ssize = Expression "Step size" 2; 

            _result
                = Image (colour_transform_to (get_type start) im)
            {
                base = colour_transform_to Image_type.LAB start;
                step = to_real ssize;
                offset = to_real nstep * step;
                range c = [c - offset, c - offset + step ... c + offset]; 
                                
                mk_patch b a = image_new 150 150 3
                    Image_format.FLOAT Image_coding.NOCODING 
                    Image_type.LAB (Vector [base?0, a, b]) 0 0;

                mk_strip b = map (mk_patch b) (range base?1);
                mk_grid = map mk_strip (range base?2);
                im = imagearray_assemble 15 15 mk_grid;
            }    
        }
    }
}

