1Filter_conv_item = class
2	Menupullright "_Convolution" "various spatial convolution filters" {
3	/* Some useful masks.
4	 */
5	filter_blur = Matrix_con 9 0 [[1, 1, 1], [1, 1, 1], [1, 1, 1]];
6	filter_sharp = Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]];
7	filter_emboss = Matrix_con 1 128 [[-1, 0], [0, 1]];
8	filter_laplacian = Matrix_con 1 128
9		[[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]];
10	filter_sobel = Matrix_con 1 128 [[1, 2, 1], [0, 0, 0], [-1, -2, -1]];
11	filter_lindet = Matrix_con 1 0 [[1, 1, 1], [-2, -2, -2], [1, 1, 1]];
12
13	Blur_item = class
14		Menuaction "_Blur" "3x3 blur of image" {
15		action x = map_unary (conv filter_blur) x;
16	}
17
18	Sharpen_item = class
19		Menuaction "_Sharpen" "3x3 sharpen of image" {
20		action x = map_unary (conv filter_sharp) x;
21	}
22
23	Emboss_item = class
24		Menuaction "_Emboss" "1 pixel displace emboss" {
25		action x = map_unary (conv filter_emboss) x;
26	}
27
28	Laplacian_item = class
29		Menuaction "_Laplacian" "3x3 laplacian edge detect" {
30		action x = map_unary (conv filter_laplacian) x;
31	}
32
33	Sobel_item = class
34		Menuaction "So_bel" "3x3 Sobel edge detect" {
35		action x
36			= map_unary sobel x
37		{
38			sobel im
39				= abs (a - 128) + abs (b - 128)
40			{
41				a = conv filter_sobel im;
42				b = conv (rot270 filter_sobel) im;
43			}
44		}
45	}
46
47/* 3x3 line detect of image
48diagonals should be scaled down by root(2) I guess
49Kirk
50*/
51	Linedet_item = class
52		Menuaction "Li_ne Detect" "3x3 line detect" {
53		action x
54			= map_unary lindet x
55		{
56			lindet im
57				= foldr1 max_pair images
58			{
59				masks = take 4 (iterate rot45 filter_lindet);
60				images = map (converse conv im) masks;
61			}
62		}
63	}
64
65	Usharp_item = class
66		Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
67		action x = class
68			_result {
69			_vislevel = 3;
70
71			size = Option "Radius" [
72				"3 pixels",
73				"5 pixels",
74				"7 pixels",
75				"9 pixels",
76				"11 pixels",
77				"51 pixels"
78			] 0;
79
80			st = Scale "Smoothness threshold" 0 5 2;
81			bm = Scale "Brighten by at most" 1 50 10;
82			dm = Scale "Darken by at most" 1 50 20;
83			fs = Scale "Sharpen flat areas by" 0 5 0.5;
84			js = Scale "Sharpen jaggy areas by" 0 5 1;
85
86			_result
87				= map_unary process x
88			{
89				process in
90					= Image in'''
91				{
92					in' = colour_transform_to Image_type.LABS in.value;
93					in'' = sharpen [3, 5, 7, 9, 11, 51]?size st bm dm fs js in';
94					in''' = colour_transform_to (get_type in) in'';
95				}
96			}
97		}
98	}
99
100	sep1 = Menuseparator;
101
102	Custom_blur_item = class
103		Menuaction "Custom B_lur / Sharpen"
104			"blur or sharpen with tuneable parameters" {
105		action x = class
106			_result {
107			_vislevel = 3;
108
109			type = Option "Type" ["Blur", "Sharpen"] 0;
110			r = Scale "Radius" 1 100 1;
111			fac = Scale "Amount" 0 1 1;
112			layers = Scale "Layers" 1 100 10;
113			shape = Option "Mask shape" [
114				"Square",
115				"Gaussian"
116			] 0;
117			prec = Option "Precision" ["Int", "Float", "Approximate"] 0;
118
119			_result
120				= map_unary process x
121			{
122				process in
123					= clip2fmt blur.format proc
124				{
125					mask
126						= matrix_blur r.value, shape.value == 0
127						= matrix_gaussian_blur r.value;
128					blur = [convsep, convsepf, aconvsep layers]?prec mask in;
129					proc
130						= in + fac * (in - blur), type == 1
131						= blur * fac + in * (1 - fac);
132				}
133			}
134		}
135	}
136
137	Custom_conv_item = class
138		Menuaction "Custom C_onvolution"
139			"convolution filter with tuneable parameters" {
140		action x = class
141			_result {
142			_vislevel = 3;
143
144			matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
145			separable
146				= Toggle "Seperable convolution" false,
147					matrix.width == 1 || matrix.height == 1
148				= false;
149			type = Option "Convolution type" ["Int", "Float"] 0;
150			rotate = Option "Rotate" [
151				"Don't rotate",
152				"4 x 45 degrees",
153				"8 x 45 degrees",
154				"2 x 90 degrees"
155			] 0;
156
157			_result
158				= map_unary process x
159			{
160				process in
161					= in.Image in'
162				{
163					conv_fn
164						= im_lindetect, !separable && type == 0 && rotate == 1
165						= im_compass, !separable && type == 0 && rotate == 2
166						= im_gradient, !separable && type == 0 && rotate == 3
167						= im_conv, !separable && type == 0
168						= im_convsep, separable && type == 0
169						= im_conv_f, !separable && type == 1
170						= im_convsep_f, separable && type == 1
171						= error "boink!";
172					in' = conv_fn in.value matrix;
173				}
174			}
175		}
176	}
177}
178
179Filter_rank_item = class
180	Menupullright "_Rank" "various rank filters" {
181	Median_item = class
182		Menuaction "_Median" "3x3 median rank filter" {
183		action x = map_unary (rank 3 3 4) x;
184	}
185
186	Image_rank_item = class
187		Menuaction "_Image Rank" "pixelwise rank a list or group of images" {
188		action x = class
189			_result {
190			_vislevel = 3;
191
192			select
193				= Expression "Rank" ((int) (guess_size / 2))
194			{
195				guess_size
196					= len x, is_list x
197					= len x.value, is_Group x
198					= 0;
199			}
200
201			// can't really iterate over groups ... since we allow a group
202			// argument
203			_result = rank_image select x;
204		}
205	}
206
207	Custom_rank_item = class
208		Menuaction "Custom _Rank" "rank filter with tuneable parameters" {
209		action x = class
210			_result {
211			_vislevel = 3;
212
213			window_width = Expression "Window width" 3;
214			window_height = Expression "Window height" 3;
215			select = Expression "Rank"
216				((int) ((to_real window_width * to_real window_height) / 2));
217
218			_result
219				= map_unary process x
220			{
221				process in
222					= rank window_width window_height select in;
223			}
224		}
225	}
226}
227
228Filter_morphology_item = class
229	Menupullright "_Morphology" "various morphological filters" {
230	/* Some useful masks.
231	 */
232	mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]];
233	mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]];
234	mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
235	thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]];
236
237	Threshold_item = Select_item.Threshold_item;
238
239	sep1 = Menuseparator;
240
241	Dilate_item = class
242		Menupullright "_Dilate" "morphological dilate" {
243		Dilate8_item = class
244			Menuaction "_8-connected" "dilate with an 8-connected mask" {
245			action x = map_unary (dilate mask8) x;
246		}
247
248		Dilate4_item = class
249			Menuaction "_4-connected" "dilate with a 4-connected mask" {
250			action x = map_unary (dilate mask4) x;
251		}
252	}
253
254	Erode_item = class
255		Menupullright "_Erode" "morphological erode" {
256		Erode8_item = class
257			Menuaction "_8-connected" "erode with an 8-connected mask" {
258			action x = map_unary (erode mask8) x;
259		}
260
261		Erode4_item = class
262			Menuaction "_4-connected" "erode with a 4-connected mask" {
263			action x = map_unary (erode mask4) x;
264		}
265	}
266
267	Custom_morph_item = class
268		Menuaction "Custom _Morphology"
269			"convolution morphological operator" {
270		action x = class
271			_result {
272			_vislevel = 3;
273
274			mask = mask4;
275			type = Option "Operation" ["Erode", "Dilate"] 1;
276			apply = Expression "Number of times to apply mask" 1;
277
278			_result
279				= map_unary morph x
280			{
281				morph image
282					= Image value'
283				{
284					fatmask = (iterate (dilate mask) mask)?(to_real apply - 1);
285
286					value'
287						= im_erode image.value fatmask, type.value == 0
288						= im_dilate image.value fatmask;
289				}
290			}
291		}
292	}
293
294	sep2 = Menuseparator;
295
296	Open_item = class
297		Menuaction "_Open" "open with an 8-connected mask" {
298		action x = map_unary (dilate mask8 @ erode mask8) x;
299	}
300
301	Close_item = class
302		Menuaction "_Close" "close with an 8-connected mask" {
303		action x = map_unary (erode mask8 @ dilate mask8) x;
304	}
305
306	Clean_item = class
307		Menuaction "C_lean" "remove 8-connected isolated points" {
308		action x
309			= map_unary clean x
310		{
311			clean x = x ^ erode mask1 x;
312		}
313	}
314
315	Thin_item = class
316		Menuaction "_Thin" "thin once" {
317		action x
318			= map_unary thinall x
319		{
320			masks = take 8 (iterate rot45 thin);
321			thin1 m x = x ^ erode m x;
322			thinall x = foldr thin1 x masks;
323		}
324	}
325
326}
327
328Filter_fourier_item = class
329	Menupullright "_Fourier" "various Fourier filters" {
330	preview_size = 64;
331
332	sense_option = Option "Sense" [
333		"Pass",
334		"Reject"
335	] 0;
336
337	// make a visualisation image
338	make_vis fn = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
339		(im_create_fmask preview_size preview_size);
340
341	// make the process function
342	process fn in
343		= (Image @ fn) (im_flt_image_freq in.value);
344
345	New_ideal_item = class
346		Menupullright "_Ideal" "various ideal Fourier filters" {
347		High_low_item = class
348			Menuaction "_High or Low Pass"
349				"highpass/lowpass ideal Fourier filter" {
350			action x = class
351				_result {
352				_vislevel = 3;
353
354				sense = sense_option;
355				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
356
357				// call a freq func with our parameters
358				_params f = f sense.value fc.value 0 0 0 0;
359
360				visualize_mask = make_vis _params;
361
362				_result = map_unary (process _params) x;
363			}
364		}
365
366		Ring_item = class
367			Menuaction "_Ring Pass or Ring Reject"
368				"ring pass/reject ideal Fourier filter" {
369			action x = class
370				_result {
371				_vislevel = 3;
372
373				sense = sense_option;
374				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
375				rw = Scale "Ring width" 0.01 0.99 0.5;
376
377				// call a freq func with our parameters
378				_params f = f (sense.value + 6) fc.value
379					rw.value 0 0 0;
380
381				visualize_mask = make_vis _params;
382
383				_result = map_unary (process _params) x;
384			}
385		}
386
387		Band_item = class
388			Menuaction "_Band Pass or Band Reject"
389				"band pass/reject ideal Fourier filter" {
390			action x = class
391				_result {
392				_vislevel = 3;
393
394				sense = sense_option;
395				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
396				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
397				r = Scale "Radius" 0.01 0.99 0.5;
398
399				// call a freq func with our parameters
400				_params f = f (sense.value + 12) fcx.value fcy.value
401					r.value 0 0;
402
403				visualize_mask = make_vis _params;
404
405				_result = map_unary (process _params) x;
406			}
407		}
408	}
409
410	New_gaussian_item = class
411		Menupullright "_Gaussian" "various Gaussian Fourier filters" {
412		High_low_item = class
413			Menuaction "_High or Low Pass"
414				"highpass/lowpass Gaussian Fourier filter" {
415			action x = class
416				_result {
417				_vislevel = 3;
418
419				sense = sense_option;
420				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
421				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
422
423				// call a freq func with our parameters
424				_params f = f (sense.value + 4) fc.value
425					ac.value 0 0 0;
426
427				visualize_mask = make_vis _params;
428
429				_result = map_unary (process _params) x;
430			}
431		}
432
433		Ring_item = class
434			Menuaction "_Ring Pass or Ring Reject"
435				"ring pass/reject Gaussian Fourier filter" {
436			action x = class
437				_result {
438				_vislevel = 3;
439
440				sense = sense_option;
441				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
442				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
443				rw = Scale "Ring width" 0.01 0.99 0.5;
444
445				// call a freq func with our parameters
446				_params f = f (sense.value + 10) fc.value
447					rw.value ac.value 0 0;
448
449				visualize_mask = make_vis _params;
450
451				_result = map_unary (process _params) x;
452			}
453		}
454
455		Band_item = class
456			Menuaction "_Band Pass or Band Reject"
457				"band pass/reject Gaussian Fourier filter" {
458			action x = class
459				_result {
460				_vislevel = 3;
461
462				sense = sense_option;
463				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
464				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
465				r = Scale "Radius" 0.01 0.99 0.5;
466				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
467
468				// call a freq func with our parameters
469				_params f = f (sense.value + 16) fcx.value fcy.value
470					r.value ac.value 0;
471
472				visualize_mask = make_vis _params;
473
474				_result = map_unary (process _params) x;
475			}
476		}
477	}
478
479	New_butterworth_item = class
480		Menupullright "_Butterworth"
481			"various Butterworth Fourier filters" {
482		High_low_item = class
483			Menuaction "_High or Low Pass"
484				"highpass/lowpass Butterworth Fourier filter" {
485			action x = class
486				_result {
487				_vislevel = 3;
488
489				sense = sense_option;
490				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
491				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
492				o = Scale "Order" 1 10 2;
493
494				// call a freq func with our parameters
495				_params f = f (sense.value + 2) o.value fc.value ac.value
496						0 0;
497
498				visualize_mask = make_vis _params;
499
500				_result = map_unary (process _params) x;
501			}
502		}
503
504		Ring_item = class
505			Menuaction "_Ring Pass or Ring Reject"
506				"ring pass/reject Butterworth Fourier filter" {
507			action x = class
508				_result {
509				_vislevel = 3;
510
511				sense = sense_option;
512				fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
513				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
514				rw = Scale "Ring width" 0.01 0.99 0.5;
515				o = Scale "Order" 1 10 2;
516
517				// call a freq func with our parameters
518				_params f = f (sense.value + 8) o.value fc.value rw.value
519					ac.value 0;
520
521				visualize_mask = make_vis _params;
522
523				_result = map_unary (process _params) x;
524			}
525		}
526
527		Band_item = class
528			Menuaction "_Band Pass or Band Reject"
529				"band pass/reject Butterworth Fourier filter" {
530			action x = class
531				_result {
532				_vislevel = 3;
533
534				sense = sense_option;
535				fcx = Scale "Horizontal frequency cutoff" 0.01 0.99 0.5;
536				fcy = Scale "Vertical frequency cutoff" 0.01 0.99 0.5;
537				r = Scale "Radius" 0.01 0.99 0.5;
538				ac = Scale "Amplitude cutoff" 0.01 0.99 0.5;
539				o = Scale "Order" 1 10 2;
540
541				// call a freq func with our parameters
542				_params f = f (sense.value + 14) o.value fcx.value fcy.value
543						r.value ac.value;
544
545				visualize_mask = make_vis _params;
546
547				_result = map_unary (process _params) x;
548			}
549		}
550	}
551}
552
553Filter_enhance_item = class
554	Menupullright "_Enhance" "various enhancement filters" {
555	Falsecolour_item = class
556		Menuaction "_False Colour" "false colour a mono image" {
557		action x = class
558			_result {
559			_vislevel = 3;
560
561			o = Scale "Offset" (-255) 255 0;
562			clip = Toggle "Clip colour range" false;
563
564			_result
565				= map_unary process x
566			{
567				process im
568					= falsecolour mono''
569				{
570					mono = colour_transform_to Image_type.B_W im;
571					mono' = mono + o;
572					mono''
573						= (unsigned char) mono', clip
574						= (unsigned char) (mono' & 0xff);
575				}
576			}
577		}
578	}
579
580	Statistical_diff_item = class
581		Menuaction "_Statistical Difference"
582			"statistical difference of an image" {
583		action x = class
584			_result {
585			_vislevel = 3;
586
587			wsize = Expression "Window size" 11;
588			tmean = Expression "Target mean" 128;
589			mean_weight = Scale "Mean weight" 0 1 0.8;
590			tdev = Expression "Target deviation" 50;
591			dev_weight = Scale "Deviation weight" 0 1 0.8;
592			border = Toggle "Output image matches input image in size" true;
593
594			_result
595				= map_unary process x
596			{
597				process in
598					= Image in''
599				{
600					in' = colour_transform_to Image_type.B_W in.value;
601					fn
602						= im_stdif, border
603						= im_stdif_raw;
604					in'' = fn in'
605						mean_weight.value tmean.expr
606						dev_weight.value tdev.expr
607						wsize.expr wsize.expr;
608				}
609			}
610		}
611	}
612
613	Hist_equal_item = class
614		Menupullright "_Equalise Histogram" "equalise contrast" {
615		Global_item = class
616			Menuaction "_Global" "equalise contrast globally" {
617			action x = map_unary hist_equalize x;
618		}
619
620		Local_item = class
621			Menuaction "_Local" "equalise contrast within a roving window" {
622			action x = class
623				_result {
624				_vislevel = 3;
625
626				window_width = Expression "Window width" 20;
627				window_height = Expression "Window height" 20;
628
629				_result
630					= map_unary process x
631				{
632					process in
633						= hist_equalize_local
634							window_width.expr window_height.expr in;
635				}
636			}
637		}
638	}
639}
640
641Filter_correlate_item = class
642	Menupullright "Spatial _Correlation" "calculate correlation surfaces" {
643	Correlate_item = class
644		Menuaction "_Correlate" "calculate correlation coefficient" {
645		action a b
646			= map_binary corr a b
647		{
648			corr a b
649				= correlate a b,
650					a.width <= b.width && a.height <= b.height
651				= correlate b a;
652		}
653	}
654
655	Correlate_fast_item = class
656		Menuaction "_Simple Difference"
657			"calculate sum of squares of differences" {
658		action a b
659			= map_binary corr a b
660		{
661			corr a b
662				= correlate_fast a b,
663					a.width <= b.width && a.height <= b.height
664				= correlate_fast b a;
665		}
666	}
667}
668
669Filter_hough_item = class
670	Menupullright "_Hough Transform" "transform to parameter space" {
671	Line_item = class
672		Menuaction "_Line" "find straight line Hough transform" {
673		action a = class
674			_result {
675			_vislevel = 3;
676
677			pspace_width = Expression "Parameter space width" 64;
678			pspace_height = Expression "Parameter space height" 64;
679
680			_result
681				= map_unary line a
682			{
683				line a
684					= hough_line
685						(to_real pspace_width) (to_real pspace_height) a;
686			}
687		}
688	}
689
690	Circle_item = class
691		Menuaction "_Circle" "find circle Hough transform" {
692		action a = class
693			_result {
694			_vislevel = 3;
695
696			scale = Expression "Scale down parameter space by" 10;
697			min_radius = Expression "Minimum radius" 10;
698			max_radius = Expression "Maximum radius" 30;
699
700			_result
701				= map_unary circle a
702			{
703				circle a
704					= hough_circle (to_real scale) (to_real min_radius)
705						(to_real max_radius) a;
706			}
707		}
708	}
709}
710
711Filter_coordinate_item = class
712	Menupullright "_Coordinate Transform" "various coordinate transforms" {
713	// run a function which wants a complex arg on a non-complex two-band
714	// image
715	run_cmplx fn x
716		= re x' ++ im x'
717	{
718		x' = fn (x?0, x?1);
719	}
720
721	Polar_item = class
722		Menuaction "_Polar" "transform to polar coordinates" {
723		action a = class
724			_result {
725			_vislevel = 3;
726
727			interp = Interpolate_picker Interpolate_type.BILINEAR;
728
729			_result
730				= map_unary to_polar a
731			{
732				to_polar im
733					= mapim interp.value map' im
734				{
735					// xy image, origin in the centre, scaled to fit image to
736					// a circle
737					xy = make_xy im.width im.height;
738					xy' = xy - Vector [im.width / 2, im.height / 2];
739					scale = min [im.width, im.height] / im.width;
740					xy'' = 2 * xy' / scale;
741
742					// to polar, scale vertical axis to 360 degrees
743					map = run_cmplx polar xy'';
744					map' = map * Vector [1, im.height / 360];
745				}
746			}
747		}
748	}
749
750	Rectangular_item = class
751		Menuaction "_Rectangular" "transform to rectangular coordinates" {
752		action a = class
753			_result {
754			_vislevel = 3;
755
756			interp = Interpolate_picker Interpolate_type.BILINEAR;
757
758			_result
759				= map_unary to_rect a
760			{
761				to_rect im
762					= mapim interp.value map'' im
763				{
764					// xy image, vertical scaled to 360 degrees
765					xy = make_xy im.width im.height;
766					xy' = xy * Vector [1, 360 / im.height];
767
768					// to rect, scale to image rect
769					map = run_cmplx rectangular xy';
770					scale = min [im.width, im.height] / im.width;
771					map' = map * scale / 2;
772
773					map'' = map' + Vector [im.width / 2, im.height / 2];
774				}
775			}
776		}
777	}
778}
779
780#separator
781
782Filter_tilt_item = class
783	Menupullright "Ti_lt Brightness" "tilt brightness" {
784	Left_right_item = class
785		Menuaction "_Left to Right" "linear left-right brighten" {
786		action x = class
787			_result {
788			_vislevel = 3;
789
790			tilt = Scale "Left-right tilt" (-1) 1 0;
791
792			_result
793					= map_unary tilt_lr x
794			{
795				tilt_lr image
796					= image * scale
797				{
798					ramp = im_fgrey image.width image.height;
799					scale = (ramp - 0.5) * tilt + 1;
800				}
801			}
802		}
803	}
804
805	Top_bottom_item = class
806		Menuaction "_Top to Bottom" "linear top-bottom brighten" {
807		action x = class
808			_result {
809			_vislevel = 3;
810
811			tilt = Scale "Top-bottom tilt" (-1) 1 0;
812
813			_result
814				= map_unary tilt_tb x
815			{
816				tilt_tb image
817					= image * scale
818				{
819					ramp = rot90
820						(im_fgrey image.height image.width);
821					scale = (ramp - 0.5) * tilt + 1;
822				}
823			}
824		}
825	}
826
827	sep1 = Menuseparator;
828
829	Left_right_cos_item = class
830		Menuaction "Cosine Left-_right" "cosine left-right brighten" {
831		action x = class
832			_result {
833			_vislevel = 3;
834
835			tilt = Scale "Left-right tilt" (-1) 1 0;
836			shift = Scale "Shift by" (-1) 1 0;
837
838			_result
839					= map_unary tilt_lr x
840			{
841				tilt_lr image
842					= image * scale
843				{
844					ramp = im_fgrey image.width image.height - 0.5 -
845							shift.value;
846					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
847				}
848			}
849		}
850	}
851
852	Top_bottom_cos_item = class
853		Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
854		action x = class
855			_result {
856			_vislevel = 3;
857
858			tilt = Scale "Top-bottom tilt" (-1) 1 0;
859			shift = Scale "Shift by" (-1) 1 0;
860
861			_result
862				= map_unary tilt_tb x
863			{
864				tilt_tb image
865					= image * scale
866				{
867					ramp = rot90 (im_fgrey image.height image.width) - 0.5 -
868							shift.value;
869					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
870				}
871			}
872		}
873	}
874
875	sep2 = Menuseparator;
876
877	Circular_item = class
878		Menuaction "_Circular" "circular brighten" {
879		action x = class
880			_result {
881			_vislevel = 3;
882
883			tilt = Scale "Tilt" (-1) 1 0;
884			hshift = Scale "Horizontal shift by" (-1) 1 0;
885			vshift = Scale "Vertical shift by" (-1) 1 0;
886
887			_result
888				= map_unary tilt_tb x
889			{
890				tilt_tb image
891					= image * scale
892				{
893					hramp = im_fgrey image.width image.height - 0.5 -
894						hshift.value;
895					vramp = rot90 (im_fgrey image.height image.width) - 0.5 -
896						vshift.value;
897					ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
898					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
899				}
900			}
901		}
902	}
903}
904
905Filter_blend_item = class
906	Menupullright "_Blend" "blend objects together" {
907	Scale_blend_item = class
908		Menuaction "_Scale" "blend two objects together with a scale" {
909		action a b = class
910			_result {
911			_vislevel = 3;
912
913			p = Scale "Blend position" 0 1 0.5;
914
915			_result
916				= map_binary process a b
917			{
918				process im1 im2 = im1 * (1 - p.value) + im2 * p.value;
919			}
920		}
921	}
922
923	Image_blend_item = class
924		Menuaction "_Image" "use an image to blend two objects" {
925		action a b c = class
926			_result {
927			_vislevel = 3;
928
929			i = Toggle "Invert mask" false;
930
931			_result
932				= map_trinary process a b c
933			{
934				process a b c
935					= blend condition in1 in2, !i
936					= blend (invert condition) in1 in2
937				{
938					compare a b
939						// prefer image as the condition
940						= false,
941							!has_image a && has_image b
942						// prefer mono images as the condition
943						= false,
944							has_bands a && has_bands b &&
945							get_bands a > 1 && get_bands b == 1
946						// prefer uchar as the condition
947						= false,
948							has_format a && has_format b &&
949							get_format a > Image_format.UCHAR &&
950								get_format b == Image_format.UCHAR
951						= true;
952					[condition, in1, in2] = sortc compare [a, b, c];
953				}
954			}
955		}
956	}
957
958	Line_blend_item = class
959		Menuaction "_Along Line"
960			"blend between image a and image b along a line" {
961		action a b = class
962			_result {
963			_vislevel = 3;
964
965			orientation = Option "Orientation" [
966				"Left to Right",
967				"Top to Bottom"
968			] 0;
969			blend_position = Scale "Blend position" 0 1 0.5;
970			blend_width = Scale "Blend width" 0 1 0.05;
971
972			_result
973				= map_binary process a b
974            {
975                process a b
976					= blend (Image condition) b a
977                {
978					output_width = max_pair a.width b.width;
979					output_height = max_pair a.height b.height;
980					range
981						= output_width, orientation == 0
982						= output_height;
983					blend_position'
984						= floor (range * blend_position.value);
985					blend_width'
986						= 1, blend_width.value == 0
987						= floor (range * blend_width.value);
988                   	start = blend_position' - blend_width' / 2;
989
990					background = (make_xy output_width output_height) >=
991						blend_position';
992                   	ramp
993						= im_grey blend_width' output_height, orientation == 0
994                        = rot90 (im_grey blend_width' output_width);
995					condition
996						= insert_noexpand start 0 ramp background?0,
997							orientation == 0
998						= insert_noexpand 0 start ramp background?1;
999               	}
1000			}
1001		}
1002	}
1003
1004	Blend_alpha_item = class
1005		Menuaction "_Alpha" "blend images with optional alpha channels" {
1006		// usage: layerit foreground background
1007		// input images must be either 1 or 3 bands, optionally + 1 band
1008		// which is used as the alpha channel
1009		// rich lott
1010
1011		scale_mask im opacity
1012			= (unsigned char) (to_real opacity / 255 * im);
1013
1014		// to mono
1015		intensity = colour_transform_to Image_type.B_W;
1016
1017		// All the blend functions
1018		// I am grateful to this page
1019		// http://www.pegtop.net/delphi/blendmodes/
1020		// for most of the formulae.
1021
1022		blend_normal mask opacity fg bg
1023			= blend (scale_mask mask opacity) fg bg;
1024
1025		blend_iflighter mask opacity fg bg
1026			= blend (if fg' > bg' then mask' else 0) fg bg
1027		{
1028			fg' = intensity fg;
1029			bg' = intensity bg;
1030			mask' = scale_mask mask opacity ;
1031		}
1032
1033		blend_ifdarker mask opacity fg bg
1034			= blend (if fg' < bg' then mask' else 0) fg bg
1035		{
1036			fg' = intensity fg ;
1037			bg' = intensity bg ;
1038			mask' = scale_mask mask opacity ;
1039		}
1040
1041		blend_multiply mask opacity fg bg
1042			= blend (scale_mask mask opacity) fg' bg
1043		{
1044			fg' = fg / 255 * bg;
1045		}
1046
1047		blend_add mask opacity fg bg
1048			= blend mask fg' bg
1049		{
1050			fg' = opacity / 255 * fg + bg;
1051		}
1052
1053		blend_subtract mask opacity fg bg
1054			= blend mask fg' bg
1055		{
1056			fg' = bg - opacity / 255 * fg;
1057		}
1058
1059		blend_screen mask opacity fg bg
1060			= blend mask fg' bg
1061		{
1062			fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255;
1063		}
1064
1065		blend_burn mask opacity fg bg
1066			= blend mask fg'' bg
1067		{
1068			// fades to white which has no effect.
1069			fg' = (255 - opacity) + opacity * fg / 255;
1070			fg'' = 255 - 255 * (255 - bg) / fg';
1071		}
1072
1073		blend_softlight mask opacity fg bg
1074			= blend mask' fg' bg
1075		{
1076			mask' = scale_mask mask opacity;
1077			fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255;
1078		}
1079
1080		blend_hardlight mask opacity fg bg
1081			= blend mask' fg' bg
1082		{
1083			mask' = scale_mask mask opacity;
1084			fg'
1085				= 2 / 255 * fg * bg, bg < 129
1086				= 255 - 2 * (255 - bg) * (255 - fg) / 255;
1087		}
1088
1089		blend_lighten mask opacity fg bg
1090			= blend mask' fg' bg
1091		{
1092			mask' = scale_mask mask opacity;
1093			fg' = if bg < fg then fg else bg;
1094		}
1095
1096		blend_darken mask opacity fg bg
1097			= blend mask' fg' bg
1098		{
1099			mask' = scale_mask mask opacity;
1100			fg' = if bg > fg then fg else bg;
1101		}
1102
1103		blend_dodge mask opacity fg bg
1104			= blend mask fg'' bg
1105		{
1106			// one added to avoid divide by zero
1107			fg' = 1 + 255 - (opacity / 255 * fg);
1108			fg'' = bg * 255 / fg';
1109		}
1110
1111		blend_reflect mask opacity fg bg
1112			= blend mask' fg' bg
1113		{
1114			mask' = scale_mask mask opacity;
1115			fg' = bg * bg / (255 - fg);
1116		}
1117
1118		blend_freeze mask opacity fg bg
1119			= blend mask' fg' bg
1120		{
1121			mask' = scale_mask mask opacity;
1122			fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg);
1123		}
1124
1125		blend_or mask opacity fg bg
1126			= bg | (unsigned char) fg'
1127		{
1128			mask' = scale_mask mask opacity;
1129			fg' = fg * mask' / 255;
1130		}
1131
1132		blend_and mask opacity fg bg
1133			= bg & (unsigned char) fg'
1134		{
1135			mask' = scale_mask mask opacity;
1136			fg' = fg * mask' / 255;
1137		}
1138
1139		// blend types
1140		NORMAL = 0;
1141		IFLIGHTER = 1;
1142		IFDARKER = 2;
1143		MULTIPLY = 3;
1144		ADD = 4;
1145		SUBTRACT = 5;
1146		SCREEN = 6;
1147		BURN = 7;
1148		DODGE = 8;
1149		HARDLIGHT = 9;
1150		SOFTLIGHT = 10;
1151		LIGHTEN = 11;
1152		DARKEN = 12;
1153		REFLECT = 13;
1154		FREEZE = 14;
1155		OR = 15;
1156		AND = 16;
1157
1158		// names we show the user for blend types
1159		names = Enum [
1160			_ "Normal" => NORMAL,
1161			_ "If Lighter" => IFLIGHTER,
1162			_ "If Darker" => IFDARKER,
1163			_ "Multiply" => MULTIPLY,
1164			_ "Add" => ADD,
1165			_ "Subtract" => SUBTRACT,
1166			_ "Screen" => SCREEN,
1167			_ "Burn" => BURN,
1168			_ "Soft Light" => SOFTLIGHT,
1169			_ "Hard Light" => HARDLIGHT,
1170			_ "Lighten" => LIGHTEN,
1171			_ "Darken" => DARKEN,
1172			_ "Dodge" => DODGE,
1173			_ "Reflect" => REFLECT,
1174			_ "Freeze" => FREEZE,
1175			_ "Bitwise OR" => OR,
1176			_ "Bitwise AND" => AND
1177		];
1178
1179		// functions we call for each blend type
1180		actions = Table [
1181			[NORMAL, blend_normal],
1182			[IFLIGHTER, blend_iflighter],
1183			[IFDARKER, blend_ifdarker],
1184			[MULTIPLY, blend_multiply],
1185			[ADD, blend_add],
1186			[SUBTRACT, blend_subtract],
1187			[SCREEN, blend_screen],
1188			[BURN, blend_burn],
1189			[SOFTLIGHT, blend_softlight],
1190			[HARDLIGHT, blend_hardlight],
1191			[LIGHTEN, blend_lighten],
1192			[DARKEN, blend_darken],
1193			[DODGE, blend_dodge],
1194			[REFLECT, blend_reflect],
1195			[FREEZE, blend_freeze],
1196			[OR, blend_or],
1197			[AND, blend_and]
1198		];
1199
1200		// make sure im has an alpha channel (set opaque if it hasn't)
1201		put_alpha im
1202			= im, im.bands == 4 || im.bands == 2
1203			= im ++ 255;
1204
1205		// make sure im has no alpha channel
1206		lose_alpha im
1207			= extract_bands 0 3 im, im.bands == 4
1208			= im?0, im.bands == 2
1209			= im;
1210
1211		// does im have al alpha channel?
1212		has_alpha im = im.bands == 2 || im.bands == 4;
1213
1214		// get the alpha (set opaque if no alpha)
1215		get_alpha img
1216			= img'?3, img.bands == 4
1217			= img'?1
1218		{
1219			img' = put_alpha img;
1220		}
1221
1222		// add an alpha ... cast the alpha image to match the main image
1223		append_alpha im alpha
1224			= im ++ clip2fmt im.format alpha;
1225
1226		// makes fg the same size as bg, displaced with u, v pixel offset
1227		moveit fg bg u v
1228			= insert_noexpand u v fg bg'
1229		{
1230			bg' = image_new bg.width bg.height
1231				fg.bands fg.format fg.coding fg.type 0 0 0;
1232		}
1233
1234		action bg fg = class
1235			_value {
1236			_vislevel = 3;
1237
1238			method = Option_enum "Blend mode" names "Normal";
1239			opacity = Scale "Opacity" 0 255 255;
1240			hmove = Scale "Horizontal move by" (-bg.width) (bg.width) 0;
1241			vmove = Scale "Vertical move by" (-bg.height) (bg.height) 0;
1242
1243			_value
1244				= append_alpha blended merged_alpha, has_alpha bg
1245				= blended
1246			{
1247				// displace and resize fg (need to displace alpha too)
1248				fg' = moveit (put_alpha fg) bg hmove vmove;
1249
1250				// transform to sRGB
1251				fg'' = colour_transform_to Image_type.sRGB (lose_alpha fg');
1252				bg' = colour_transform_to Image_type.sRGB (lose_alpha bg);
1253
1254				// alphas merged
1255				merged_alpha = get_alpha bg | get_alpha fg';
1256
1257				// blend together
1258				blended = (actions.lookup 0 1 method.value_thing)
1259					(get_alpha fg') opacity.value fg'' bg';
1260			}
1261		}
1262	}
1263}
1264
1265Filter_overlay_header_item = class
1266	Menuaction "_Overlay"
1267		"make a colour overlay of two monochrome images" {
1268	action  a b = class
1269		_result {
1270		_vislevel = 3;
1271
1272		colour = Option "Colour overlay as" [
1273			_ "Green over Red",
1274			_ "Blue over Red",
1275			_ "Red over Green",
1276			_ "Red over Blue",
1277			_ "Blue over Green",
1278			_ "Green over Blue"
1279		] 0;
1280
1281		_result
1282			= map_binary overlay a b
1283		{
1284			overlay a b
1285				= image_set_type Image_type.sRGB
1286					[(a' ++ b' ++ 0),
1287						(a' ++ 0 ++ b'),
1288						(b' ++ a' ++ 0),
1289						(b' ++ 0 ++ a'),
1290						(0 ++ a' ++ b'),
1291						(0 ++ b' ++ a')]?colour
1292			{
1293				a' = colour_transform_to Image_type.B_W a;
1294				b' = colour_transform_to Image_type.B_W b;
1295			}
1296		}
1297	}
1298}
1299
1300Filter_colourize_item = class
1301	Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
1302	action a b = class
1303		_result {
1304		_vislevel = 3;
1305
1306		tint = Scale "Tint" 0 1 0.6;
1307
1308		_result
1309			= map_binary tintit a b
1310		{
1311			tintit a b
1312				= colour_transform_to (get_type colour) colourized'
1313			{
1314				// get the mono thing first
1315				[mono, colour] =
1316					sortc (const (is_colour_type @ get_type)) [a, b];
1317
1318				colour' = tint * colour_transform_to Image_type.LAB colour;
1319				mono' = colour_transform_to Image_type.B_W mono;
1320				colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2;
1321				colourized' = image_set_type Image_type.LAB colourized;
1322			}
1323		}
1324	}
1325}
1326
1327Filter_browse_multiband_item = class
1328	Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" {
1329	Bandwise_item = class
1330		Menuaction "B_andwise" "browse through the bands of a multiband image" {
1331		action image = class
1332        	_result {
1333			_vislevel = 3;
1334
1335        	band = Scale "Band" 0 (image.bands - 1) 0;
1336       		display = Option "Display as" [
1337			_ "Grey",
1338			_ "Green over Red",
1339			_ "Blue over Red",
1340			_ "Red over Green",
1341			_ "Red over Blue",
1342			_ "Blue over Green",
1343			_ "Green over Blue"
1344		] 0;
1345
1346        	_result
1347				= output
1348			{
1349				down = (int) band.value;
1350				up = down + 1;
1351				remainder = band.value - down;
1352
1353				fade x a
1354					= Vector [0], x == 0
1355					= a * x;
1356
1357				a = fade remainder image?up;
1358				b = fade (1 - remainder) image?down;
1359
1360				output = [
1361					a + b,
1362					a ++ b ++ 0,
1363					a ++ 0 ++ b,
1364					b ++ a ++ 0,
1365					b ++ 0 ++ a,
1366					0 ++ a ++ b,
1367					0 ++ b ++ a
1368				] ? display;
1369			}
1370		}
1371	}
1372
1373	Bitwise_item = class
1374		Menuaction "Bi_twise" "browse through the bits of an image" {
1375		action x = class
1376        	_result {
1377			_vislevel = 3;
1378
1379        	bit
1380				= Islider "Bit" 0 (nbits - 1) (nbits - 1)
1381			{
1382				nbits
1383					= x.bits, is_Image x
1384					= 8;
1385				Islider c f t v = class
1386					scope.Scale c f t ((int) v) {
1387					Scale = Islider;
1388				}
1389			}
1390
1391        	_result
1392				= map_unary process x
1393			{
1394				process im = (im & (0x1 << bit.value)) != 0;
1395			}
1396		}
1397	}
1398}
1399
1400#separator
1401
1402Filter_negative_item = class
1403	Menuaction "Photographic _Negative" "swap black and white" {
1404	action x
1405		= map_unary invert x
1406	{
1407		invert in
1408			= clip2fmt in.format (colour_transform_to (get_type in) rgb')
1409		{
1410			rgb = colour_transform_to Image_type.sRGB in;
1411			rgb' = 255 - rgb;
1412		}
1413	}
1414}
1415
1416Filter_solarize_item = class
1417	Menuaction "_Solarise" "invert colours above a threshold" {
1418	action x = class
1419		_result {
1420		_vislevel = 3;
1421
1422		kink = Scale "Kink" 0 1 0.5;
1423
1424		_result
1425			= map_unary process x
1426		{
1427			process image
1428				= hist_map tab'''' image
1429			{
1430				// max pixel value for this format
1431				mx = Image_format.maxval image.format;
1432
1433				// make a LUT ... just 8 and 16 bit
1434				tab
1435					= im_identity_ushort image.bands mx,
1436						image.format ==
1437							Image_format.USHORT
1438					= im_identity image.bands;
1439				tab' = Image tab;
1440
1441				// make basic ^ shape
1442				tab''
1443					= tab' * (1 / kink), tab' < mx * kink
1444					= (mx - tab') / (1 - kink);
1445				tab''' = clip2fmt image.format tab'';
1446
1447				// smooth a bit
1448				mask = matrix_blur (tab'''.width / 8);
1449				tab'''' = convsep mask tab''';
1450			}
1451		}
1452	}
1453}
1454
1455Filter_diffuse_glow_item = class
1456	Menuaction "_Diffuse Glow" "add a halo to highlights" {
1457	action x = class
1458		_result {
1459		_vislevel = 3;
1460
1461		r = Scale "Radius" 0 50 5;
1462		highlights = Scale "Highlights" 0 100 95;
1463		glow = Scale "Glow" 0 1 0.5;
1464		colour = Colour_new_item.Widget_colour_item.action;
1465
1466		_result
1467			= map_unary process x
1468		{
1469			process image
1470				= image'
1471			{
1472				mono = (unsigned char) (colour_transform_to
1473					Image_type.B_W image);
1474				thresh = hist_thresh (highlights.value / 100) mono;
1475				mask = mono > thresh;
1476				blur = convsep (matrix_gaussian_blur r.value) mask;
1477				colour' = colour_transform_to image.type colour;
1478				image' = image + colour' * glow * (blur / 255);
1479			}
1480		}
1481	}
1482}
1483
1484Filter_drop_shadow_item = class
1485	Menuaction "Drop S_hadow" "add a drop shadow to an image" {
1486	action x = class
1487        _result {
1488        _vislevel = 3;
1489
1490        sx = Scale "Horizontal shadow" (-50) 50 5;
1491        sy = Scale "Vertical shadow" (-50) 50 5;
1492        ss = Scale "Shadow softness" 0 20 5;
1493        bg_colour = Expression "Background colour" 255;
1494        sd_colour = Expression "Shadow colour" 128;
1495        alpha = Toggle "Shadow in alpha channel" false;
1496        transparent = Toggle "Zero pixels are transparent" false;
1497
1498        _result
1499            = map_unary shadow x
1500        {
1501            shadow image
1502            	= Image final
1503            {
1504                blur_size = ss.value * 2 + 1;
1505
1506                // matrix we blur with to soften shadows
1507                blur_matrix = matrix_gaussian_blur blur_size;
1508                matrix_size = blur_matrix.width;
1509                matrix_radius = (int) (matrix_size / 2) + 1;
1510
1511                // position and size of shadow image in input cods
1512                // before and after fuzzing
1513                shadow_rect = Rect sx.value sy.value
1514					image.width image.height;
1515                fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius;
1516
1517                // size and pos of final image, in input cods
1518                final_rect = image.rect.union fuzzy_shadow_rect;
1519
1520                // hard part of shadow in output cods
1521                shadow_rect' = Rect
1522                    (shadow_rect.left - final_rect.left)
1523                    (shadow_rect.top - final_rect.top)
1524                    shadow_rect.width shadow_rect.height;
1525
1526                // make the shadow mask ... true for parts which cast
1527                // a shadow
1528                mask
1529                    = (foldr1 bitwise_and @ bandsplit) (image.value != 0),
1530                		transparent
1531                    = image_new image.width image.height 1 Image_format.UCHAR
1532                        Image_coding.NOCODING Image_type.B_W 255 0 0;
1533                mask' = embed 0 shadow_rect'.left shadow_rect'.top
1534                        final_rect.width final_rect.height mask;
1535				mask'' = convsep blur_matrix mask';
1536
1537                // use mask to fade between bg and shadow colour
1538                mk_background colour = image_new
1539                	final_rect.width final_rect.height
1540                    image.bands image.format image.coding image.type
1541                    colour 0 0;
1542
1543                bg_image = mk_background bg_colour.expr;
1544                shadow_image = mk_background sd_colour.expr;
1545                bg = blend mask'' shadow_image bg_image;
1546
1547                // make a full size mask
1548                fg_mask = embed 0
1549                    (image.rect.left - final_rect.left)
1550                    (image.rect.top - final_rect.top)
1551                    final_rect.width final_rect.height mask;
1552
1553                // wrap up the input image ... put the shadow colour
1554                // around it, so if we are outputting a separate
1555                // alpha the shadow colour will be set correctly
1556                fg = insert (image.rect.left - final_rect.left)
1557                    (image.rect.top - final_rect.top)
1558                    image.value shadow_image;
1559
1560                final
1561                    // make a separate alpha
1562                    = fg ++ mask'', alpha
1563
1564                    // paste image over shadow
1565                    = if fg_mask then fg else bg;
1566            }
1567        }
1568    }
1569}
1570
1571Filter_paint_text_item = class
1572	Menuaction "_Paint Text" "paint text into an image" {
1573	action x
1574		= paint_position, is_Group x
1575		= paint_area
1576	{
1577		paint_area = class
1578			_result {
1579			_check_args = [
1580				[x, "x", check_Image]
1581			];
1582			_vislevel = 3;
1583
1584			text = String "Text to paint" "<i>Hello</i> world!";
1585			font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
1586			align = Option "Alignment" ["Left", "Centre", "Right"] 0;
1587			dpi = Expression "DPI" 300;
1588			colour = Expression "Text colour" 255;
1589			place = Region x (x.width / 4) (x.height / 4)
1590				(x.width / 2) (x.height / 2);
1591
1592			_result
1593				= insert_noexpand place.left place.top (blend txt' fg place) x
1594			{
1595				fg = image_new place.width place.height x.bands x.format
1596					x.coding x.type colour.expr 0 0;
1597				txt = Image (im_text text.value font.value
1598					place.width align.value (to_real dpi));
1599	            bg = im_black place.width place.height 1;
1600				txt' = insert_noexpand 0 0 txt bg;
1601			}
1602		}
1603
1604		paint_position = class
1605			_result {
1606			_vislevel = 3;
1607
1608			text = Pattern_images_item.Text_item.action;
1609			colour = Expression "Text colour" 255;
1610			position = Option "Position" [
1611				_ "North-west",
1612				_ "North",
1613				_ "North-east",
1614				_ "West",
1615				_ "Centre",
1616				_ "East",
1617				_ "South-west",
1618				_ "South",
1619				_ "South-east",
1620				_ "Specify in pixels"
1621			] 4;
1622			left = Expression "Pixels from left" 0;
1623			top = Expression "Pixels from top" 0;
1624
1625			_result
1626				= map_unary paint x
1627			{
1628				paint image
1629					= insert_noexpand x' y' place' image
1630				{
1631					xr = image.width - text.width;
1632					yr = image.height - text.height;
1633					x
1634						= left.expr, position == 9
1635						= [0, xr / 2, xr]?(position % 3);
1636					y
1637						= top.expr, position == 9
1638						= [0, yr / 2, yr]?(position / 3);
1639					x' = range 0 x (image.width - 1);
1640					y' = range 0 y (image.height - 1);
1641					w' = range 1 text.width (image.width - x');
1642					h' = range 1 text.height (image.height - y');
1643
1644					place = extract_area x' y' w' h' image;
1645					text' = insert_noexpand 0 0 text (im_black w' h' 1);
1646					fg = image_new w' h' image.bands image.format
1647						image.coding image.type colour.expr 0 0;
1648					place' = blend text' fg place;
1649				}
1650			}
1651		}
1652	}
1653}
1654
1655Autotrace_item = class
1656	Menuaction "_Trace" "convert a bitmap to an SVG file" {
1657	action x = class
1658		_result {
1659		_vislevel = 3;
1660
1661		despeckle = Scale "Despeckle level" 1 20 1;
1662		line = Scale "Line threshold" 1 20 1;
1663		center = Toggle "Trace centreline" false;
1664		scale = Scale "SVG scale" 0.1 10 1;
1665
1666		command
1667			= "autotrace %s " ++ join_sep " "
1668				[ofmt, ofile, desp, lint, cent]
1669		{
1670			prog = search_for_error "autotrace";
1671			ofmt = "-output-format svg";
1672			ofile = "-output-file %s";
1673			desp = "-despeckle-level " ++ print despeckle.value;
1674			lint = "-line-threshold " ++ print line.value;
1675			cent = if center then "-centerline " else "";
1676		}
1677
1678		_result
1679			= Image output
1680		{
1681			[output] = vips_call "system"
1682				[command]
1683				[$in => [x.value],
1684				 $in_format => "%s.ppm",
1685				 $out => true,
1686				 $out_format => "%s.svg[scale=" ++ print scale.value ++ "]"
1687				];
1688		}
1689	}
1690}
1691
1692