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 1.5;
81			bm = Scale "Brighten by at most" 1 50 10;
82			dm = Scale "Darken by at most" 1 50 50;
83			fs = Scale "Sharpen flat areas by" (-2) 5 1;
84			js = Scale "Sharpen jaggy areas by" (-2) 5 2;
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_greyc_item = class
712	Menupullright "_GREYCstoration" "noise-removing filter" {
713	Denoise_item = class
714		Menuaction "Denoise" "Noise-removing filter" {
715		action x = class
716			_result {
717			_vislevel = 3;
718
719			iterations = Scale "Iterations" 1 5 1;
720			amplitude = Scale "Amplitude" 1 100 40;
721			sharpness = Scale "Sharpness" 0 3 0.9;
722			anisotropy = Scale "Anisotropy" 0 1 0.15;
723			alpha = Scale "Noise scale" 0 5 0.6;
724			sigma = Scale "Geometry regularity" 0 2 1.1;
725			dl = Scale "Spatial integration step" 0 1 0.8;
726			da = Scale "Angular integration step" 0 90 30;
727			gauss_prec = Scale "Precision" 1 10 2;
728			interpolation = Option "Interpolation"
729				["Nearest-neighbour", "Bilinear", "Runge-Kutta"] 0;
730			fast_approx = Toggle "Fast approximation" true;
731
732			_result = greyc
733				(to_real iterations) (to_real amplitude) (to_real sharpness)
734				(to_real anisotropy) (to_real alpha) (to_real sigma)
735				(to_real dl) (to_real da)
736				(to_real gauss_prec) (to_real interpolation)
737				(to_real fast_approx) x;
738		}
739	}
740
741	Enlarge_item = class
742		Menuaction "Enlarge" "Enlarge image" {
743		action x = class
744			_result {
745			_vislevel = 3;
746
747			scale = Scale "Enlarge" 1 10 3;
748			iterations = Scale "Iterations" 1 5 3;
749			amplitude = Scale "Amplitude" 1 100 20;
750			sharpness = Scale "Sharpness" 0 3 0.2;
751			anisotropy = Scale "Anisotropy" 0 1 0.9;
752			alpha = Scale "Noise scale" 0 5 0.1;
753			sigma = Scale "Geometry regularity" 0 2 1.5;
754			dl = Scale "Spatial integration step" 0 1 0.8;
755			da = Scale "Angular integration step" 0 90 30;
756			gauss_prec = Scale "Precision" 1 10 2;
757			interpolation = Option "Interpolation"
758				["Nearest-neighbour", "Bilinear", "Runge-Kutta"] 0;
759			fast_approx = Toggle "Fast approximation" true;
760
761			_result = greyc
762				(to_real iterations) (to_real amplitude) (to_real sharpness)
763				(to_real anisotropy) (to_real alpha) (to_real sigma)
764				(to_real dl) (to_real da)
765				(to_real gauss_prec) (to_real interpolation)
766				(to_real fast_approx)
767					(resize Interpolate_bilinear
768						(to_real scale) (to_real scale) x);
769		}
770	}
771}
772
773#separator
774
775Filter_tilt_item = class
776	Menupullright "Ti_lt Brightness" "tilt brightness" {
777	Left_right_item = class
778		Menuaction "_Left to Right" "linear left-right brighten" {
779		action x = class
780			_result {
781			_vislevel = 3;
782
783			tilt = Scale "Left-right tilt" (-1) 1 0;
784
785			_result
786					= map_unary tilt_lr x
787			{
788				tilt_lr image
789					= image * scale
790				{
791					ramp = im_fgrey image.width image.height;
792					scale = (ramp - 0.5) * tilt + 1;
793				}
794			}
795		}
796	}
797
798	Top_bottom_item = class
799		Menuaction "_Top to Bottom" "linear top-bottom brighten" {
800		action x = class
801			_result {
802			_vislevel = 3;
803
804			tilt = Scale "Top-bottom tilt" (-1) 1 0;
805
806			_result
807				= map_unary tilt_tb x
808			{
809				tilt_tb image
810					= image * scale
811				{
812					ramp = rot90
813						(im_fgrey image.height image.width);
814					scale = (ramp - 0.5) * tilt + 1;
815				}
816			}
817		}
818	}
819
820	sep1 = Menuseparator;
821
822	Left_right_cos_item = class
823		Menuaction "Cosine Left-_right" "cosine left-right brighten" {
824		action x = class
825			_result {
826			_vislevel = 3;
827
828			tilt = Scale "Left-right tilt" (-1) 1 0;
829			shift = Scale "Shift by" (-1) 1 0;
830
831			_result
832					= map_unary tilt_lr x
833			{
834				tilt_lr image
835					= image * scale
836				{
837					ramp = im_fgrey image.width image.height - 0.5 -
838							shift.value;
839					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
840				}
841			}
842		}
843	}
844
845	Top_bottom_cos_item = class
846		Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
847		action x = class
848			_result {
849			_vislevel = 3;
850
851			tilt = Scale "Top-bottom tilt" (-1) 1 0;
852			shift = Scale "Shift by" (-1) 1 0;
853
854			_result
855				= map_unary tilt_tb x
856			{
857				tilt_tb image
858					= image * scale
859				{
860					ramp = rot90 (im_fgrey image.height image.width) - 0.5 -
861							shift.value;
862					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
863				}
864			}
865		}
866	}
867
868	sep2 = Menuseparator;
869
870	Circular_item = class
871		Menuaction "_Circular" "circular brighten" {
872		action x = class
873			_result {
874			_vislevel = 3;
875
876			tilt = Scale "Tilt" (-1) 1 0;
877			hshift = Scale "Horizontal shift by" (-1) 1 0;
878			vshift = Scale "Vertical shift by" (-1) 1 0;
879
880			_result
881				= map_unary tilt_tb x
882			{
883				tilt_tb image
884					= image * scale
885				{
886					hramp = im_fgrey image.width image.height - 0.5 -
887						hshift.value;
888					vramp = rot90 (im_fgrey image.height image.width) - 0.5 -
889						vshift.value;
890					ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
891					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
892				}
893			}
894		}
895	}
896}
897
898Filter_blend_item = class
899	Menupullright "_Blend" "blend objects together" {
900	Scale_blend_item = class
901		Menuaction "_Scale" "blend two objects together with a scale" {
902		action a b = class
903			_result {
904			_vislevel = 3;
905
906			p = Scale "Blend position" 0 1 0.5;
907
908			_result
909				= map_binary process a b
910			{
911				process im1 im2 = im1 * (1 - p.value) + im2 * p.value;
912			}
913		}
914	}
915
916	Image_blend_item = class
917		Menuaction "_Image" "use an image to blend two objects" {
918		action a b c = class
919			_result {
920			_vislevel = 3;
921
922			i = Toggle "Invert mask" false;
923
924			_result
925				= map_trinary process a b c
926			{
927				process a b c
928					= blend condition in1 in2, !i
929					= blend (invert condition) in1 in2
930				{
931					compare a b
932						// prefer image as the condition
933						= false,
934							!has_image a && has_image b
935						// prefer mono images as the condition
936						= false,
937							has_bands a && has_bands b &&
938							get_bands a > 1 && get_bands b == 1
939						// prefer uchar as the condition
940						= false,
941							has_format a && has_format b &&
942							get_format a > Image_format.UCHAR &&
943								get_format b == Image_format.UCHAR
944						= true;
945					[condition, in1, in2] = sortc compare [a, b, c];
946				}
947			}
948		}
949	}
950
951	Line_blend_item = class
952		Menuaction "_Along Line"
953			"blend between image a and image b along a line" {
954		action a b = class
955			_result {
956			_vislevel = 3;
957
958			orientation = Option "Orientation" [
959				"Left to Right",
960				"Top to Bottom"
961			] 0;
962			blend_position = Scale "Blend position" 0 1 0.5;
963			blend_width = Scale "Blend width" 0 1 0.05;
964
965			_result
966				= map_binary process a b
967            {
968                process a b
969					= blend (Image condition) b a
970                {
971					output_width = max_pair a.width b.width;
972					output_height = max_pair a.height b.height;
973					range
974						= output_width, orientation == 0
975						= output_height;
976					blend_position'
977						= floor (range * blend_position.value);
978					blend_width'
979						= 1, blend_width.value == 0
980						= floor (range * blend_width.value);
981                   	start = blend_position' - blend_width' / 2;
982
983					background = (make_xy output_width output_height) >=
984						blend_position';
985                   	ramp
986						= im_grey blend_width' output_height, orientation == 0
987                        = rot90 (im_grey blend_width' output_width);
988					condition
989						= insert_noexpand start 0 ramp background?0,
990							orientation == 0
991						= insert_noexpand 0 start ramp background?1;
992               	}
993			}
994		}
995	}
996
997	Blend_alpha_item = class
998		Menuaction "_Alpha" "blend images with optional alpha channels" {
999		// usage: layerit foreground background
1000		// input images must be either 1 or 3 bands, optionally + 1 band
1001		// which is used as the alpha channel
1002		// rich lott
1003
1004		scale_mask im opacity
1005			= (unsigned char) (to_real opacity / 255 * im);
1006
1007		// to mono
1008		intensity = colour_transform_to Image_type.B_W;
1009
1010		// All the blend functions
1011		// I am grateful to this page
1012		// http://www.pegtop.net/delphi/blendmodes/
1013		// for most of the formulae.
1014
1015		blend_normal mask opacity fg bg
1016			= blend (scale_mask mask opacity) fg bg;
1017
1018		blend_iflighter mask opacity fg bg
1019			= blend (if fg' > bg' then mask' else 0) fg bg
1020		{
1021			fg' = intensity fg;
1022			bg' = intensity bg;
1023			mask' = scale_mask mask opacity ;
1024		}
1025
1026		blend_ifdarker mask opacity fg bg
1027			= blend (if fg' < bg' then mask' else 0) fg bg
1028		{
1029			fg' = intensity fg ;
1030			bg' = intensity bg ;
1031			mask' = scale_mask mask opacity ;
1032		}
1033
1034		blend_multiply mask opacity fg bg
1035			= blend (scale_mask mask opacity) fg' bg
1036		{
1037			fg' = fg / 255 * bg;
1038		}
1039
1040		blend_add mask opacity fg bg
1041			= blend mask fg' bg
1042		{
1043			fg' = opacity / 255 * fg + bg;
1044		}
1045
1046		blend_subtract mask opacity fg bg
1047			= blend mask fg' bg
1048		{
1049			fg' = bg - opacity / 255 * fg;
1050		}
1051
1052		blend_screen mask opacity fg bg
1053			= blend mask fg' bg
1054		{
1055			fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255;
1056		}
1057
1058		blend_burn mask opacity fg bg
1059			= blend mask fg'' bg
1060		{
1061			// fades to white which has no effect.
1062			fg' = (255 - opacity) + opacity * fg / 255;
1063			fg'' = 255 - 255 * (255 - bg) / fg';
1064		}
1065
1066		blend_softlight mask opacity fg bg
1067			= blend mask' fg' bg
1068		{
1069			mask' = scale_mask mask opacity;
1070			fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255;
1071		}
1072
1073		blend_hardlight mask opacity fg bg
1074			= blend mask' fg' bg
1075		{
1076			mask' = scale_mask mask opacity;
1077			fg'
1078				= 2 / 255 * fg * bg, bg < 129
1079				= 255 - 2 * (255 - bg) * (255 - fg) / 255;
1080		}
1081
1082		blend_lighten mask opacity fg bg
1083			= blend mask' fg' bg
1084		{
1085			mask' = scale_mask mask opacity;
1086			fg' = if bg < fg then fg else bg;
1087		}
1088
1089		blend_darken 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_dodge mask opacity fg bg
1097			= blend mask fg'' bg
1098		{
1099			// one added to avoid divide by zero
1100			fg' = 1 + 255 - (opacity / 255 * fg);
1101			fg'' = bg * 255 / fg';
1102		}
1103
1104		blend_reflect mask opacity fg bg
1105			= blend mask' fg' bg
1106		{
1107			mask' = scale_mask mask opacity;
1108			fg' = bg * bg / (255 - fg);
1109		}
1110
1111		blend_freeze mask opacity fg bg
1112			= blend mask' fg' bg
1113		{
1114			mask' = scale_mask mask opacity;
1115			fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg);
1116		}
1117
1118		blend_or mask opacity fg bg
1119			= bg | (unsigned char) fg'
1120		{
1121			mask' = scale_mask mask opacity;
1122			fg' = fg * mask' / 255;
1123		}
1124
1125		blend_and 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 types
1133		NORMAL = 0;
1134		IFLIGHTER = 1;
1135		IFDARKER = 2;
1136		MULTIPLY = 3;
1137		ADD = 4;
1138		SUBTRACT = 5;
1139		SCREEN = 6;
1140		BURN = 7;
1141		DODGE = 8;
1142		HARDLIGHT = 9;
1143		SOFTLIGHT = 10;
1144		LIGHTEN = 11;
1145		DARKEN = 12;
1146		REFLECT = 13;
1147		FREEZE = 14;
1148		OR = 15;
1149		AND = 16;
1150
1151		// names we show the user for blend types
1152		names = Enum [
1153			_ "Normal" => NORMAL,
1154			_ "If Lighter" => IFLIGHTER,
1155			_ "If Darker" => IFDARKER,
1156			_ "Multiply" => MULTIPLY,
1157			_ "Add" => ADD,
1158			_ "Subtract" => SUBTRACT,
1159			_ "Screen" => SCREEN,
1160			_ "Burn" => BURN,
1161			_ "Soft Light" => SOFTLIGHT,
1162			_ "Hard Light" => HARDLIGHT,
1163			_ "Lighten" => LIGHTEN,
1164			_ "Darken" => DARKEN,
1165			_ "Dodge" => DODGE,
1166			_ "Reflect" => REFLECT,
1167			_ "Freeze" => FREEZE,
1168			_ "Bitwise OR" => OR,
1169			_ "Bitwise AND" => AND
1170		];
1171
1172		// functions we call for each blend type
1173		actions = Table [
1174			[NORMAL, blend_normal],
1175			[IFLIGHTER, blend_iflighter],
1176			[IFDARKER, blend_ifdarker],
1177			[MULTIPLY, blend_multiply],
1178			[ADD, blend_add],
1179			[SUBTRACT, blend_subtract],
1180			[SCREEN, blend_screen],
1181			[BURN, blend_burn],
1182			[SOFTLIGHT, blend_softlight],
1183			[HARDLIGHT, blend_hardlight],
1184			[LIGHTEN, blend_lighten],
1185			[DARKEN, blend_darken],
1186			[DODGE, blend_dodge],
1187			[REFLECT, blend_reflect],
1188			[FREEZE, blend_freeze],
1189			[OR, blend_or],
1190			[AND, blend_and]
1191		];
1192
1193		// make sure im has an alpha channel (set opaque if it hasn't)
1194		put_alpha im
1195			= im, im.bands == 4 || im.bands == 2
1196			= im ++ 255;
1197
1198		// make sure im has no alpha channel
1199		lose_alpha im
1200			= extract_bands 0 3 im, im.bands == 4
1201			= im?0, im.bands == 2
1202			= im;
1203
1204		// does im have al alpha channel?
1205		has_alpha im = im.bands == 2 || im.bands == 4;
1206
1207		// get the alpha (set opaque if no alpha)
1208		get_alpha img
1209			= img'?3, img.bands == 4
1210			= img'?1
1211		{
1212			img' = put_alpha img;
1213		}
1214
1215		// add an alpha ... cast the alpha image to match the main image
1216		append_alpha im alpha
1217			= im ++ clip2fmt im.format alpha;
1218
1219		// makes fg the same size as bg, displaced with u, v pixel offset
1220		moveit fg bg u v
1221			= insert_noexpand u v fg bg'
1222		{
1223			bg' = image_new bg.width bg.height
1224				fg.bands fg.format fg.coding fg.type 0 0 0;
1225		}
1226
1227		action bg fg = class
1228			_value {
1229			_vislevel = 3;
1230
1231			method = Option_enum "Blend mode" names "Normal";
1232			opacity = Scale "Opacity" 0 255 255;
1233			hmove = Scale "Horizontal move by" (-bg.width) (bg.width) 0;
1234			vmove = Scale "Vertical move by" (-bg.height) (bg.height) 0;
1235
1236			_value
1237				= append_alpha blended merged_alpha, has_alpha bg
1238				= blended
1239			{
1240				// displace and resize fg (need to displace alpha too)
1241				fg' = moveit (put_alpha fg) bg hmove vmove;
1242
1243				// transform to sRGB
1244				fg'' = colour_transform_to Image_type.sRGB (lose_alpha fg');
1245				bg' = colour_transform_to Image_type.sRGB (lose_alpha bg);
1246
1247				// alphas merged
1248				merged_alpha = get_alpha bg | get_alpha fg';
1249
1250				// blend together
1251				blended = (actions.lookup 0 1 method.value_thing)
1252					(get_alpha fg') opacity.value fg'' bg';
1253			}
1254		}
1255	}
1256}
1257
1258Filter_overlay_header_item = class
1259	Menuaction "_Overlay"
1260		"make a colour overlay of two monochrome images" {
1261	action  a b = class
1262		_result {
1263		_vislevel = 3;
1264
1265		colour = Option "Colour overlay as" [
1266			_ "Green over Red",
1267			_ "Blue over Red",
1268			_ "Red over Green",
1269			_ "Red over Blue",
1270			_ "Blue over Green",
1271			_ "Green over Blue"
1272		] 0;
1273
1274		_result
1275			= map_binary overlay a b
1276		{
1277			overlay a b
1278				= image_set_type Image_type.sRGB
1279					[(a' ++ b' ++ 0),
1280						(a' ++ 0 ++ b'),
1281						(b' ++ a' ++ 0),
1282						(b' ++ 0 ++ a'),
1283						(0 ++ a' ++ b'),
1284						(0 ++ b' ++ a')]?colour
1285			{
1286				a' = colour_transform_to Image_type.B_W a;
1287				b' = colour_transform_to Image_type.B_W b;
1288			}
1289		}
1290	}
1291}
1292
1293Filter_colourize_item = class
1294	Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
1295	action a b = class
1296		_result {
1297		_vislevel = 3;
1298
1299		tint = Scale "Tint" 0 1 0.6;
1300
1301		_result
1302			= map_binary tintit a b
1303		{
1304			tintit a b
1305				= colour_transform_to (get_type colour) colourized'
1306			{
1307				// get the mono thing first
1308				[mono, colour] =
1309					sortc (const (is_colour_type @ get_type)) [a, b];
1310
1311				colour' = tint * colour_transform_to Image_type.LAB colour;
1312				mono' = colour_transform_to Image_type.B_W mono;
1313				colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2;
1314				colourized' = image_set_type Image_type.LAB colourized;
1315			}
1316		}
1317	}
1318}
1319
1320Filter_browse_multiband_item = class
1321	Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" {
1322	Bandwise_item = class
1323		Menuaction "B_andwise" "browse through the bands of a multiband image" {
1324		action image = class
1325        	_result {
1326			_vislevel = 3;
1327
1328        	band = Scale "Band" 0 (image.bands - 1) 0;
1329       		display = Option "Display as" [
1330			_ "Grey",
1331			_ "Green over Red",
1332			_ "Blue over Red",
1333			_ "Red over Green",
1334			_ "Red over Blue",
1335			_ "Blue over Green",
1336			_ "Green over Blue"
1337		] 0;
1338
1339        	_result
1340				= output
1341			{
1342				down = (int) band.value;
1343				up = down + 1;
1344				remainder = band.value - down;
1345
1346				fade x a
1347					= Vector [0], x == 0
1348					= a * x;
1349
1350				a = fade remainder image?up;
1351				b = fade (1 - remainder) image?down;
1352
1353				output = [
1354					a + b,
1355					a ++ b ++ 0,
1356					a ++ 0 ++ b,
1357					b ++ a ++ 0,
1358					b ++ 0 ++ a,
1359					0 ++ a ++ b,
1360					0 ++ b ++ a
1361				] ? display;
1362			}
1363		}
1364	}
1365
1366	Bitwise_item = class
1367		Menuaction "Bi_twise" "browse through the bits of an image" {
1368		action x = class
1369        	_result {
1370			_vislevel = 3;
1371
1372        	bit
1373				= Islider "Bit" 0 (nbits - 1) (nbits - 1)
1374			{
1375				nbits
1376					= x.bits, is_Image x
1377					= 8;
1378				Islider c f t v = class
1379					scope.Scale c f t ((int) v) {
1380					Scale = Islider;
1381				}
1382			}
1383
1384        	_result
1385				= map_unary process x
1386			{
1387				process im = (im & (0x1 << bit.value)) != 0;
1388			}
1389		}
1390	}
1391}
1392
1393#separator
1394
1395Filter_negative_item = class
1396	Menuaction "Photographic _Negative" "swap black and white" {
1397	action x
1398		= map_unary invert x
1399	{
1400		invert in
1401			= clip2fmt in.format (colour_transform_to (get_type in) rgb')
1402		{
1403			rgb = colour_transform_to Image_type.sRGB in;
1404			rgb' = 255 - rgb;
1405		}
1406	}
1407}
1408
1409Filter_solarize_item = class
1410	Menuaction "_Solarise" "invert colours above a threshold" {
1411	action x = class
1412		_result {
1413		_vislevel = 3;
1414
1415		kink = Scale "Kink" 0 1 0.5;
1416
1417		_result
1418			= map_unary process x
1419		{
1420			process image
1421				= hist_map tab'''' image
1422			{
1423				// max pixel value for this format
1424				mx = Image_format.maxval image.format;
1425
1426				// make a LUT ... just 8 and 16 bit
1427				tab
1428					= im_identity_ushort image.bands mx,
1429						image.format ==
1430							Image_format.USHORT
1431					= im_identity image.bands;
1432				tab' = Image tab;
1433
1434				// make basic ^ shape
1435				tab''
1436					= tab' * (1 / kink), tab' < mx * kink
1437					= (mx - tab') / (1 - kink);
1438				tab''' = clip2fmt image.format tab'';
1439
1440				// smooth a bit
1441				mask = matrix_blur (tab'''.width / 8);
1442				tab'''' = convsep mask tab''';
1443			}
1444		}
1445	}
1446}
1447
1448Filter_diffuse_glow_item = class
1449	Menuaction "_Diffuse Glow" "add a halo to highlights" {
1450	action x = class
1451		_result {
1452		_vislevel = 3;
1453
1454		r = Scale "Radius" 0 50 5;
1455		highlights = Scale "Highlights" 0 100 95;
1456		glow = Scale "Glow" 0 1 0.5;
1457		colour = Colour_new_item.Widget_colour_item.action;
1458
1459		_result
1460			= map_unary process x
1461		{
1462			process image
1463				= image'
1464			{
1465				mono = (unsigned char) (colour_transform_to
1466					Image_type.B_W image);
1467				thresh = hist_thresh (highlights.value / 100) mono;
1468				mask = mono > thresh;
1469				blur = convsep (matrix_gaussian_blur r.value) mask;
1470				colour' = colour_transform_to image.type colour;
1471				image' = image + colour' * glow * (blur / 255);
1472			}
1473		}
1474	}
1475}
1476
1477Filter_drop_shadow_item = class
1478	Menuaction "Drop S_hadow" "add a drop shadow to an image" {
1479	action x = class
1480        _result {
1481        _vislevel = 3;
1482
1483        sx = Scale "Horizontal shadow" (-50) 50 5;
1484        sy = Scale "Vertical shadow" (-50) 50 5;
1485        ss = Scale "Shadow softness" 0 20 5;
1486        bg_colour = Expression "Background colour" 255;
1487        sd_colour = Expression "Shadow colour" 128;
1488        alpha = Toggle "Shadow in alpha channel" false;
1489        transparent = Toggle "Zero pixels are transparent" false;
1490
1491        _result
1492            = map_unary shadow x
1493        {
1494            shadow image
1495            	= Image final
1496            {
1497                blur_size = ss.value * 2 + 1;
1498
1499                // matrix we blur with to soften shadows
1500                blur_matrix = matrix_gaussian_blur blur_size;
1501                matrix_size = blur_matrix.width;
1502                matrix_radius = (int) (matrix_size / 2) + 1;
1503
1504                // position and size of shadow image in input cods
1505                // before and after fuzzing
1506                shadow_rect = Rect sx.value sy.value
1507					image.width image.height;
1508                fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius;
1509
1510                // size and pos of final image, in input cods
1511                final_rect = image.rect.union fuzzy_shadow_rect;
1512
1513                // hard part of shadow in output cods
1514                shadow_rect' = Rect
1515                    (shadow_rect.left - final_rect.left)
1516                    (shadow_rect.top - final_rect.top)
1517                    shadow_rect.width shadow_rect.height;
1518
1519                // make the shadow mask ... true for parts which cast
1520                // a shadow
1521                mask
1522                    = (foldr1 bitwise_and @ bandsplit) (image.value != 0),
1523                		transparent
1524                    = image_new image.width image.height 1 Image_format.UCHAR
1525                        Image_coding.NOCODING Image_type.B_W 255 0 0;
1526                mask' = embed 0 shadow_rect'.left shadow_rect'.top
1527                        final_rect.width final_rect.height mask;
1528				mask'' = convsep blur_matrix mask';
1529
1530                // use mask to fade between bg and shadow colour
1531                mk_background colour = image_new
1532                	final_rect.width final_rect.height
1533                    image.bands image.format image.coding image.type
1534                    colour 0 0;
1535
1536                bg_image = mk_background bg_colour.expr;
1537                shadow_image = mk_background sd_colour.expr;
1538                bg = blend mask'' shadow_image bg_image;
1539
1540                // make a full size mask
1541                fg_mask = embed 0
1542                    (image.rect.left - final_rect.left)
1543                    (image.rect.top - final_rect.top)
1544                    final_rect.width final_rect.height mask;
1545
1546                // wrap up the input image ... put the shadow colour
1547                // around it, so if we are outputting a separate
1548                // alpha the shadow colour will be set correctly
1549                fg = insert (image.rect.left - final_rect.left)
1550                    (image.rect.top - final_rect.top)
1551                    image.value shadow_image;
1552
1553                final
1554                    // make a separate alpha
1555                    = fg ++ mask'', alpha
1556
1557                    // paste image over shadow
1558                    = if fg_mask then fg else bg;
1559            }
1560        }
1561    }
1562}
1563
1564Filter_paint_text_item = class
1565	Menuaction "_Paint Text" "paint text into an image" {
1566	action x
1567		= paint_position, is_Group x
1568		= paint_area
1569	{
1570		paint_area = class
1571			_result {
1572			_check_args = [
1573				[x, "x", check_Image]
1574			];
1575			_vislevel = 3;
1576
1577			text = String "Text to paint" "<i>Hello</i> world!";
1578			font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
1579			align = Option "Alignment" ["Left", "Centre", "Right"] 0;
1580			dpi = Expression "DPI" 300;
1581			colour = Expression "Text colour" 255;
1582			place = Region x (x.width / 4) (x.height / 4)
1583				(x.width / 2) (x.height / 2);
1584
1585			_result
1586				= insert_noexpand place.left place.top (blend txt' fg place) x
1587			{
1588				fg = image_new place.width place.height x.bands x.format
1589					x.coding x.type colour.expr 0 0;
1590				txt = Image (im_text text.value font.value
1591					place.width align.value (to_real dpi));
1592	            bg = im_black place.width place.height 1;
1593				txt' = insert_noexpand 0 0 txt bg;
1594			}
1595		}
1596
1597		paint_position = class
1598			_result {
1599			_vislevel = 3;
1600
1601			text = Pattern_images_item.Text_item.action;
1602			colour = Expression "Text colour" 255;
1603			position = Option "Position" [
1604				_ "North-west",
1605				_ "North",
1606				_ "North-east",
1607				_ "West",
1608				_ "Centre",
1609				_ "East",
1610				_ "South-west",
1611				_ "South",
1612				_ "South-east",
1613				_ "Specify in pixels"
1614			] 4;
1615			left = Expression "Pixels from left" 0;
1616			top = Expression "Pixels from top" 0;
1617
1618			_result
1619				= map_unary paint x
1620			{
1621				paint image
1622					= insert_noexpand x' y' place' image
1623				{
1624					xr = image.width - text.width;
1625					yr = image.height - text.height;
1626					x
1627						= left.expr, position == 9
1628						= [0, xr / 2, xr]?(position % 3);
1629					y
1630						= top.expr, position == 9
1631						= [0, yr / 2, yr]?(position / 3);
1632					x' = range 0 x (image.width - 1);
1633					y' = range 0 y (image.height - 1);
1634					w' = range 1 text.width (image.width - x');
1635					h' = range 1 text.height (image.height - y');
1636
1637					place = extract_area x' y' w' h' image;
1638					text' = insert_noexpand 0 0 text (im_black w' h' 1);
1639					fg = image_new w' h' image.bands image.format
1640						image.coding image.type colour.expr 0 0;
1641					place' = blend text' fg place;
1642				}
1643			}
1644		}
1645	}
1646}
1647