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