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