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"
				Image_type.type_names "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") field_names 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;

		Custom_item = class
			Menuaction "C_ustom" "get any header field" {
			action x = class
				_result {
				_vislevel = 3;

				field = String "Field" "Xsize";
				parse = Option "Parse" [
					"No parsing",
					"Parse string as integer",
					"Parse string as real",
					"Parse string as hh:mm:ss"
				] 0;

				_result
					= map_unary (wrap @ process @ get_header field.value) x
				{
					parse_str parse str = parse (split is_space str)?0;

					parse_field name cast parse x
						= cast x, is_number x
						= parse_str parse x, is_string x
						= error ("not " ++ name);

					get_int = parse_field "int" 
						cast_signed_int parse_int;
					get_float = parse_field "float" 
						cast_float parse_float;
					get_time = parse_field "hh:mm:ss" 
						cast_signed_int parse_time;

					wrap x
						= Real x, is_real x
						= Vector x, is_real_list x
						= Image x, is_image x
						= Bool x, is_bool x
						= Matrix x, is_matrix x
						= String "String" x, is_string x
						= List x, is_list x
						= x;

					process = [
						id,
						get_int, 
						get_float,
						get_time
					]?parse;
				}
			}
		}
	}

    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" Image_type.type_names 
					(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));
			}
		}
	}
}

Image_cache_item = class
	Menuaction "C_ache" "cache calculated image pixels" {
	action x = class
		_result {
		_vislevel = 3;

		tile_width = Number "Tile width" 128;
		tile_height = Number "Tile height" 128;
		max_tiles = Number "Maximum number of tiles to cache" (-1);

		_result
			= map_unary process x
		{
			process image
				= cache (to_real tile_width) (to_real tile_height) 
					(to_real max_tiles) image;
		}
	}
}

#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 = [
						// we can't use id here since we want to "declass"
						// the members of x ... consider if x is a crop class,
						// for example, we don't want to inherit from crop, we
						// want to make a new image class
						rot180 @ rot180,
						rot90,
						rot180,
						rot270
					] ? 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;
				interp = Interpolate_picker Interpolate_type.BILINEAR;
	
				_result
					= map_unary process x
				{
					process image 
						= rotate interp angle image;
				}
			}
		}
	
		Straighten_item = class
			Menuaction "_Straighten" 
				("smallest rotation that makes an arrow either horizontal " ++
				"or vertical") {
			action x = class
				_result {
				_vislevel = 3;

				interp = Interpolate_picker Interpolate_type.BILINEAR;

				_result
					= map_unary straighten x
				{
					straighten arrow
						= rotate interp 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" {
		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 = Interpolate_picker Interpolate_type.BILINEAR;

				_result 
					= map_unary process x
				{
					process image 
						= resize interp xfactor yfactor 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;
				aspect = Toggle "Break aspect ratio" false;
				interp = Interpolate_picker Interpolate_type.BILINEAR;

				_result
					= map_unary process x
				{
					process image
						= resize interp h v image, aspect
						= resize interp fac fac image
					{
						xfac = to_real size / image.width;
						yfac = to_real size / image.height;
						max_factor 
							= [xfac, 1], xfac > yfac
							= [1, yfac];
						min_factor 
							= [xfac, 1], xfac < yfac
							= [1, yfac];
						[h, v] = [
							max_factor, 
							min_factor, 
							[xfac, 1], 
							[1, yfac]]?which;

						fac 
							= h, v == 1
							= v;
					}
				}
			}
		}

		Size_within_item = class
			Menuaction "Size _Within" "size to fit within a rectangle" {
			action x = class 
				_result {
				_vislevel = 3;

				// the rects we size to fit within
				_rects = [
					[2048, 1536], [1920, 1200], [1600, 1200], [1400, 1050], 
					[1280, 1024], [1024, 768], [800, 600], [640, 480] 
				];

				within = Option "Fit within (pixels)" (
					[print w ++ " x " ++ print h :: [w, h] <- _rects] ++
					["Custom"]
				) 4;
				custom_width = Expression "Custom width" 1000;
				custom_height = Expression "Custom height" 1000;
			    size = Option "Page size" [
					"Full page", "Half page", "Quarter page" 
				] 0;
				interp = Interpolate_picker Interpolate_type.BILINEAR;

			 	_result
				 	= map_unary process x
				{
					xdiv = [1, 2, 2]?size;
					ydiv = [1, 1, 2]?size;
					allrect = _rects ++ [
						[custom_width.expr, custom_height.expr]
					];
					[width, height] = allrect?within;

					process x
						= resize interp fac fac x, fac < 1
						= x
					{
						xfac = (width / xdiv) / x.width;
						yfac = (height / ydiv) / x.height;
						fac = min_pair xfac yfac;
					}
				}
			}
		}

		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" ["Nearest", "Bilinear"] 1;
		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;

	Bandand_item = class
		Menuaction "Bitwise Band AND" "bitwise AND of image bands" {
		action x = bandand x;
	}

	Bandor_item = class
		Menuaction "Bitwise Band OR" "bitwise OR of image bands" {
		action x = bandor x;
	}

    sep2 = 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
					= foldl1 [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 
		= crop x [l, t, w, h]
	{
		fields = [
			[has_left, get_left, 0],
			[has_top, get_top, 0],
			[has_width, get_width, 100],
			[has_height, get_height, 100]
		];

		[l, t, w, h] 
			= map get_default fields
		{
			get_default line
				= get x, has x
				= default
			{
				[has, get, default] = line;
			}
		}
	}

	crop x geo = class
		_result {
		_vislevel = 3;

		l = Expression "Crop left" ((int) (geo?0 + geo?2 / 4));
		t = Expression "Crop top" ((int) (geo?1 + geo?3 / 4));
		w = Expression "Crop width" (max_pair 1 ((int) (geo?2 / 2)));
		h = Expression "Crop height" (max_pair 1 ((int) (geo?3 / 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]
			];
			_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_draw_item = class 
	Menupullright "_Draw" "draw lines, circles, rectangles, floods" {
	Line_item = class Menuaction "_Line" "draw line on image" {
		action x = class
			_result {
			_vislevel = 3;

			x1 = Expression "Start x" 0;
			y1 = Expression "Start y" 0;
			x2 = Expression "End x" 100;
			y2 = Expression "End y" 100;

			i = Expression "Ink" [0];

			_result 
				= map_unary line x
			{
				line im 
					= draw_line x1 y1 x2 y2 i.expr im;
			}
		}
	}

	Rect_item = class Menuaction "_Rectangle" "draw rectangle on image" {
		action x = class
			_result {
			_vislevel = 3;

			rx = Expression "Left" 50;
			ry = Expression "Top" 50;
			rw = Expression "Width" 100;
			rh = Expression "Height" 100;

			f = Toggle "Fill" true;

			t = Scale "Line thickness" 1 50 3;

			i = Expression "Ink" [0];

			_result 
				= map_unary rect x
			{
				rect im 
					= draw_rect_width rx ry rw rh f t i.expr im;
			}
		}
	}

	Circle_item = class Menuaction "_Circle" "draw circle on image" {
		action x = class
			_result {
			_vislevel = 3;

			cx = Expression "Centre x" 100;
			cy = Expression "Centre y" 100;
			r = Expression "Radius" 50;

			f = Toggle "Fill" true;

			i = Expression "Ink" [0];

			_result 
				= map_unary circle x
			{
				circle im 
					= draw_circle cx cy r f i.expr im;
			}
		}
	}

	Flood_item = class Menuaction "_Flood" "flood bounded area of image" {
		action x = class
			_result {
			_vislevel = 3;

			sx = Expression "Start x" 100;
			sy = Expression "Start y" 100;

			e = Option "Flood while" [
				"Not equal to ink",
				"Equal to start point"
			] 0;

			// pick a default ink that won't flood, if we can
			i 
				= Expression "Ink" default_ink
			{
				default_ink
					= [0], ! has_image x
					= pixel;
				pixel = map mean (bandsplit (extract_area sx sy 1 1 im));
				im = get_image x;
			}

			_result 
				= map_unary flood x
			{
				flood im 
					= draw_flood sx sy i.expr im, e == 0
					= draw_flood_blob sx sy i.expr im;
			}
		}
	}

	Draw_scalebar_item = class Menuaction "_Scale" "draw scale bar" {
		action x = class
			_result {
			_vislevel = 3;

			px = Expression "Left" 50;
			py = Expression "Top" 50;
			wid = Expression "Width" 100;
			thick = Scale "Line thickness" 1 50 3;
			text = String "Dimension text" "50μm";
			font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
			pos = Option "Position Text" ["Above", "Below"] 1;
			vp = Option "Dimension by" [
				"Inner Vertical Edge", 
				"Centre of Vertical", 
				"Outer Vertical Edge"
			] 1;
            dpi = Expression "DPI" 100;
            ink = Colour "Lab" [50,0,0];
      
            _result
                = map_unary process x
            {
                process im
                    = blend (Image scale) ink' im
                {
                    // make an ink compatible with the image
                    ink' = colour_transform_to (get_type im) ink;

                    x = to_real px;
                    y = to_real py;
                    w = to_real wid;
                    d = to_real dpi;

                    t = floor thick;

                    bg = image_new (get_width im) (get_height im) (get_bands im)
                        (get_format im) (get_coding im) (get_type im) 0 0 0;
                    draw_block x y w t im =
                        draw_rect_width x y w t true 1 [255] im;
                    label = im_text text.value font.value w 1 d;
                    lw = get_width label;
                    lh = get_height label;
                    ly = [y - lh - t, y + 2 * t]?pos;
                    vx = [
						[x - t, x + w],
						[x - t / 2, x + w - t / 2],
						[x, x + w - t]
					]?vp;

					scale = (draw_block x y w t @
						draw_block vx?0 (y - 2 * t) t (t * 5) @
						draw_block vx?1 (y - 2 * t) t (t * 5) @
						insert_noexpand (x + w / 2 - lw / 2) ly label)
						bg;
				}
			}
		}
	}
}

Image_join_item = class 
	Menupullright "_Join" "join two or more images together" {
	Bandwise_item = class
		Menuaction "_Bandwise Join" "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;

			// we can't use map_unary since chop-into-tiles returns a group of
			// groups and we want to be able to reassemble that
			// TODO: chop-into-tiles should return an array class which
			// displays as group but does not have the looping behaviour?
			_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));
		}
	}

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

			ncol = Number "Max. Number of Columns" 1;
			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;

			_l 
				= split_lines ncol.value x.value, is_Group x
				= split_lines ncol.value x;

			_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 _l));
		}
	}
}

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" 9;
			ssize = Expression "Step size" 10; 
			psize = Expression "Patch size" 32;
			sepsize = Expression "Separator size" 4;
	
			_result
				= colour_transform_to (get_type start) blocks'''
			{
				size = (to_real nstep * 2 + 1) * to_real psize - 
					to_real sepsize;
				xy = make_xy size size; 

				xy_grid = (xy % to_real psize) < 
					(to_real psize - to_real sepsize);
				grid = xy_grid?0 & xy_grid?1;

				blocks = (int) (to_real ssize * ((int) (xy / to_real psize)));
				lab_start = colour_transform_to Image_type.LAB start;
				blocks' = blocks - to_real nstep * to_real ssize + 
					Vector (tl lab_start.value);
				blocks'' = hd lab_start.value ++ Image blocks';
				blocks''' 
					= image_set_type Image_type.LAB blocks'', Image grid
					= 0;
            }    
        }
    }
}

