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