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			smooth_threshold = Slider 0 5 1.5;
81			brighten_max = Slider 1 50 10;
82			darken_max = Slider 1 50 50;
83			flat_sharp = Slider (-2) 5 1;
84			jaggy_sharp = Slider (-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 smooth_threshold
94						brighten_max darken_max
95						flat_sharp jaggy_sharp in';
96					in''' = colour_transform_to (get_type in) in'';
97				}
98			}
99		}
100	}
101
102	sep1 = Menuseparator;
103
104	Custom_blur_item = class
105		Menuaction "Custom B_lur" "blur with tuneable parameters" {
106		action x = class
107			_result {
108			_vislevel = 3;
109
110			radius = Slider 1 50 1;
111			shape = Option "Mask shape" [
112				"Square",
113				"Gaussian"
114			] 0;
115
116			_result
117				= map_unary process x
118			{
119				process in
120					= convsep mask in
121				{
122					mask
123						= matrix_blur radius.value, shape.value == 0
124						= matrix_gaussian_blur radius.value;
125				}
126			}
127		}
128	}
129
130	Custom_conv_item = class
131		Menuaction "Custom C_onvolution"
132			"convolution filter with tuneable parameters" {
133		action x = class
134			_result {
135			_vislevel = 3;
136
137			matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
138			separable
139				= Toggle "Seperable convolution" false,
140					matrix.width == 1 || matrix.height == 1
141				= false;
142			type = Option "Convolution type" ["Int", "Float"] 0;
143
144			_result
145				= map_unary process x
146			{
147				process in
148					= Image in'
149				{
150					conv_fn
151						= im_conv, !separable && type == 0
152						= im_convsep, separable && type == 0
153						= im_convf, !separable && type == 1
154						= im_convsepf, separable && type == 1
155						= error "boink!";
156					in' = conv_fn in.value matrix;
157				}
158			}
159		}
160	}
161}
162
163Filter_rank_item = class
164	Menupullright "_Rank" "various rank filters" {
165	Median_item = class
166		Menuaction "_Median" "3x3 median rank filter" {
167		action x = map_unary (rank 3 3 5) x;
168	}
169
170	Image_rank_item = class
171		Menuaction "_Image Rank" "pixelwise rank a list or group of images" {
172		action x = class
173			_result {
174			_vislevel = 3;
175
176			select
177				= Expression "Rank" ((int) ((guess_size + 1) / 2))
178			{
179				guess_size
180					= len x, is_list x
181					= len x.value, is_Group x
182					= 0;
183			}
184
185			// can't really iterate over groups ... since we allow a group
186			// argument
187			_result = rank_image select x;
188		}
189	}
190
191	Custom_rank_item = class
192		Menuaction "Custom _Rank" "rank filter with tuneable parameters" {
193		action x = class
194			_result {
195			_vislevel = 3;
196
197			width = Expression "Window width" 3;
198			height = Expression "Window height" 3;
199			select = Expression "Rank"
200				((int) ((to_real width * to_real height + 1) / 2));
201
202			_result
203				= map_unary process x
204			{
205				process in
206					= rank width height select in;
207			}
208		}
209	}
210}
211
212Filter_morphology_item = class
213	Menupullright "_Morphology" "various morphological filters" {
214	/* Some useful masks.
215	 */
216	mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]];
217	mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]];
218	mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
219	thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]];
220
221	Threshold_item = class
222		Menuaction "Thres_hold" "simple image threshold" {
223		action x = class
224			_result {
225			_vislevel = 3;
226
227			threshold
228				= Slider 0 mx (mx / 2)
229			{
230				mx
231					= Image_format.maxval x.format, is_Image x
232					= 255;
233			}
234
235			_result = map_unary (more threshold.value) x;
236		}
237	}
238
239	sep1 = Menuseparator;
240
241	Dilate8_item = class
242		Menuaction "Dilate _8-connected" "dilate with an 8-connected mask" {
243		action x = map_unary (dilate mask8) x;
244	}
245
246	Dilate4_item = class
247		Menuaction "Dilate _4-connected" "dilate with a 4-connected mask" {
248		action x = map_unary (dilate mask4) x;
249	}
250
251	Erode8_item = class
252		Menuaction "_Erode 8-connected" "erode with an 8-connected mask" {
253		action x = map_unary (erode mask8) x;
254	}
255
256	Erode4_item = class
257		Menuaction "E_rode 4-connected" "erode with a 4-connected mask" {
258		action x = map_unary (erode mask4) x;
259	}
260
261	Custom_morph_item = class
262		Menuaction "Custom _Morphology"
263			"convolution morphological operator" {
264		action x = class
265			_result {
266			_vislevel = 3;
267
268			mask = mask4;
269			type = Option "Operation" ["Erode", "Dilate"] 1;
270			apply = Expression "Number of times to apply mask" 1;
271
272			_result
273				= map_unary morph x
274			{
275				morph image
276					= Image value'
277				{
278					fatmask = (iterate (dilate mask) mask)?(to_real apply - 1);
279
280					value'
281						= im_erode image.value fatmask, type.value == 0
282						= im_dilate image.value fatmask;
283				}
284			}
285		}
286	}
287
288	sep2 = Menuseparator;
289
290	Open_item = class
291		Menuaction "_Open" "open with an 8-connected mask" {
292		action x = map_unary (dilate mask8 @ erode mask8) x;
293	}
294
295	Close_item = class
296		Menuaction "_Close" "close with an 8-connected mask" {
297		action x = map_unary (erode mask8 @ dilate mask8) x;
298	}
299
300	Clean_item = class
301		Menuaction "C_lean" "remove 8-connected isolated points" {
302		action x
303			= map_unary clean x
304		{
305			clean x = x ^ erode mask1 x;
306		}
307	}
308
309	Thin_item = class
310		Menuaction "_Thin" "thin once" {
311		action x
312			= map_unary thinall x
313		{
314			masks = take 8 (iterate rot45 thin);
315			thin1 m x = x ^ erode m x;
316			thinall x = foldr thin1 x masks;
317		}
318	}
319}
320
321Filter_fourier_item = class
322	Menupullright "_Fourier" "various Fourier filters" {
323	preview_size = 64;
324
325	sense_option = Option "Sense" [
326		"Pass",
327		"Reject"
328	] 0;
329
330	// make a visualisation image
331	make_vis fn = (Image @ image_set_type Image_type.FOURIER @ rotquad @ fn)
332		(im_create_fmask preview_size preview_size);
333
334	// make the process function
335	process fn in
336		= (Image @ fn) (im_flt_image_freq in.value);
337
338	New_ideal_item = class
339		Menupullright "_Ideal" "various ideal Fourier filters" {
340		High_low_item = class
341			Menuaction "_High or Low Pass"
342				"highpass/lowpass ideal Fourier filter" {
343			action x = class
344				_result {
345				_vislevel = 3;
346
347				sense = sense_option;
348				frequency_cutoff = Slider 0.01 0.99 0.5;
349
350				// call a freq func with our parameters
351				_params f = f sense.value frequency_cutoff.value 0 0 0 0;
352
353				visualize_mask = make_vis _params;
354
355				_result = map_unary (process _params) x;
356			}
357		}
358
359		Ring_item = class
360			Menuaction "_Ring Pass or Ring Reject"
361				"ring pass/reject ideal Fourier filter" {
362			action x = class
363				_result {
364				_vislevel = 3;
365
366				sense = sense_option;
367				frequency_cutoff = Slider 0.01 0.99 0.5;
368				ring_width = Slider 0.01 0.99 0.5;
369
370				// call a freq func with our parameters
371				_params f = f (sense.value + 6) frequency_cutoff.value
372					ring_width.value 0 0 0;
373
374				visualize_mask = make_vis _params;
375
376				_result = map_unary (process _params) x;
377			}
378		}
379
380		Band_item = class
381			Menuaction "_Band Pass or Band Reject"
382				"band pass/reject ideal Fourier filter" {
383			action x = class
384				_result {
385				_vislevel = 3;
386
387				sense = sense_option;
388				frequency_cutoff_x = Slider 0.01 0.99 0.5;
389				frequency_cutoff_y = Slider 0.01 0.99 0.5;
390				radius = Slider 0.01 0.99 0.5;
391
392				// call a freq func with our parameters
393				_params f = f (sense.value + 12)
394						frequency_cutoff_x.value frequency_cutoff_y.value
395						radius.value 0 0;
396
397				visualize_mask = make_vis _params;
398
399				_result = map_unary (process _params) x;
400			}
401		}
402	}
403
404	New_gaussian_item = class
405		Menupullright "_Gaussian" "various Gaussian Fourier filters" {
406		High_low_item = class
407			Menuaction "_High or Low Pass"
408				"highpass/lowpass Gaussian Fourier filter" {
409			action x = class
410				_result {
411				_vislevel = 3;
412
413				sense = sense_option;
414				frequency_cutoff = Slider 0.01 0.99 0.5;
415				amplitude_cutoff = Slider 0.01 0.99 0.5;
416
417				// call a freq func with our parameters
418				_params f = f (sense.value + 4) frequency_cutoff.value
419					amplitude_cutoff.value 0 0 0;
420
421				visualize_mask = make_vis _params;
422
423				_result = map_unary (process _params) x;
424			}
425		}
426
427		Ring_item = class
428			Menuaction "_Ring Pass or Ring Reject"
429				"ring pass/reject Gaussian Fourier filter" {
430			action x = class
431				_result {
432				_vislevel = 3;
433
434				sense = sense_option;
435				frequency_cutoff = Slider 0.01 0.99 0.5;
436				amplitude_cutoff = Slider 0.01 0.99 0.5;
437				ring_width = Slider 0.01 0.99 0.5;
438
439				// call a freq func with our parameters
440				_params f = f (sense.value + 10) frequency_cutoff.value
441					ring_width.value amplitude_cutoff.value 0 0;
442
443				visualize_mask = make_vis _params;
444
445				_result = map_unary (process _params) x;
446			}
447		}
448
449		Band_item = class
450			Menuaction "_Band Pass or Band Reject"
451				"band pass/reject Gaussian Fourier filter" {
452			action x = class
453				_result {
454				_vislevel = 3;
455
456				sense = sense_option;
457				frequency_cutoff_x = Slider 0.01 0.99 0.5;
458				frequency_cutoff_y = Slider 0.01 0.99 0.5;
459				radius = Slider 0.01 0.99 0.5;
460				amplitude_cutoff = Slider 0.01 0.99 0.5;
461
462				// call a freq func with our parameters
463				_params f = f (sense.value + 16)
464					frequency_cutoff_x.value frequency_cutoff_y.value
465					radius.value amplitude_cutoff.value 0;
466
467				visualize_mask = make_vis _params;
468
469				_result = map_unary (process _params) x;
470			}
471		}
472	}
473
474	New_butterworth_item = class
475		Menupullright "_Butterworth"
476			"various Butterworth Fourier filters" {
477		High_low_item = class
478			Menuaction "_High or Low Pass"
479				"highpass/lowpass Butterworth Fourier filter" {
480			action x = class
481				_result {
482				_vislevel = 3;
483
484				sense = sense_option;
485				frequency_cutoff = Slider 0.01 0.99 0.5;
486				amplitude_cutoff = Slider 0.01 0.99 0.5;
487				order = Slider 1 10 2;
488
489				// call a freq func with our parameters
490				_params f = f (sense.value + 2)
491						order.value
492						frequency_cutoff.value amplitude_cutoff.value
493						0 0;
494
495				visualize_mask = make_vis _params;
496
497				_result = map_unary (process _params) x;
498			}
499		}
500
501		Ring_item = class
502			Menuaction "_Ring Pass or Ring Reject"
503				"ring pass/reject Butterworth Fourier filter" {
504			action x = class
505				_result {
506				_vislevel = 3;
507
508				sense = sense_option;
509				frequency_cutoff = Slider 0.01 0.99 0.5;
510				amplitude_cutoff = Slider 0.01 0.99 0.5;
511				ring_width = Slider 0.01 0.99 0.5;
512				order = Slider 1 10 2;
513
514				// call a freq func with our parameters
515				_params f = f (sense.value + 8)
516						order.value frequency_cutoff.value ring_width.value
517						amplitude_cutoff.value 0;
518
519				visualize_mask = make_vis _params;
520
521				_result = map_unary (process _params) x;
522			}
523		}
524
525		Band_item = class
526			Menuaction "_Band Pass or Band Reject"
527				"band pass/reject Butterworth Fourier filter" {
528			action x = class
529				_result {
530				_vislevel = 3;
531
532				sense = sense_option;
533				frequency_cutoff_x = Slider 0.01 0.99 0.5;
534				frequency_cutoff_y = Slider 0.01 0.99 0.5;
535				radius = Slider 0.01 0.99 0.5;
536				amplitude_cutoff = Slider 0.01 0.99 0.5;
537				order = Slider 1 10 2;
538
539				// call a freq func with our parameters
540				_params f = f (sense.value + 14) order.value
541						frequency_cutoff_x.value frequency_cutoff_y.value
542						radius.value amplitude_cutoff.value;
543
544				visualize_mask = make_vis _params;
545
546				_result = map_unary (process _params) x;
547			}
548		}
549	}
550}
551
552Filter_enhance_item = class
553	Menupullright "_Enhance" "various enhancement filters" {
554	Falsecolour_item = class
555		Menuaction "_False Colour" "false colour a mono image" {
556		action x = class
557			_result {
558			_vislevel = 3;
559
560			offset = Slider (-255) 255 0;
561			clip = Toggle "Clip colour range" false;
562
563			_result
564				= map_unary process x
565			{
566				process im
567					= falsecolour mono''
568				{
569					mono = colour_transform_to Image_type.B_W im;
570					mono' = mono + offset;
571					mono''
572						= (unsigned char) mono', clip
573						= (unsigned char) (mono' & 0xff);
574				}
575			}
576		}
577	}
578
579	Statistical_diff_item = class
580		Menuaction "_Statistical Difference"
581			"statistical difference of an image" {
582		action x = class
583			_result {
584			_vislevel = 3;
585
586			wsize = Expression "Window size" 11;
587			tmean = Expression "Target mean" 128;
588			mean_weight = Slider 0 1 0.8;
589			tdev = Expression "Target deviation" 50;
590			dev_weight = Slider 0 1 0.8;
591			border = Toggle "Output image matches input image in size" true;
592
593			_result
594				= map_unary process x
595			{
596				process in
597					= Image in''
598				{
599					in' = colour_transform_to Image_type.B_W in.value;
600					fn
601						= im_stdif, border
602						= im_stdif_raw;
603					in'' = fn in'
604						mean_weight.value tmean.expr
605						dev_weight.value tdev.expr
606						wsize.expr wsize.expr;
607				}
608			}
609		}
610	}
611
612	Hist_equal_item = class
613		Menupullright "_Equalise Histogram" "equalise contrast" {
614		Global_item = class
615			Menuaction "_Global" "equalise contrast globally" {
616			action x = map_unary hist_equalize x;
617		}
618
619		Local_item = class
620			Menuaction "_Local" "equalise contrast within a roving window" {
621			action x = class
622				_result {
623				_vislevel = 3;
624
625				width = Expression "Window width" 20;
626				height = Expression "Window height" 20;
627
628				_result
629					= map_unary process x
630				{
631					process in
632						= hist_equalize_local width.expr height.expr in;
633				}
634			}
635		}
636	}
637}
638
639#separator
640
641Filter_tilt_item = class
642	Menupullright "Ti_lt Brightness" "tilt brightness" {
643	Left_right_item = class
644		Menuaction "_Left to Right" "linear left-right brighten" {
645		action x = class
646			_result {
647			_vislevel = 3;
648
649			tilt = Slider (-1) 1 0;
650
651			_result
652					= map_unary tilt_lr x
653			{
654				tilt_lr image
655					= image * scale
656				{
657					ramp = im_fgrey image.width image.height;
658					scale = (ramp - 0.5) * tilt + 1;
659				}
660			}
661		}
662	}
663
664	Top_bottom_item = class
665		Menuaction "_Top to Bottom" "linear top-bottom brighten" {
666		action x = class
667			_result {
668			_vislevel = 3;
669
670			tilt = Slider (-1) 1 0;
671
672			_result
673				= map_unary tilt_tb x
674			{
675				tilt_tb image
676					= image * scale
677				{
678					ramp = rot90
679						(im_fgrey image.height image.width);
680					scale = (ramp - 0.5) * tilt + 1;
681				}
682			}
683		}
684	}
685
686	sep1 = Menuseparator;
687
688	Left_right_cos_item = class
689		Menuaction "Cosine Left-_right" "cosine left-right brighten" {
690		action x = class
691			_result {
692			_vislevel = 3;
693
694			tilt = Slider (-1) 1 0;
695			shift = Slider (-1) 1 0;
696
697			_result
698					= map_unary tilt_lr x
699			{
700				tilt_lr image
701					= image * scale
702				{
703					ramp = im_fgrey image.width image.height - 0.5 -
704							shift.value;
705					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
706				}
707			}
708		}
709	}
710
711	Top_bottom_cos_item = class
712		Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
713		action x = class
714			_result {
715			_vislevel = 3;
716
717			tilt = Slider (-1) 1 0;
718			shift = Slider (-1) 1 0;
719
720			_result
721				= map_unary tilt_tb x
722			{
723				tilt_tb image
724					= image * scale
725				{
726					ramp = rot90 (im_fgrey image.height image.width) - 0.5 -
727							shift.value;
728					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
729				}
730			}
731		}
732	}
733
734	sep2 = Menuseparator;
735
736	Circular_item = class
737		Menuaction "_Circular" "circular brighten" {
738		action x = class
739			_result {
740			_vislevel = 3;
741
742			tilt = Slider (-1) 1 0;
743			hshift = Slider (-1) 1 0;
744			vshift = Slider (-1) 1 0;
745
746			_result
747				= map_unary tilt_tb x
748			{
749				tilt_tb image
750					= image * scale
751				{
752					hramp = im_fgrey image.width image.height - 0.5 -
753						hshift.value;
754					vramp = rot90 (im_fgrey image.height image.width) - 0.5 -
755						vshift.value;
756					ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
757					scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
758				}
759			}
760		}
761	}
762}
763
764Filter_blend_item = class
765	Menupullright "_Blend" "blend objects together" {
766	Slider_blend_item = class
767		Menuaction "_Slider Blend" "blend two objects together with a slider" {
768		action a b = class
769			_result {
770			_vislevel = 3;
771
772			blend = Slider 0 1 0.5;
773
774			_result
775				= map_binary process a b
776			{
777				process im1 im2 = im1 * (1 - blend) + im2 * blend;
778			}
779		}
780	}
781
782	Image_blend_item = class
783		Menuaction "_Image Blend" "use an image to blend two objects" {
784		action a b c
785				= map_trinary process a b c
786		{
787			process a b c
788				= blend condition in1 in2
789			{
790				compare a b
791					// prefer image as the condition
792					= false,
793						!has_image a && has_image b
794					// prefer mono images as the condition
795					= false,
796						has_bands a && has_bands b &&
797						get_bands a > 1 && get_bands b == 1
798					// prefer uchar as the condition
799					= false,
800						has_format a && has_format b &&
801						get_format a > Image_format.UCHAR &&
802							get_format b == Image_format.UCHAR
803					= true;
804				args' = sortc compare [a, b, c];
805				condition = args'?0;
806				in1 = args'?1;
807				in2 = args'?2;
808			}
809		}
810	}
811
812	Line_blend_item = class
813		Menuaction "_Along Line"
814			"blend between image a and image b along a line" {
815		action a b = class
816			_result {
817			_vislevel = 3;
818
819			orientation = Option "Orientation" [
820				"Left to Right",
821				"Top to Bottom"
822			] 0;
823			blend_position = Slider 0 1 0.5;
824			blend_width = Slider 0 1 0.05;
825
826			_result
827				= map_binary process a b
828            {
829                process a b
830					= blend (Image condition) b a
831                {
832					output_width = max_pair a.width b.width;
833					output_height = max_pair a.height b.height;
834					range
835						= output_width, orientation == 0
836						= output_height;
837					blend_position'
838						= floor (range * blend_position.value);
839					blend_width'
840						= 1, blend_width.value == 0
841						= floor (range * blend_width.value);
842                   	start = blend_position' - blend_width' / 2;
843
844					background = (make_xy output_width output_height) >=
845						blend_position';
846                   	ramp
847						= im_grey blend_width' output_height, orientation == 0
848                        = rot90 (im_grey blend_width' output_width);
849					condition
850						= insert_noexpand start 0 ramp background?0,
851							orientation == 0
852						= insert_noexpand 0 start ramp background?1;
853               	}
854			}
855		}
856	}
857}
858
859Filter_overlay_header_item = class
860	Menuaction "_Overlay"
861		"make a colour overlay of two monochrome images" {
862	action  a b = class
863		_result {
864		_vislevel = 3;
865
866		colour = Option "Colour overlay as" [
867			"Green over Red",
868			"Blue over Red",
869			"Red over Green",
870			"Red over Blue",
871			"Blue over Green",
872			"Green over Blue"
873		] 0;
874
875		_result
876			= map_binary overlay a b
877		{
878			overlay a b
879				= image_set_type Image_type.sRGB
880					[(a' ++ b' ++ 0),
881						(a' ++ 0 ++ b'),
882						(b' ++ a' ++ 0),
883						(b' ++ 0 ++ a'),
884						(0 ++ a' ++ b'),
885						(0 ++ b' ++ a')]?colour
886			{
887				a' = colour_transform_to Image_type.B_W a;
888				b' = colour_transform_to Image_type.B_W b;
889			}
890		}
891	}
892}
893
894Filter_colourize_item = class
895	Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
896	action a b = class
897		_result {
898		_vislevel = 3;
899
900		tint = Slider 0 1 0.6;
901
902		_result
903			= map_binary tintit a b
904		{
905			tintit a b
906				= colour_transform_to (get_type colour) colourized'
907			{
908				// get the mono thing first
909				args = sortc (const (is_colour_type @ get_type)) [a, b];
910				mono = args?0;
911				colour = args?1;
912
913				colour' = tint * colour_transform_to Image_type.LAB colour;
914				mono' = colour_transform_to Image_type.B_W mono;
915				colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2;
916				colourized' = image_set_type Image_type.LAB colourized;
917			}
918		}
919	}
920}
921
922Filter_browse_multiband_item = class
923	Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" {
924	Bandwise_item = class
925		Menuaction "B_andwise" "browse through the bands of a multiband image" {
926		action image = class
927        	_result {
928			_vislevel = 3;
929
930        	band = Slider 0 (image.bands - 1) 0;
931       		display = Option "Display as" [
932				"Grey",
933				"Green over Red",
934				"Blue over Red",
935				"Red over Green",
936				"Red over Blue",
937				"Blue over Green",
938				"Green over Blue"
939			] 0;
940
941        	_result
942				= output
943			{
944				down = (int) band.value;
945				up = down + 1;
946				remainder = band.value - down;
947
948				fade x a
949					= Vector [0], x == 0
950					= a * x;
951
952				a = fade remainder image?up;
953				b = fade (1 - remainder) image?down;
954
955				output = [
956					a + b,
957					a ++ b ++ 0,
958					a ++ 0 ++ b,
959					b ++ a ++ 0,
960					b ++ 0 ++ a,
961					0 ++ a ++ b,
962					0 ++ b ++ a
963				] ? display;
964			}
965		}
966	}
967
968	Bitwise_item = class
969		Menuaction "Bi_twise" "browse through the bits of an image" {
970		action x = class
971        	_result {
972			_vislevel = 3;
973
974        	bit
975				= Islider 0 (nbits - 1) (nbits - 1)
976			{
977				nbits
978					= x.bits, is_Image x
979					= 8;
980				Islider f t v = class
981					scope.Slider f t ((int) v) {
982					Slider = Islider;
983				}
984			}
985
986        	_result
987				= map_unary process x
988			{
989				process im = (im & (0x1 << bit.value)) != 0;
990			}
991		}
992	}
993}
994
995#separator
996
997Filter_negative_item = class
998	Menuaction "Photographic _Negative" "swap black and white" {
999	action x
1000		= map_unary invert x
1001	{
1002		invert in
1003			= clip2fmt in.format (colour_transform_to (get_type in) rgb')
1004		{
1005			rgb = colour_transform_to Image_type.sRGB in;
1006			rgb' = 255 - rgb;
1007		}
1008	}
1009}
1010
1011Filter_solarize_item = class
1012	Menuaction "_Solarise" "invert colours above a threshold" {
1013	action x = class
1014		_result {
1015		_vislevel = 3;
1016
1017		kink = Slider 0 1 0.5;
1018
1019		_result
1020			= map_unary process x
1021		{
1022			process image
1023				= hist_map tab'''' image
1024			{
1025				// max pixel value for this format
1026				mx = Image_format.maxval image.format;
1027
1028				// make a LUT ... just 8 and 16 bit
1029				tab
1030					= im_identity_ushort image.bands mx,
1031						image.format ==
1032							Image_format.USHORT
1033					= im_identity image.bands;
1034				tab' = Image tab;
1035
1036				// make basic ^ shape
1037				tab''
1038					= tab' * (1 / kink), tab' < mx * kink
1039					= (mx - tab') / (1 - kink);
1040				tab''' = clip2fmt image.format tab'';
1041
1042				// smooth a bit
1043				mask = matrix_blur (tab'''.width / 8);
1044				tab'''' = convsep mask tab''';
1045			}
1046		}
1047	}
1048}
1049
1050Filter_diffuse_glow_item = class
1051	Menuaction "_Diffuse Glow" "add a halo to highlights" {
1052	action x = class
1053		_result {
1054		_vislevel = 3;
1055
1056		radius = Slider 0 50 5;
1057		highlights = Slider 0 100 95;
1058		glow = Slider 0 1 0.5;
1059		colour = Colour_new_item.Widget_colour_item.action;
1060
1061		_result
1062			= map_unary process x
1063		{
1064			process image
1065				= image'
1066			{
1067				mono = (unsigned char) (colour_transform_to
1068					Image_type.B_W image);
1069				thresh = hist_thresh (highlights.value / 100) mono;
1070				mask = mono > thresh;
1071				blur = convsep (matrix_gaussian_blur
1072					radius.value) mask;
1073				colour' = colour_transform_to
1074					image.type colour;
1075				image' = image + colour' * glow * (blur / 255);
1076			}
1077		}
1078	}
1079}
1080
1081Filter_drop_shadow_item = class
1082	Menuaction "Drop S_hadow" "add a drop shadow to an image" {
1083	action x = class
1084        _result {
1085        _vislevel = 3;
1086
1087        shadow_x = Slider (-50) 50 5;
1088        shadow_y = Slider (-50) 50 5;
1089        shadow_softness = Slider 0 20 5;
1090        bg_colour = Expression "Background colour" 255;
1091        sd_colour = Expression "Shadow colour" 128;
1092        alpha = Toggle "Shadow in alpha channel" false;
1093        transparent = Toggle "Zero pixels are transparent" false;
1094
1095        _result
1096            = map_unary shadow x
1097        {
1098            shadow image
1099            	= Image final
1100            {
1101                blur_size = shadow_softness.value * 2 + 1;
1102
1103                // matrix we blur with to soften shadows
1104                blur_matrix = matrix_gaussian_blur blur_size;
1105                matrix_size = blur_matrix.width;
1106                matrix_radius = (int) (matrix_size / 2) + 1;
1107
1108                // position and size of shadow image in input cods
1109                // before and after fuzzing
1110                shadow_rect = Rect shadow_x.value shadow_y.value
1111					image.width image.height;
1112                fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius;
1113
1114                // size and pos of final image, in input cods
1115                final_rect = image.rect.union fuzzy_shadow_rect;
1116
1117                // hard part of shadow in output cods
1118                shadow_rect' = Rect
1119                    (shadow_rect.left - final_rect.left)
1120                    (shadow_rect.top - final_rect.top)
1121                    shadow_rect.width shadow_rect.height;
1122
1123                // make the shadow mask ... true for parts which cast
1124                // a shadow
1125                mask
1126                    = (foldr1 bitwise_and @ bandsplit) (image.value != 0),
1127                		transparent
1128                    = image_new image.width image.height 1 Image_format.UCHAR
1129                        Image_coding.NOCODING Image_type.B_W 255 0 0;
1130                mask' = embed 0 shadow_rect'.left shadow_rect'.top
1131                        final_rect.width final_rect.height mask;
1132				mask'' = convsep blur_matrix mask';
1133
1134                // use mask to fade between bg and shadow colour
1135                mk_background colour = image_new
1136                	final_rect.width final_rect.height
1137                    image.bands image.format image.coding image.type
1138                    colour 0 0;
1139
1140                bg_image = mk_background bg_colour.expr;
1141                shadow_image = mk_background sd_colour.expr;
1142                bg = blend mask'' shadow_image bg_image;
1143
1144                // make a full size mask
1145                fg_mask = embed 0
1146                    (image.rect.left - final_rect.left)
1147                    (image.rect.top - final_rect.top)
1148                    final_rect.width final_rect.height mask;
1149
1150                // wrap up the input image ... put the shadow colour
1151                // around it, so if we are outputting a separate
1152                // alpha the shadow colour will be set correctly
1153                fg = insert (image.rect.left - final_rect.left)
1154                    (image.rect.top - final_rect.top)
1155                    image.value shadow_image;
1156
1157                final
1158                    // make a separate alpha
1159                    = fg ++ mask'', alpha
1160
1161                    // paste image over shadow
1162                    = if fg_mask then fg else bg;
1163            }
1164        }
1165    }
1166}
1167
1168Filter_paint_text_item = class
1169	Menuaction "_Paint Text" "paint text into an image" {
1170	action x
1171		= paint_position, is_Group x
1172		= paint_area
1173	{
1174		paint_area = class
1175			_result {
1176			_check_args = [
1177				[x, "x", check_Image]
1178			];
1179			_vislevel = 3;
1180
1181			text = String "Text to paint" "<i>Hello</i> world!";
1182			font = Fontname "Use font" Workspaces.Preferences.PAINTBOX_FONT;
1183			align = Option "Alignment" ["Left", "Centre", "Right"] 0;
1184			dpi = Expression "DPI" 300;
1185			colour = Expression "Text colour" 255;
1186			place = Region x (x.width / 4) (x.height / 4)
1187				(x.width / 2) (x.height / 2);
1188
1189			_result
1190				= insert_noexpand place.left place.top (blend txt' fg place) x
1191			{
1192				fg = image_new place.width place.height x.bands x.format
1193					x.coding x.type colour.expr 0 0;
1194				txt = Image (im_text text.value font.value
1195					place.width align.value (to_real dpi));
1196	            bg = im_black place.width place.height 1;
1197				txt' = insert_noexpand 0 0 txt bg;
1198			}
1199		}
1200
1201		paint_position = class
1202			_result {
1203			_vislevel = 3;
1204
1205			text = Pattern_images_item.Text_item.action;
1206			colour = Expression "Text colour" 255;
1207			position = Option "Position" [
1208				"North-west",
1209				"North",
1210				"North-east",
1211				"West",
1212				"Centre",
1213				"East",
1214				"South-west",
1215				"South",
1216				"South-east",
1217				"Specify in pixels"
1218			] 4;
1219			left = Expression "Pixels from left" 0;
1220			top = Expression "Pixels from top" 0;
1221
1222			_result
1223				= map_unary paint x
1224			{
1225				paint image
1226					= insert_noexpand x' y' place' image
1227				{
1228					xr = image.width - text.width;
1229					yr = image.height - text.height;
1230					x
1231						= left.expr, position == 9
1232						= [0, xr / 2, xr]?(position % 3);
1233					y
1234						= top.expr, position == 9
1235						= [0, yr / 2, yr]?(position / 3);
1236					x' = range 0 x (image.width - 1);
1237					y' = range 0 y (image.height - 1);
1238					w' = range 1 text.width (image.width - x');
1239					h' = range 1 text.height (image.height - y');
1240
1241					place = extract_area x' y' w' h' image;
1242					text' = insert_noexpand 0 0 text (im_black w' h' 1);
1243					fg = image_new w' h' image.bands image.format
1244						image.coding image.type colour.expr 0 0;
1245					place' = blend text' fg place;
1246				}
1247			}
1248		}
1249	}
1250}
1251