1Image_new_item = class
2	Menupullright "_New" "make new things" {
3	Image_black_item = class
4		Menuaction "_Image" "make a new image" {
5		format_names = [
6			"8-bit unsigned int - UCHAR", 		// 0
7			"8-bit signed int - CHAR", 			// 1
8			"16-bit unsigned int - USHORT", 	// 2
9			"16-bit signed int - SHORT", 		// 3
10			"32-bit unsigned int - UINT", 		// 4
11			"32-bit signed int - INT", 			// 5
12			"32-bit float - FLOAT", 			// 6
13			"64-bit complex - COMPLEX", 		// 7
14			"64-bit float - DOUBLE", 			// 8
15			"128-bit complex - DPCOMPLEX" 		// 9
16		];
17
18		action = class
19			Image _result {
20			_vislevel = 3;
21
22			nwidth = Expression "Image width (pixels)" 64;
23			nheight = Expression "Image height (pixels)" 64;
24			nbands = Expression "Image bands" 1;
25			format_option = Option "Image format" format_names 0;
26			type_option = Option_enum
27				Image_type.type_names "Image type" "B_W";
28			pixel = Expression "Pixel value" 0;
29
30			_result
31				= image_new (to_real nwidth) (to_real nheight) (to_real nbands)
32					(to_real format_option) Image_coding.NOCODING
33					type_option.value_thing pixel.expr 0 0;
34		}
35	}
36
37	Image_new_from_image_item = class
38		Menuaction "_From Image" "make a new image based on image x" {
39		action x = class
40			Image _result {
41				_vislevel = 3;
42
43				pixel = Expression "Pixel value" 0;
44
45				_result
46					= image_new x.width x.height x.bands
47						x.format x.coding x.type pixel.expr x.xoffset x.yoffset;
48		}
49	}
50
51	Image_region_item = class
52		Menupullright "_Region on Image" "make a new region on an image" {
53		Region_item = class
54			Menuaction "_Region" "make a region on an image" {
55			action image = scope.Region_relative image 0.25 0.25 0.5 0.5;
56		}
57
58		Mark_item = class
59			Menuaction "_Point" "make a point on an image" {
60			action image = scope.Mark_relative image 0.5 0.5;
61		}
62
63		Arrow_item = class
64			Menuaction "_Arrow" "make an arrow on an image" {
65			action image = scope.Arrow_relative image 0.25 0.25 0.5 0.5;
66		}
67
68		HGuide_item = class
69			Menuaction "_Horizontal Guide"
70				"make a horizontal guide on an image" {
71			action image = scope.HGuide image 0.5;
72		}
73
74		VGuide_item = class
75			Menuaction "_Vertical Guide" "make a vertical guide on an image" {
76			action image = scope.VGuide image 0.5;
77		}
78
79    	sep1 = Menuseparator;
80
81		Move_item = class
82			Menuaction "From Region"
83				"new region on image using existing region as a guide" {
84			action a b
85				= map_binary process a b
86			{
87				process a b
88					= x.Region target x.left x.top x.width x.height,
89							is_Region x
90					= x.Arrow target x.left x.top x.width x.height,
91							is_Arrow x
92					= error "bad arguments to region-from-region"
93				{
94					// prefer image then region
95					compare a b
96						= false,
97							!is_Image a && is_Image b
98						= false,
99							is_Region a && !is_Region b
100						= true;
101
102					[target, x] = sortc compare [a, b];
103				}
104			}
105		}
106	}
107}
108
109Image_convert_to_image_item = class
110	Menuaction "Con_vert to Image" "convert anything to an image" {
111	action x = to_image x;
112}
113
114Image_number_format_item = class
115	Menupullright "_Format" "convert numeric format" {
116
117	U8_item = class
118		Menuaction "_8 bit unsigned" "convert to unsigned 8 bit [0, 255]" {
119		action x = map_unary cast_unsigned_char x;
120	}
121
122	U16_item = class
123		Menuaction "1_6 bit unsigned"
124			"convert to unsigned 16 bit [0, 65535]" {
125		action x = map_unary cast_unsigned_short x;
126	}
127
128	U32_item = class
129		Menuaction "_32 bit unsigned"
130			"convert to unsigned 32 bit [0, 4294967295]" {
131		action x = map_unary cast_unsigned_int x;
132	}
133
134    sep1 = Menuseparator;
135
136	S8_item = class
137		Menuaction "8 _bit signed" "convert to signed 8 bit [-128, 127]" {
138		action x = map_unary cast_signed_char x;
139	}
140
141	S16_item = class
142		Menuaction "16 b_it signed"
143			"convert to signed 16 bit [-32768, 32767]" {
144		action x = map_unary cast_signed_short x;
145	}
146
147	S32_item = class
148		Menuaction "32 bi_t signed"
149			"convert to signed 32 bit [-2147483648, 2147483647]" {
150		action x = map_unary cast_signed_int x;
151	}
152
153    sep2 = Menuseparator;
154
155	Float_item = class
156		Menuaction "_Single precision float"
157			"convert to IEEE 32 bit float" {
158		action x = map_unary cast_float x;
159	}
160
161	Double_item = class
162		Menuaction "_Double precision float"
163			"convert to IEEE 64 bit float" {
164		action x = map_unary cast_double x;
165	}
166
167    sep3 = Menuseparator;
168
169	Scmplxitem = class
170		Menuaction "Single _precision complex"
171			"convert to 2 x IEEE 32 bit float" {
172		action x = map_unary cast_complex x;
173	}
174
175	Dcmplx_item = class
176		Menuaction "Double p_recision complex"
177			"convert to 2 x IEEE 64 bit float" {
178		action x = map_unary cast_double_complex x;
179	}
180}
181
182Image_header_item = class
183	Menupullright "_Header" "do stuff to the image header" {
184
185	Image_get_item = class
186		Menupullright "_Get" "get header fields" {
187
188		// the header fields we can get
189		fields = class {
190			type = 0;
191			width = 1;
192			height = 2;
193			format = 3;
194			bands = 4;
195			xres = 5;
196			yres = 6;
197			xoffset = 7;
198			yoffset = 8;
199			coding = 9;
200
201			field_names = Enum [
202				$width => width,
203				$height => height,
204				$bands => bands,
205				$format => format,
206				$type => type,
207				$xres => xres,
208				$yres => yres,
209				$xoffset => xoffset,
210				$yoffset => yoffset,
211				$coding => coding
212			];
213
214			field_option name = Option_enum field_names (_ "Field") name;
215
216			field_funcs = Table [
217				[type, get_type],
218				[width, get_width],
219				[height, get_height],
220				[format, get_format],
221				[bands, get_bands],
222				[xres, get_xres],
223				[yres, get_yres],
224				[xoffset, get_xoffset],
225				[yoffset, get_yoffset],
226				[coding, get_coding]
227			];
228		}
229
230		get_field field_name x = class
231			_result {
232			_vislevel = 3;
233
234			field = fields.field_option field_name;
235
236			_result
237				= map_unary (Real @
238					fields.field_funcs.lookup 0 1 field.value_thing) x;
239		}
240
241		Width_item = class
242			Menuaction "_Width" "get width" {
243			action x = get_field "width" x;
244		}
245
246		Height_item = class
247			Menuaction "_Height" "get height" {
248			action x = get_field "height" x;
249		}
250
251		Bands_item = class
252			Menuaction "_Bands" "get bands" {
253			action x = get_field "bands" x;
254		}
255
256		Format_item = class
257			Menuaction "_Format" "get format" {
258			action x = get_field "format" x;
259		}
260
261		Type_item = class
262			Menuaction "_Type" "get type" {
263			action x = get_field "type" x;
264		}
265
266		Xres_item = class
267			Menuaction "_Xres" "get X resolution" {
268			action x = get_field "xres" x;
269		}
270
271		Yres_item = class
272			Menuaction "_Yres" "get Y resolution" {
273			action x = get_field "yres" x;
274		}
275
276		Xoffset_item = class
277			Menuaction "X_offset" "get X offset" {
278			action x = get_field "xoffset" x;
279		}
280
281		Yoffset_item = class
282			Menuaction "Yo_ffset" "get Y offset" {
283			action x = get_field "yoffset" x;
284		}
285
286		Coding_item = class
287			Menuaction "_Coding" "get coding" {
288			action x = get_field "coding" x;
289		}
290
291    	sep1 = Menuseparator;
292
293		Custom_item = class
294			Menuaction "C_ustom" "get any header field" {
295			action x = class
296				_result {
297				_vislevel = 3;
298
299				field = String "Field" "Xsize";
300				parse = Option "Parse" [
301					"No parsing",
302					"Parse string as integer",
303					"Parse string as real",
304					"Parse string as hh:mm:ss"
305				] 0;
306
307				_result
308					= map_unary (wrap @ process @ get_header field.value) x
309				{
310					parse_str parse str = parse (split is_space str)?0;
311
312					parse_field name cast parse x
313						= cast x, is_number x
314						= parse_str parse x, is_string x
315						= error ("not " ++ name);
316
317					get_int = parse_field "int" cast_int parse_int;
318					get_float = parse_field "float" cast_float parse_float;
319					get_time = parse_field "hh:mm:ss" cast_int parse_time;
320
321					wrap x
322						= Real x, is_real x
323						= Vector x, is_real_list x
324						= Image x, is_image x
325						= Bool x, is_bool x
326						= Matrix x, is_matrix x
327						= String "String" x, is_string x
328						= List x, is_list x
329						= x;
330
331					process = [
332						id,
333						get_int,
334						get_float,
335						get_time
336					]?parse;
337				}
338			}
339		}
340	}
341
342    sep1 = Menuseparator;
343
344	Image_set_meta_item = class
345		Menuaction "_Set" "set image metadata" {
346		action x = class
347			_result {
348			_vislevel = 3;
349
350			fname = String "Field" "field-name";
351			val = Expression "Value" 42;
352
353			_result
354				= map_unary process x
355			{
356				process image
357					= set_header fname.value val.expr image;
358			}
359		}
360	}
361
362	Image_edit_header_item = class
363		Menuaction "_Edit" "change advisory header fields of image" {
364		type_names = Image_type.type_names;
365		all_names = sort (map (extract 0) type_names.value);
366
367		get_prop has get def x
368			= get x, has x
369			= def;
370
371		action x = class
372			_result {
373			_vislevel = 3;
374
375			nxres = Expression "Xres" (get_prop has_xres get_xres 1 x);
376			nyres = Expression "Yres" (get_prop has_yres get_yres 1 x);
377			nxoff = Expression "Xoffset" (get_prop has_xoffset get_xoffset 0 x);
378			nyoff = Expression "Yoffset" (get_prop has_yoffset get_yoffset 0 x);
379
380			type_option
381				= Option_enum Image_type.type_names "Image type"
382					(Image_type.type_names.get_name type)
383			{
384				type
385					= x.type, is_Image x
386					= Image_type.MULTIBAND;
387			}
388
389			_result
390				= map_unary process x
391			{
392				process image
393					= Image (im_copy_set image.value type_option.value_thing
394						(to_real nxres) (to_real nyres)
395						(to_real nxoff) (to_real nyoff));
396			}
397		}
398	}
399}
400
401Image_cache_item = class
402	Menuaction "C_ache" "cache calculated image pixels" {
403	action x = class
404		_result {
405		_vislevel = 3;
406
407		tile_width = Number "Tile width" 32;
408		tile_height = Number "Tile height" 32;
409		max_tiles = Number "Maximum number of tiles to cache" (-1);
410
411		_result
412			= map_unary process x
413		{
414			process image
415				= cache (to_real tile_width) (to_real tile_height)
416					(to_real max_tiles) image;
417		}
418	}
419}
420
421#separator
422
423Image_levels_item = class
424	Menupullright "_Levels" "change image levels" {
425	Scale_item = class
426		Menuaction "_Scale to 0 - 255" "linear transform to fit 0 - 255 range" {
427		action x = map_unary scale x;
428	}
429
430	Linear_item = class
431		Menuaction "_Linear" "linear transform of image levels" {
432		action x = class
433			_result {
434			_vislevel = 3;
435
436			scale = Scale "Scale" 0.001 3 1;
437			offset = Scale "Offset" (-128) 128 0;
438
439			_result
440				= map_unary adj x
441			{
442				adj x
443					// only force back to input type if this is a thing
444					// with a type ... so we work for Colour / Matrix etc.
445					= clip2fmt x.format x', has_member "format" x
446					= x'
447				{
448					x' = x * scale + offset;
449				}
450			}
451		}
452	}
453
454	Gamma_item = class
455		Menuaction "_Power" "power transform of image levels (gamma)" {
456		action x = class
457			_result {
458			_vislevel = 3;
459
460			gamma = Scale "Gamma" 0.001 4 1;
461			image_maximum_hint = "You may need to change image_maximum if " ++
462				"this is not an 8 bit image";
463			im_mx
464				= Expression "Image maximum" mx
465			{
466				mx
467						= Image_format.maxval x.format, has_format x
468						= 255;
469			}
470
471			_result
472				= map_unary gam x
473			{
474				gam x
475					= clip2fmt (get_format x) x', has_format x
476					= x'
477				{
478					x' = (im_mx.expr / im_mx.expr ** gamma) * x ** gamma;
479				}
480			}
481		}
482	}
483
484	Tone_item = class
485		Menuaction "_Tone Curve" "adjust tone curve" {
486		action x = class
487			_result {
488			_vislevel = 3;
489
490			b = Scale "Black point"  0 100 0;
491			w = Scale "White point"  0 100 100;
492
493			sp = Scale "Shadow point" 0.1 0.3 0.2;
494			mp = Scale "Mid-tone point" 0.4 0.6 0.5;
495			hp = Scale "Highlight point" 0.7 0.9 0.8;
496
497			sa = Scale "Shadow adjust" (-15) 15 0;
498			ma = Scale "Mid-tone adjust" (-30) 30 0;
499			ha = Scale "Highlight adjust" (-15) 15 0;
500
501			curve = tone_build x.format b w sp mp hp sa ma ha;
502
503			_result = map_unary (hist_map curve) x;
504		}
505	}
506}
507
508Image_transform_item = class
509	Menupullright "_Transform" "transform images" {
510	Rotate_item = class
511		Menupullright "Ro_tate" "rotate image" {
512		Fixed_item = class
513			Menupullright "_Fixed" "clockwise rotation by fixed angles" {
514	        rotate_widget default x = class
515				_result {
516				_vislevel = 3;
517
518				angle = Option "Rotate by" [
519					"Don't rotate",
520					"90 degrees clockwise",
521					"180 degrees",
522					"90 degrees anticlockwise"
523				] default;
524
525				_result
526					= map_unary process x
527				{
528					process in = [
529						in,
530						rot90 in,
531						rot180 in,
532						rot270 in
533					] ? angle;
534				}
535			}
536
537			Rot90_item = class
538				Menuaction "_90 Degrees" "clockwise rotation by 90 degrees" {
539				action x = rotate_widget 1 x;
540			}
541
542			Rot180_item = class
543				Menuaction "_180 Degrees" "clockwise rotation by 180 degrees" {
544				action x = rotate_widget 2 x;
545			}
546
547			Rot270_item = class
548				Menuaction "_270 Degrees" "clockwise rotation by 270 degrees" {
549				action x = rotate_widget 3 x;
550			}
551		}
552
553		Free_item = class
554			Menuaction "_Free" "clockwise rotation by any angle" {
555			action x = class
556				_result {
557				_vislevel = 3;
558
559				angle = Scale "Angle" (-180) 180 0;
560
561				_result
562					= map_unary process x
563				{
564					process image
565						= rotate angle image;
566				}
567			}
568		}
569
570		Straighten_item = class
571			Menuaction "_Straighten"
572				("smallest rotation that makes an arrow either horizontal " ++
573				"or vertical") {
574			action x
575				= map_unary straighten x
576			{
577				straighten arrow
578					= rotate angle'' arrow.image
579				{
580					x = arrow.width;
581					y = arrow.height;
582
583					angle = im (polar (x, y));
584
585					angle'
586						= angle - 360, angle > 315
587						= angle - 180, angle > 135
588						= angle;
589
590					angle''
591						= -angle', angle' >= (-45) && angle' < 45
592						= 90 - angle';
593				}
594			}
595		}
596	}
597
598	Flip_item = class
599		Menupullright "_Flip" "mirror left/right or up/down" {
600		Left_right_item = class
601			Menuaction "_Left Right" "mirror object left/right" {
602			action x = map_unary fliplr x;
603		}
604
605		Top_bottom_item = class
606			Menuaction "_Top Bottom" "mirror object top/bottom" {
607			action x = map_unary fliptb x;
608		}
609	}
610
611	Resize_item = class
612		Menupullright "_Resize" "change image size" {
613		_interp = Option_enum Interpolate.names "Interpolation" "Bilinear";
614
615		Scale_item = class
616			Menuaction "_Scale" "scale image size by a factor" {
617			action x = class
618				_result {
619				_vislevel = 3;
620
621				xfactor = Expression "Horizontal scale factor" 1;
622				yfactor = Expression "Vertical scale factor" 1;
623				interp = _interp;
624
625				_result
626					= map_unary process x
627				{
628					process image
629						= resize xfactor yfactor interp.value_thing image;
630				}
631			}
632		}
633
634		Size_item = class
635			Menuaction "_Size To" "resize to a fixed size" {
636			action x = class
637				_result {
638				_vislevel = 3;
639
640				which = Option "Resize axis" [
641					"Shortest",
642					"Longest",
643					"Horizontal",
644					"Vertical"
645				] 0;
646				size = Expression "Resize to (pixels)" 128;
647				interp = _interp;
648
649				_result
650					= map_unary process x
651				{
652					process image
653						= resize fac fac interp.value_thing image
654					{
655						xfac = to_real size / image.width;
656						yfac = to_real size / image.height;
657						max_factor = max_pair xfac yfac;
658						min_factor = min_pair xfac yfac;
659						fac = [max_factor, min_factor, xfac, yfac]?which;
660					}
661				}
662			}
663		}
664
665		Size_within_item = class
666			Menuaction "Size _Within" "size to fit within a rectangle" {
667			action x = class
668				_result {
669				_vislevel = 3;
670
671				// the rects we size to fit within
672				_rects = [
673					[2048, 1536], [1920, 1200], [1600, 1200], [1400, 1050],
674					[1280, 1024], [1024, 768], [800, 600], [640, 480]
675				];
676
677				within = Option "Fit within (pixels)" (
678					[print w ++ " x " ++ print h :: [w, h] <- _rects] ++
679					["Custom"]
680				) 4;
681				custom_width = Expression "Custom width" 1000;
682				custom_height = Expression "Custom height" 1000;
683			    size = Option "Page size" [
684					"Full page", "Half page", "Quarter page"
685				] 0;
686				interp = _interp;
687
688			 	_result
689				 	= map_unary process x
690				{
691					xdiv = [1, 2, 2]?size;
692					ydiv = [1, 1, 2]?size;
693					allrect = _rects ++ [
694						[custom_width.expr, custom_height.expr]
695					];
696					[width, height] = allrect?within;
697
698					process x
699						= resize fac fac interp.value_thing x, fac < 1
700						= x
701					{
702						xfac = (width / xdiv) / x.width;
703						yfac = (height / ydiv) / x.height;
704						fac = min_pair xfac yfac;
705					}
706				}
707			}
708		}
709
710		Resize_canvas_item = class
711			Menuaction "_Canvas" "change size of surrounding image" {
712			action x = class
713				_result {
714				_vislevel = 3;
715
716				// try to guess a sensible size for the new image
717				_guess_size
718					= x.rect, is_Image x
719					= Rect 0 0 100 100;
720
721				nwidth = Expression "New width (pixels)" _guess_size.width;
722				nheight = Expression "New height (pixels)" _guess_size.height;
723				bgcolour = Expression "Background colour" 0;
724
725				position = Option "Position" [
726					"North-west",
727					"North",
728					"North-east",
729					"West",
730					"Centre",
731					"East",
732					"South-west",
733					"South",
734					"South-east",
735					"Specify in pixels"
736				] 4;
737				left = Expression "Pixels from left" 0;
738				top = Expression "Pixels from top" 0;
739
740				_result
741					= map_unary process x
742				{
743					process image
744						= insert_noexpand xp yp image background
745					{
746						width = image.width;
747						height = image.height;
748						coding = image.coding;
749						bands
750							= 3, coding == Image_coding.LABPACK
751							= image.bands;
752						format
753							= Image_format.FLOAT, coding == Image_coding.LABPACK
754							= image.format;
755						type = image.type;
756
757						// placement vectors ... left, centre, right
758						xposv = [0, to_real nwidth / 2 - width / 2,
759							to_real nwidth - width];
760						yposv = [0, to_real nheight / 2 - height / 2,
761							to_real nheight - height];
762						xp
763							= left, position == 9
764							= xposv?((int) (position % 3));
765						yp
766							= top, position == 9
767							= yposv?((int) (position / 3));
768
769						background = image_new nwidth nheight
770							bands format coding type bgcolour.expr 0 0;
771					}
772				}
773			}
774		}
775	}
776
777	Image_perspective_item = Perspective_item;
778
779	Image_rubber_item = class
780		Menupullright "Ru_bber Sheet"
781			"automatically warp images to superposition" {
782		rubber_interp = Option "Interpolation"
783			(map (extract 0) Interpolate.names.value) Interpolate.BILINEAR;
784		rubber_order = Option "Order" ["0", "1", "2", "3"] 1;
785		rubber_wrap = Toggle "Wrap image edges" false;
786
787		// a transform ... a matrix, plus the size of the image the
788		// matrix was made for
789		Transform matrix image_width image_height = class
790			matrix {
791			// scale a transform ... if it worked for a m by n image, make
792			// it work for a (m * xfac) by (y * yfac) image
793			rescale xfac yfac
794				= Transform (Matrix (map2 (map2 multiply) matrix.value facs))
795					(image_width * xfac) (image_height * yfac)
796			{
797				facs = [
798					[xfac, yfac],
799					[1, 1],
800					[1, 1],
801					[1 / xfac, 1 / yfac],
802					[1 / xfac, 1 / yfac],
803					[1 / xfac, 1 / yfac]
804				];
805			}
806		}
807
808		// yuk!!!! fix is_instanceof to not need absolute names
809		is_Transform = is_instanceof
810			"Image_transform_item.Image_rubber_item.Transform";
811
812		Find_item = class
813			Menuaction "_Find"
814				("find a transform which will map sample image onto " ++
815				"reference") {
816			action reference sample = class
817				_trn {
818				_vislevel = 3;
819
820				// controls
821				order = rubber_order;
822				interp = rubber_interp;
823				wrap = rubber_wrap;
824				max_err = Expression "Maximum error" 0.3;
825				max_iter = Expression "Maximum iterations" 10;
826
827				// transform
828				[sample', trn, err] = transform_search
829					max_err max_iter order interp wrap
830					sample reference;
831				transformed_image = Image sample';
832				_trn = Transform trn reference.width reference.height;
833				final_error = err;
834			}
835		}
836
837		Apply_item = class
838			Menuaction "_Apply" "apply a transform to an image" {
839			action a b = class
840				_result {
841				_vislevel = 3;
842
843				// controls
844				interp = rubber_interp;
845				wrap = rubber_wrap;
846
847				_result
848					= map_binary trans a b
849				{
850					trans a b
851						= transform interp wrap t' i
852					{
853						// get the transform arg first
854						[i, t] = sortc (const is_Transform) [a, b];
855						t' = t.rescale (i.width / t.image_width)
856							(i.height / t.image_height);
857					}
858				}
859			}
860		}
861	}
862
863    sep1 = Menuseparator;
864
865	Match_item = class
866		Menuaction "_Linear Match"
867			"rotate and scale one image to match another" {
868		action x y = class
869			_result {
870			_vislevel = 3;
871
872			// try to find an image ... for a group, get the first item
873			find_image x
874				= x, is_Image x
875				= find_image x?0, is_list x
876				= find_image x.value, is_class x && has_value x
877				= error "unable to find image";
878
879			_a = find_image x;
880			_b = find_image y;
881
882			ap1 = Mark_relative _a 0.5 0.25;
883			bp1 = Mark_relative _b 0.5 0.25;
884			ap2 = Mark_relative _a 0.5 0.75;
885			bp2 = Mark_relative _b 0.5 0.75;
886
887			refine = Toggle "Refine selected tie-points" false;
888			lock = Toggle "No resize" false;
889
890			_result
891				= map_binary process x y
892			{
893				process a b
894					= Image b'''
895				{
896					_prefs = Workspaces.Preferences;
897					window = _prefs.MOSAIC_WINDOW_SIZE;
898					object = _prefs.MOSAIC_OBJECT_SIZE;
899
900					a' = a.value;
901					b' = b.value;
902
903					b'' = clip2fmt a.format b';
904
905					// return p2 ... if lock is set, return a p2 a standard
906					// distance along the vector joining p1 and p2
907					norm p1 p2
908						= Rect left' top' 0 0, lock
909						= p2
910					{
911						v = (p2.left - p1.left, p2.top - p1.top);
912						// 100000 to give precision since we pass points as
913						// ints to match
914						n = 100000 * sign v;
915						left' = p1.left + re n;
916						top' = p1.top + im n;
917					}
918
919					ap2'' = norm ap1 ap2;
920					bp2'' = norm bp1 bp2;
921
922					b'''
923						= im_match_linear_search a' b''
924							ap1.left ap1.top bp1.left bp1.top
925							ap2''.left ap2''.top bp2''.left bp2''.top
926							object window,
927								// we can't search if lock is on
928								refine && !lock
929						= im_match_linear a' b''
930							ap1.left ap1.top bp1.left bp1.top
931							ap2''.left ap2''.top bp2''.left bp2''.top;
932				}
933			}
934		}
935	}
936
937	Image_perspective_match_item = Perspective_match_item;
938}
939
940Image_band_item = class
941	Menupullright "_Band" "manipulate image bands" {
942	// like extract_bands, but return [] for zero band image
943	// makes compose a bit simpler
944	exb b n x
945		= [], to_real n == 0
946		= extract_bands b n x;
947
948	Extract_item = class Menuaction "_Extract" "extract bands from image" {
949		action x = class
950			_result {
951			_vislevel = 3;
952
953			first = Expression "Extract from band" 0;
954			number = Expression "Extract this many bands" 1;
955
956			_result = map_unary (exb first number) x;
957		}
958	}
959
960	Insert_item = class Menuaction "_Insert" "insert bands into image" {
961		action x y = class
962			_result {
963			_vislevel = 3;
964
965			first = Expression "Insert at position" 0;
966
967			_result
968				= map_binary process x y
969			{
970				process im1 im2
971					= exb 0 f im1 ++ im2 ++ exb f (b - f) im1
972				{
973					f = to_real first;
974					b = im1.bands;
975				}
976			}
977		}
978	}
979
980	Delete_item = class Menuaction "_Delete" "delete bands from image" {
981		action x = class
982			_result {
983			_vislevel = 3;
984
985			first = Expression "Delete from band" 0;
986			number = Expression "Delete this many bands" 1;
987
988			_result
989				= map_unary process x
990			{
991				process im
992					= exb 0 f im ++ exb (f + n) (b - (f + n)) im
993				{
994					f = to_real first;
995					n = to_real number;
996					b = im.bands;
997				}
998			}
999		}
1000	}
1001
1002	Bandwise_item = Image_join_item.Bandwise_item;
1003
1004    sep1 = Menuseparator;
1005
1006	To_dimension_item = class
1007		Menuaction "To D_imension" "convert bands to width or height" {
1008		action x = class
1009			_result {
1010			_vislevel = 3;
1011
1012			orientation = Option "Orientation" [
1013				"Horizontal",
1014				"Vertical"
1015			] 0;
1016
1017			_result
1018				= map_unary process x
1019			{
1020				process im
1021					= foldl1 [join_lr, join_tb]?orientation (bandsplit im);
1022			}
1023		}
1024	}
1025
1026	To_bands_item = class
1027		Menuaction "To B_ands" "turn width or height to bands" {
1028		action x = class
1029			_result {
1030			_vislevel = 3;
1031
1032			orientation = Option "Orientation" [
1033				"Horizontal",
1034				"Vertical"
1035			] 0;
1036
1037			_result
1038				= map_unary process x
1039			{
1040				process im
1041					= bandjoin (map extract_column [0 .. im.width - 1]),
1042						orientation == 0
1043					= bandjoin (map extract_row [0 .. im.height - 1])
1044				{
1045					extract_column n
1046						= extract_area n 0 1 im.height im;
1047					extract_row n
1048						= extract_area 0 n im.width 1 im;
1049				}
1050			}
1051		}
1052	}
1053}
1054
1055Image_crop_item = class
1056	Menuaction "_Crop" "extract a rectangular area from an image" {
1057	action x = class
1058		_result {
1059		_vislevel = 3;
1060
1061		// try to find the image geometry ... don't bother trying to look
1062		// inside groups though
1063		_geo
1064			= x.rect, is_Image x
1065			= Rect 0 0 100 100;
1066
1067		l = Expression "Crop left" ((int) (_geo.left + _geo.width / 4));
1068		t = Expression "Crop top" ((int) (_geo.top + _geo.height / 4));
1069		w = Expression "Crop width" (max_pair 1 ((int) (_geo.width / 2)));
1070		h = Expression "Crop height" (max_pair 1 ((int) (_geo.height / 2)));
1071
1072		_result
1073			= map_nary (list_5ary extract) [x, l.expr, t.expr, w.expr, h.expr]
1074		{
1075			extract im l t w h
1076				= extract_area left' top' width' height' im
1077			{
1078				width' = min_pair (to_real w) im.width;
1079				height' = min_pair (to_real h) im.height;
1080				left' = range 0 (to_real l) (im.width - width');
1081				top' = range 0 (to_real t) (im.height - height');
1082			}
1083		}
1084	}
1085}
1086
1087Image_insert_item = class
1088	Menuaction "_Insert" "insert a small image into a large image" {
1089	action a b
1090		= insert_position, is_Group a || is_Group b
1091		= insert_area
1092	{
1093		insert_area = class
1094			_result {
1095			_check_args = [
1096				[a, "a", check_Image],
1097				[b, "b", check_Image]
1098			] ++ super._check_args;
1099			_vislevel = 3;
1100
1101			// sort to get smallest first
1102			_pred x y = x.width * x.height < y.width * y.height;
1103			[_a', _b'] = sortc _pred [a, b];
1104
1105			place
1106				= Area _b' left top width height
1107			{
1108				// be careful in case b is smaller than a
1109				left = max_pair 0 ((_b'.width - _a'.width) / 2);
1110				top = max_pair 0 ((_b'.height - _a'.height) / 2);
1111				width = min_pair _a'.width _b'.width;
1112				height = min_pair _a'.height _b'.height;
1113			}
1114
1115			_result
1116				= insert_noexpand place.left place.top
1117					(clip2fmt _b'.format a'') _b'
1118			{
1119				a'' = extract_area 0 0 place.width place.height _a';
1120			}
1121		}
1122
1123		insert_position = class
1124			_result {
1125			_vislevel = 3;
1126
1127			position = Option "Position" [
1128				"North-west",
1129				"North",
1130				"North-east",
1131				"West",
1132				"Centre",
1133				"East",
1134				"South-west",
1135				"South",
1136				"South-east",
1137				"Specify in pixels"
1138			] 4;
1139			left = Expression "Pixels from left" 0;
1140			top = Expression "Pixels from top" 0;
1141
1142			_result
1143				= map_binary insert a b
1144			{
1145				insert a b
1146					= insert_noexpand left top (clip2fmt b.format a) b,
1147						position == 9
1148					= insert_noexpand xp yp (clip2fmt b.format a) b
1149				{
1150					xr = b.width - a.width;
1151					yr = b.height - a.height;
1152					xp = [0, xr / 2, xr]?((int) (position % 3));
1153					yp = [0, yr / 2, yr]?((int) (position / 3));
1154				}
1155			}
1156		}
1157	}
1158}
1159
1160Image_select_item = Select_item;
1161
1162Image_join_item = class
1163	Menupullright "_Join" "join two or more images together" {
1164	Bandwise_item = class
1165		Menuaction "_Bandwise" "join two images bandwise" {
1166		action a b = join a b;
1167	}
1168
1169    sep1 = Menuseparator;
1170
1171	join_lr shim bg align a b
1172		= im2
1173	{
1174		w = a.width + b.width + shim;
1175		h = max_pair a.height b.height;
1176
1177		back = image_new w h a.bands a.format a.coding a.type bg 0 0;
1178
1179		ya = [0, max_pair 0 ((b.height - a.height)/2),
1180			max_pair 0 (b.height - a.height)];
1181		yb = [0, max_pair 0 ((a.height - b.height)/2),
1182			max_pair 0 (a.height - b.height)];
1183
1184		im1 = insert_noexpand 0 ya?align a back;
1185		im2 = insert_noexpand (a.width + shim) yb?align b im1;
1186	}
1187
1188	join_tb shim bg align a b
1189		= im2
1190	{
1191		w = max_pair a.width b.width;
1192		h = a.height + b.height + shim;
1193
1194		back = image_new w h a.bands a.format a.coding a.type bg 0 0;
1195
1196		xa = [0, max_pair 0 ((b.width - a.width)/2),
1197			max_pair 0 (b.width - a.width)];
1198		xb = [0, max_pair 0 ((a.width - b.width)/2),
1199			max_pair 0 (a.width - b.width)];
1200
1201		im1 = insert_noexpand xa?align 0 a back;
1202		im2 = insert_noexpand xb?align (a.height + shim) b im1;
1203	}
1204
1205	halign_names = ["Top", "Centre", "Bottom"];
1206	valign_names = ["Left", "Centre", "Right"];
1207
1208	Left_right_item = class
1209		Menuaction "_Left to Right" "join two images left-right" {
1210		action a b = class
1211			_result {
1212			_vislevel = 3;
1213
1214			shim = Scale "Spacing" 0 100 0;
1215			bg_colour = Expression "Background colour" 0;
1216			align = Option "Alignment" halign_names 1;
1217
1218			_result = map_binary
1219				(join_lr shim.value bg_colour.expr align.value) a b;
1220		}
1221	}
1222
1223	Top_bottom_item = class
1224		Menuaction "_Top to Bottom" "join two images top-bottom" {
1225		action a b = class
1226			_result {
1227			_vislevel = 3;
1228
1229			shim = Scale "Spacing" 0 100 0;
1230			bg_colour = Expression "Background colour" 0;
1231			align = Option "Alignment" valign_names 1;
1232
1233			_result = map_binary
1234				(join_tb shim.value bg_colour.expr align.value) a b;
1235		}
1236	}
1237
1238    sep2 = Menuseparator;
1239
1240	Array_item = class
1241		Menuaction "_Array"
1242			"join a list of lists of images into a single image" {
1243		action x = class
1244			_result {
1245			_vislevel = 3;
1246
1247			hshim = Scale "Horizontal spacing" (-100) (100) 0;
1248			vshim = Scale "Vertical spacing" (-100) (100) 0;
1249			bg_colour = Expression "Background colour" 0;
1250			halign = Option "Horizontal alignment" valign_names 1;
1251			valign = Option "Vertical alignment" halign_names 1;
1252
1253			_result
1254				= (image_set_origin 0 0 @
1255					foldl1 (join_tb vshim.value bg_colour.expr halign.value) @
1256					map (foldl1 (join_lr hshim.value
1257						bg_colour.expr valign.value))) (to_list (to_list x));
1258		}
1259	}
1260}
1261
1262Image_tile_item = class
1263	Menupullright "Til_e" "tile an image across and down" {
1264	tile_widget default_type x = class
1265		_result {
1266		_vislevel = 3;
1267
1268		across = Expression "Tiles across" 2;
1269		down = Expression "Tiles down" 2;
1270		repeat = Option "Tile type"
1271			["Replicate", "Four-way mirror"] default_type;
1272
1273		_result
1274			= map_unary process x
1275		{
1276			process image
1277				= tile across down image, repeat == 0
1278				= tile across down image''
1279			{
1280				image' = insert image.width 0 (fliplr image) image;
1281				image'' = insert 0 image.height (fliptb image') image';
1282			}
1283		}
1284	}
1285
1286	Replicate_item = class
1287		Menuaction "_Replicate" "replicate image across and down" {
1288		action x = tile_widget 0 x;
1289	}
1290
1291	Fourway_item = class
1292		Menuaction "_Four-way Mirror" "four-way mirror across and down" {
1293		action x = tile_widget 1 x;
1294	}
1295
1296	Chop_item = class
1297		Menuaction "_Chop Into Tiles" "slice an image into tiles" {
1298		action x = class
1299			_result {
1300			_vislevel = 3;
1301
1302			tile_width = Expression "Tile width" 100;
1303			tile_height = Expression "Tile height" 100;
1304			hoverlap = Expression "Horizontal overlap" 0;
1305			voverlap = Expression "Vertical overlap" 0;
1306
1307			_result
1308				= map_unary (Group @ map Group @ process) x
1309			{
1310				process x
1311					= imagearray_chop tile_width tile_height
1312						hoverlap voverlap x;
1313			}
1314		}
1315	}
1316}
1317
1318#separator
1319
1320Pattern_images_item = class
1321	Menupullright "_Patterns" "make a variety of useful patterns" {
1322	Grey_item = class
1323		Menuaction "Grey _Ramp" "make a smooth grey ramp" {
1324		action = class
1325			_result {
1326			_vislevel = 3;
1327
1328			nwidth = Expression "Image width (pixels)" 64;
1329			nheight = Expression "Image height (pixels)" 64;
1330			orientation = Option "Orientation" [
1331				"Horizontal",
1332				"Vertical"
1333			] 0;
1334			foption = Option "Format" ["8 bit", "float"] 0;
1335
1336			_result
1337				= Image im
1338			{
1339				gen
1340					= im_grey, foption == 0
1341					= im_fgrey;
1342				w = to_real nwidth;
1343				h = to_real nheight;
1344				im
1345					= gen w h, orientation == 0
1346					= rot90 (gen h w);
1347			}
1348		}
1349	}
1350
1351	Xy_item = class
1352		Menuaction "_XY Image"
1353			"make a two band image whose pixel values are their coordinates" {
1354		action = class
1355			_result {
1356			_vislevel = 3;
1357
1358			nwidth = Expression "Image width (pixels)" 64;
1359			nheight = Expression "Image height (pixels)" 64;
1360
1361			_result = Image (make_xy nwidth nheight);
1362		}
1363	}
1364
1365	Gaussian_item = class
1366		Menuaction "Gaussian _Noise" "make an image of gaussian noise" {
1367		action = class
1368			_result {
1369			_vislevel = 3;
1370
1371			nwidth = Expression "Image width (pixels)" 64;
1372			nheight = Expression "Image height (pixels)" 64;
1373			mean = Scale "Mean" 0 255 128;
1374			deviation = Scale "Deviation" 0 128 50;
1375
1376			_result = Image (im_gaussnoise (to_real nwidth) (to_real nheight)
1377				mean.value deviation.value);
1378		}
1379	}
1380
1381	Fractal_item = class
1382		Menuaction "_Fractal" "make a fractal image" {
1383		action = class
1384			_result {
1385			_vislevel = 3;
1386
1387			nsize = Expression "Image size (pixels)" 64;
1388			dimension = Scale "Dimension" 2.001 2.999 2.001;
1389
1390			_result = Image (im_fractsurf (to_real nsize) dimension.value);
1391		}
1392	}
1393
1394	Checkerboard_item = class
1395		Menuaction "_Checkerboard" "make a checkerboard image" {
1396		action = class
1397			_result {
1398			_vislevel = 3;
1399
1400			nwidth = Expression "Image width (pixels)" 64;
1401			nheight = Expression "Image height (pixels)" 64;
1402			hpsize = Expression "Horizontal patch size" 8;
1403			vpsize = Expression "Vertical patch size" 8;
1404			hpoffset = Expression "Horizontal patch offset" 0;
1405			vpoffset = Expression "Vertical patch offset" 0;
1406
1407			_result
1408				= Image (xstripes ^ ystripes)
1409			{
1410				pixels = make_xy nwidth nheight;
1411				xpixels = pixels?0 + to_real hpoffset;
1412				ypixels = pixels?1 + to_real vpoffset;
1413
1414				make_stripe pix swidth = pix % (swidth * 2) >= swidth;
1415
1416				xstripes = make_stripe xpixels (to_real hpsize);
1417				ystripes = make_stripe ypixels (to_real vpsize);
1418			}
1419		}
1420	}
1421
1422	Grid_item = class
1423		Menuaction "Gri_d" "make a grid" {
1424		action = class
1425			_result {
1426			_vislevel = 3;
1427
1428			nwidth = Expression "Image width (pixels)" 64;
1429			nheight = Expression "Image height (pixels)" 64;
1430			hspace = Expression "Horizontal line spacing" 8;
1431			vspace = Expression "Vertical line spacing" 8;
1432			thick = Expression "Line thickness" 1;
1433			hoff = Expression "Horizontal grid offset" 4;
1434			voff = Expression "Vertical grid offset" 4;
1435
1436			_result
1437				= Image (xstripes | ystripes)
1438			{
1439				pixels = make_xy nwidth nheight;
1440				xpixels = pixels?0 + to_real hoff;
1441				ypixels = pixels?1 + to_real voff;
1442
1443				make_stripe pix swidth = pix % swidth < to_real thick;
1444
1445				xstripes = make_stripe xpixels (to_real hspace);
1446				ystripes = make_stripe ypixels (to_real vspace);
1447			}
1448		}
1449	}
1450
1451	Text_item = class
1452		Menuaction "_Text" "make a bitmap of some text" {
1453		action = class
1454			_result {
1455			_vislevel = 3;
1456
1457			text = String "Text to paint" "<i>Hello</i> world!";
1458			font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
1459			wrap = Expression "Wrap text at" 500;
1460			align = Option "Alignment" [
1461				"Left",
1462				"Centre",
1463				"Right"
1464			] 0;
1465			dpi = Expression "DPI" 300;
1466
1467			_result = Image (im_text text.value font.value
1468				(to_real wrap) align.value (to_real dpi));
1469		}
1470	}
1471
1472	New_CIELAB_slice_item = class
1473		Menuaction "CIELAB _Slice" "make a slice through CIELAB space" {
1474		action = class
1475			_result {
1476			_vislevel = 3;
1477
1478			nsize = Expression "Image size (pixels)" 64;
1479			L = Scale "L value" 0 100 50;
1480
1481			_result = Image (lab_slice (to_real nsize) L.value);
1482		}
1483	}
1484
1485	sense_option = Option "Sense" [
1486		"Pass",
1487		"Reject"
1488	] 0;
1489
1490	build fn size
1491		= (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
1492			(im_create_fmask size size);
1493
1494	New_ideal_item = class
1495		Menupullright "_Ideal Fourier Mask"
1496			"make various ideal Fourier filter masks" {
1497		High_low_item = class
1498			Menuaction "_High or Low Pass"
1499				("make a mask image for a highpass/lowpass " ++
1500					"ideal Fourier filter") {
1501			action = class
1502				_result {
1503				_vislevel = 3;
1504
1505				nsize = Expression "Image size (pixels)" 64;
1506				sense = sense_option;
1507				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
1508
1509				_result
1510					= build param (to_real nsize)
1511				{
1512						param f = f sense.value fc.value 0 0 0 0;
1513				}
1514			}
1515		}
1516
1517		Ring_item = class
1518			Menuaction "_Ring Pass or Ring Reject"
1519				("make a mask image for an ring pass/reject " ++
1520				"ideal Fourier filter") {
1521			action = class
1522				_result {
1523				_vislevel = 3;
1524
1525				nsize = Expression "Image size (pixels)" 64;
1526				sense = sense_option;
1527				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
1528				rw = Scale "Ring width" 0.01 0.99 0.5;
1529
1530				_result
1531					= build param (to_real nsize)
1532				{
1533						param f = f (sense.value + 6) fc.value rw.value 0 0 0;
1534				}
1535			}
1536		}
1537
1538		Band_item = class
1539			Menuaction "_Band Pass or Band Reject"
1540				("make a mask image for a band pass/reject " ++
1541				"ideal Fourier filter") {
1542			action = class
1543				_result {
1544				_vislevel = 3;
1545
1546				nsize = Expression "Image size (pixels)" 64;
1547				sense = sense_option;
1548				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
1549				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
1550				r = Scale "Radius" 0.01 0.99 0.5;
1551
1552				_result
1553					= build param (to_real nsize)
1554				{
1555						param f = f (sense.value + 12) fcx.value fcy.value
1556							r.value 0 0;
1557				}
1558			}
1559		}
1560	}
1561
1562	New_gaussian_item = class
1563		Menupullright "_Gaussian Fourier Mask"
1564			"make various Gaussian Fourier filter masks" {
1565		High_low_item = class
1566			Menuaction "_High or Low Pass"
1567				("make a mask image for a highpass/lowpass " ++
1568					"Gaussian Fourier filter") {
1569			action = class
1570				_result {
1571				_vislevel = 3;
1572
1573				nsize = Expression "Image size (pixels)" 64;
1574				sense = sense_option;
1575				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
1576				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
1577
1578				_result
1579					= build param (to_real nsize)
1580				{
1581						param f = f (sense.value + 4) fc.value ac.value 0 0 0;
1582				}
1583			}
1584		}
1585
1586		Ring_item = class
1587			Menuaction "_Ring Pass or Ring Reject"
1588				("make a mask image for an ring pass/reject " ++
1589				"Gaussian Fourier filter") {
1590			action = class
1591				_result {
1592				_vislevel = 3;
1593
1594				nsize = Expression "Image size (pixels)" 64;
1595				sense = sense_option;
1596				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
1597				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
1598				rw = Scale "Ring width" 0.01 0.99 0.5;
1599
1600				_result
1601					= build param (to_real nsize)
1602				{
1603						param f = f (sense.value + 10) fc.value rw.value
1604							ac.value 0 0;
1605				}
1606			}
1607		}
1608
1609		Band_item = class
1610			Menuaction "_Band Pass or Band Reject"
1611				("make a mask image for a band pass/reject " ++
1612				"Gaussian Fourier filter") {
1613			action = class
1614				_result {
1615				_vislevel = 3;
1616
1617				nsize = Expression "Image size (pixels)" 64;
1618				sense = sense_option;
1619				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
1620				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
1621				r = Scale "Radius" 0.01 0.99 0.5;
1622				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
1623
1624				_result
1625					= build param (to_real nsize)
1626				{
1627						param f = f (sense.value + 16) fcx.value fcy.value
1628							r.value ac.value 0;
1629				}
1630			}
1631		}
1632	}
1633
1634	New_butterworth_item = class
1635		Menupullright "_Butterworth Fourier Mask"
1636			"make various Butterworth Fourier filter masks" {
1637		High_low_item = class
1638			Menuaction "_High or Low Pass"
1639				("make a mask image for a highpass/lowpass " ++
1640					"Butterworth Fourier filter") {
1641			action = class
1642				_result {
1643				_vislevel = 3;
1644
1645				nsize = Expression "Image size (pixels)" 64;
1646				sense = sense_option;
1647				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
1648				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
1649				order = Scale "Order" 1 10 2;
1650
1651				_result
1652					= build param (to_real nsize)
1653				{
1654						param f = f (sense.value + 2) order.value fc.value
1655							ac.value 0 0;
1656				}
1657			}
1658		}
1659
1660		Ring_item = class
1661			Menuaction "_Ring Pass or Ring Reject"
1662				("make a mask image for an ring pass/reject " ++
1663				"Butterworth Fourier filter") {
1664			action = class
1665				_result {
1666				_vislevel = 3;
1667
1668				nsize = Expression "Image size (pixels)" 64;
1669				sense = sense_option;
1670				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
1671				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
1672				rw = Scale "Ring width" 0.01 0.99 0.5;
1673				order = Scale "Order" 1 10 2;
1674
1675				_result
1676					= build param (to_real nsize)
1677				{
1678						param f = f (sense.value + 8) order.value fc.value
1679							rw.value ac.value 0;
1680				}
1681			}
1682		}
1683
1684		Band_item = class
1685			Menuaction "_Band Pass or Band Reject"
1686				("make a mask image for a band pass/reject " ++
1687				"Butterworth Fourier filter") {
1688			action = class
1689				_result {
1690				_vislevel = 3;
1691
1692				nsize = Expression "Image size (pixels)" 64;
1693				sense = sense_option;
1694				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
1695				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
1696				r = Scale "Radius" 0.01 0.99 0.5;
1697				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
1698				order = Scale "Order" 1 10 2;
1699
1700				_result
1701					= build param (to_real nsize)
1702				{
1703						param f = f (sense.value + 14) order.value fcx.value
1704							fcy.value r.value ac.value;
1705				}
1706			}
1707		}
1708	}
1709}
1710
1711Test_images_item = class
1712	Menupullright "Test I_mages" "make a variety of test images" {
1713	Eye_item = class
1714		Menuaction "_Spatial Response"
1715			"image for testing the eye's spatial response" {
1716		action = class
1717			_result {
1718			_vislevel = 3;
1719
1720			nwidth = Expression "Image width (pixels)" 64;
1721			nheight = Expression "Image height (pixels)" 64;
1722			factor = Scale "Factor" 0.001 1 0.2;
1723
1724			_result = Image (im_eye (to_real nwidth) (to_real nheight)
1725				factor.value);
1726		}
1727	}
1728
1729	Zone_plate = class
1730		Menuaction "_Zone Plate" "make a zone plate" {
1731		action = class
1732			_result {
1733			_vislevel = 3;
1734
1735			nsize = Expression "Image size (pixels)" 64;
1736
1737			_result = Image (im_zone (to_real nsize));
1738		}
1739	}
1740
1741	Frequency_test_chart_item = class
1742		Menuaction "_Frequency Testchart"
1743			"make a black/white frequency test pattern" {
1744		action = class
1745			_result {
1746			_vislevel = 3;
1747
1748			nwidth = Expression "Image width (pixels)" 64;
1749			sheight = Expression "Strip height (pixels)" 10;
1750			waves = Expression "Wavelengths" [64, 32, 16, 8, 4, 2];
1751
1752			_result
1753				= imagearray_assemble 0 0 (transpose [strips])
1754			{
1755				freq_slice wave = Image (sin (grey / wave) > 0);
1756				strips = map freq_slice waves.expr;
1757				grey = im_fgrey (to_real nwidth) (to_real sheight) *
1758					360 * (to_real nwidth);
1759			}
1760		}
1761	}
1762
1763	CRT_test_chart_item = class
1764		Menuaction "CRT _Phosphor Chart"
1765			"make an image for measuring phosphor colours" {
1766		action = class
1767			_result {
1768			_vislevel = 3;
1769
1770			brightness = Scale "Brightness" 0 255 200;
1771			psize = Expression "Patch size (pixels)" 32;
1772
1773			_result
1774				= Image (imagearray_assemble 0 0 [[green, red], [blue, white]])
1775			{
1776
1777				black = image_new (to_real psize) (to_real psize) 1
1778					Image_format.FLOAT Image_coding.NOCODING
1779					Image_type.B_W 0 0 0;
1780				notblack = black + brightness;
1781
1782				green = black ++ notblack ++ black;
1783				red = notblack ++ black ++ black;
1784				blue = black ++ black ++ notblack;
1785				white = notblack ++ notblack ++ notblack;
1786			}
1787		}
1788	}
1789
1790	Greyscale_chart_item = class
1791		Menuaction "_Greyscale" "make a greyscale" {
1792		action = class
1793			_result {
1794			_vislevel = 3;
1795
1796			pwidth = Expression "Patch width" 8;
1797			pheight = Expression "Patch height" 8;
1798			npatches = Expression "Number of patches" 16;
1799
1800			_result
1801				= Image (image_set_type Image_type.B_W
1802					(clip2fmt Image_format.UCHAR wedge))
1803			{
1804				wedge
1805					= 255 / (to_real npatches - 1) *
1806						(int) (strip?0 / to_real pwidth)
1807				{
1808					strip = make_xy (to_real pwidth * to_real npatches) pheight;
1809				}
1810			}
1811		}
1812	}
1813
1814	CMYK_test_chart_item = class
1815		Menuaction "_CMYK Wedges" "make a set of CMYK wedges" {
1816		action = class
1817			_result {
1818			_vislevel = 3;
1819
1820			pwidth = Expression "Patch width" 8;
1821			pheight = Expression "Patch height" 8;
1822			npatches = Expression "Number of patches" 16;
1823
1824			_result
1825				= Image (image_set_type Image_type.CMYK
1826					(clip2fmt Image_format.UCHAR strips))
1827			{
1828				wedge
1829					= 255 / (to_real npatches - 1) *
1830						(int) (strip?0 / to_real pwidth)
1831				{
1832					strip = make_xy (to_real pwidth * to_real npatches) pheight;
1833				}
1834
1835				black = wedge * 0;
1836
1837				C = wedge ++ black ++ black ++ black;
1838				M = black ++ wedge ++ black ++ black;
1839				Y = black ++ black ++ wedge ++ black;
1840				K = black ++ black ++ black ++ wedge;
1841
1842				strips = imagearray_assemble 0 0 [[C],[M],[Y],[K]];
1843			}
1844		}
1845	}
1846
1847    Colour_atlas_item = class
1848        Menuaction "_Colour Atlas"
1849            "make a grid of patches grouped around a colour" {
1850        action = class
1851            _result {
1852            _vislevel = 3;
1853
1854            start = Colour_picker "Lab" [50,0,0];
1855            nstep = Expression "Number of steps" 2;
1856            ssize = Expression "Step size" 2;
1857
1858            _result
1859                = Image (colour_transform_to (get_type start) im)
1860            {
1861                base = colour_transform_to Image_type.LAB start;
1862                step = to_real ssize;
1863                offset = to_real nstep * step;
1864                range c = [c - offset, c - offset + step ... c + offset];
1865
1866                mk_patch b a = image_new 8 8 3
1867                    Image_format.FLOAT Image_coding.NOCODING
1868                    Image_type.LAB (Vector [base?0, a, b]) 0 0;
1869
1870                mk_strip b = map (mk_patch b) (range base?1);
1871                mk_grid = map mk_strip (range base?2);
1872                im = imagearray_assemble 1 1 mk_grid;
1873            }
1874        }
1875    }
1876}
1877
1878