1
Filter_conv_item = class
2
Menupullright "_Convolution" "various spatial convolution filters" {
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]];
14
Menuaction "_Blur" "3x3 blur of image" {
15
action x = map_unary (conv filter_blur) x;
19
Menuaction "_Sharpen" "3x3 sharpen of image" {
20
action x = map_unary (conv filter_sharp) x;
24
Menuaction "_Emboss" "1 pixel displace emboss" {
25
action x = map_unary (conv filter_emboss) x;
28
Laplacian_item = class
29
Menuaction "_Laplacian" "3x3 laplacian edge detect" {
30
action x = map_unary (conv filter_laplacian) x;
34
Menuaction "So_bel" "3x3 Sobel edge detect" {
39
= abs (a - 128) + abs (b - 128)
41
a = conv filter_sobel im;
42
b = conv (rot270 filter_sobel) im;
47
/* 3x3 line detect of image
48
diagonals should be scaled down by root(2) I guess
52
Menuaction "Li_ne Detect" "3x3 line detect" {
57
= foldr1 max_pair images
59
masks = take 4 (iterate rot45 filter_lindet);
60
images = map (converse conv im) masks;
66
Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
71
size = Option "Radius" [
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;
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'';
100
sep1 = Menuseparator;
102
Custom_blur_item = class
103
Menuaction "Custom B_lur / Sharpen"
104
"blur or sharpen with tuneable parameters" {
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" [
116
prec = Option "Precision" ["Int", "Float"] 0;
119
= map_unary process x
122
= clip2fmt blur.format proc
125
= matrix_blur r.value, shape.value == 0
126
= matrix_gaussian_blur r.value;
127
blur = [convsep, convsepf]?prec mask in;
129
= in + fac * (in - blur), type == 1
130
= blur * fac + in * (1 - fac);
136
Custom_conv_item = class
137
Menuaction "Custom C_onvolution"
138
"convolution filter with tuneable parameters" {
143
matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
145
= Toggle "Seperable convolution" false,
146
matrix.width == 1 || matrix.height == 1
148
type = Option "Convolution type" ["Int", "Float"] 0;
151
= map_unary process x
157
= im_conv, !separable && type == 0
158
= im_convsep, separable && type == 0
159
= im_convf, !separable && type == 1
160
= im_convsepf, separable && type == 1
162
in' = conv_fn in.value matrix;
169
Filter_rank_item = class
170
Menupullright "_Rank" "various rank filters" {
172
Menuaction "_Median" "3x3 median rank filter" {
173
action x = map_unary (rank 3 3 5) x;
176
Image_rank_item = class
177
Menuaction "_Image Rank" "pixelwise rank a list or group of images" {
183
= Expression "Rank" ((int) (guess_size / 2))
187
= len x.value, is_Group x
191
// can't really iterate over groups ... since we allow a group
193
_result = rank_image select x;
197
Custom_rank_item = class
198
Menuaction "Custom _Rank" "rank filter with tuneable parameters" {
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));
209
= map_unary process x
212
= rank window_width window_height select in;
218
Filter_morphology_item = class
219
Menupullright "_Morphology" "various morphological filters" {
220
/* Some useful masks.
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]];
227
Threshold_item = Select_item.Threshold_item;
229
sep1 = Menuseparator;
232
Menupullright "_Dilate" "morphological dilate" {
234
Menuaction "_8-connected" "dilate with an 8-connected mask" {
235
action x = map_unary (dilate mask8) x;
239
Menuaction "_4-connected" "dilate with a 4-connected mask" {
240
action x = map_unary (dilate mask4) x;
245
Menupullright "_Erode" "morphological erode" {
247
Menuaction "_8-connected" "erode with an 8-connected mask" {
248
action x = map_unary (erode mask8) x;
252
Menuaction "_4-connected" "erode with a 4-connected mask" {
253
action x = map_unary (erode mask4) x;
257
Custom_morph_item = class
258
Menuaction "Custom _Morphology"
259
"convolution morphological operator" {
265
type = Option "Operation" ["Erode", "Dilate"] 1;
266
apply = Expression "Number of times to apply mask" 1;
274
fatmask = (iterate (dilate mask) mask)?(to_real apply - 1);
277
= im_erode image.value fatmask, type.value == 0
278
= im_dilate image.value fatmask;
284
sep2 = Menuseparator;
287
Menuaction "_Open" "open with an 8-connected mask" {
288
action x = map_unary (dilate mask8 @ erode mask8) x;
292
Menuaction "_Close" "close with an 8-connected mask" {
293
action x = map_unary (erode mask8 @ dilate mask8) x;
297
Menuaction "C_lean" "remove 8-connected isolated points" {
301
clean x = x ^ erode mask1 x;
306
Menuaction "_Thin" "thin once" {
308
= map_unary thinall x
310
masks = take 8 (iterate rot45 thin);
311
thin1 m x = x ^ erode m x;
312
thinall x = foldr thin1 x masks;
317
Filter_fourier_item = class
318
Menupullright "_Fourier" "various Fourier filters" {
321
sense_option = Option "Sense" [
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);
330
// make the process function
332
= (Image @ fn) (im_flt_image_freq in.value);
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" {
343
sense = sense_option;
344
fc = Scale "Frequency cutoff" 0.01 0.99 0.5;
346
// call a freq func with our parameters
347
_params f = f sense.value fc.value 0 0 0 0;
349
visualize_mask = make_vis _params;
351
_result = map_unary (process _params) x;
356
Menuaction "_Ring Pass or Ring Reject"
357
"ring pass/reject ideal Fourier filter" {
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;
366
// call a freq func with our parameters
367
_params f = f (sense.value + 6) fc.value
370
visualize_mask = make_vis _params;
372
_result = map_unary (process _params) x;
377
Menuaction "_Band Pass or Band Reject"
378
"band pass/reject ideal Fourier filter" {
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;
388
// call a freq func with our parameters
389
_params f = f (sense.value + 12) fcx.value fcy.value
392
visualize_mask = make_vis _params;
394
_result = map_unary (process _params) x;
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" {
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;
412
// call a freq func with our parameters
413
_params f = f (sense.value + 4) fc.value
416
visualize_mask = make_vis _params;
418
_result = map_unary (process _params) x;
423
Menuaction "_Ring Pass or Ring Reject"
424
"ring pass/reject Gaussian Fourier filter" {
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;
434
// call a freq func with our parameters
435
_params f = f (sense.value + 10) fc.value
436
rw.value ac.value 0 0;
438
visualize_mask = make_vis _params;
440
_result = map_unary (process _params) x;
445
Menuaction "_Band Pass or Band Reject"
446
"band pass/reject Gaussian Fourier filter" {
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;
457
// call a freq func with our parameters
458
_params f = f (sense.value + 16) fcx.value fcy.value
461
visualize_mask = make_vis _params;
463
_result = map_unary (process _params) x;
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" {
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;
483
// call a freq func with our parameters
484
_params f = f (sense.value + 2) o.value fc.value ac.value
487
visualize_mask = make_vis _params;
489
_result = map_unary (process _params) x;
494
Menuaction "_Ring Pass or Ring Reject"
495
"ring pass/reject Butterworth Fourier filter" {
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;
506
// call a freq func with our parameters
507
_params f = f (sense.value + 8) o.value fc.value rw.value
510
visualize_mask = make_vis _params;
512
_result = map_unary (process _params) x;
517
Menuaction "_Band Pass or Band Reject"
518
"band pass/reject Butterworth Fourier filter" {
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;
530
// call a freq func with our parameters
531
_params f = f (sense.value + 14) o.value fcx.value fcy.value
534
visualize_mask = make_vis _params;
536
_result = map_unary (process _params) x;
542
Filter_enhance_item = class
543
Menupullright "_Enhance" "various enhancement filters" {
544
Falsecolour_item = class
545
Menuaction "_False Colour" "false colour a mono image" {
550
o = Scale "Offset" (-255) 255 0;
551
clip = Toggle "Clip colour range" false;
554
= map_unary process x
559
mono = colour_transform_to Image_type.B_W im;
562
= (unsigned char) mono', clip
563
= (unsigned char) (mono' & 0xff);
569
Statistical_diff_item = class
570
Menuaction "_Statistical Difference"
571
"statistical difference of an image" {
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;
584
= map_unary process x
589
in' = colour_transform_to Image_type.B_W in.value;
594
mean_weight.value tmean.expr
595
dev_weight.value tdev.expr
596
wsize.expr wsize.expr;
602
Hist_equal_item = class
603
Menupullright "_Equalise Histogram" "equalise contrast" {
605
Menuaction "_Global" "equalise contrast globally" {
606
action x = map_unary hist_equalize x;
610
Menuaction "_Local" "equalise contrast within a roving window" {
615
window_width = Expression "Window width" 20;
616
window_height = Expression "Window height" 20;
619
= map_unary process x
622
= hist_equalize_local
623
window_width.expr window_height.expr in;
630
Filter_correlate_item = class
631
Menupullright "Spatial _Correlation" "calculate correlation surfaces" {
632
Correlate_item = class
633
Menuaction "_Correlate" "calculate correlation coefficient" {
635
= map_binary corr a b
639
a.width <= b.width && a.height <= b.height
644
Correlate_fast_item = class
645
Menuaction "_Simple Difference"
646
"calculate sum of squares of differences" {
648
= map_binary corr a b
651
= correlate_fast a b,
652
a.width <= b.width && a.height <= b.height
653
= correlate_fast b a;
658
Filter_greyc_item = class
659
Menupullright "_GREYCstoration" "noise-removing filter" {
661
Menuaction "Denoise" "Noise-removing filter" {
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;
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;
689
Menuaction "Enlarge" "Enlarge image" {
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;
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);
723
Filter_tilt_item = class
724
Menupullright "Ti_lt Brightness" "tilt brightness" {
725
Left_right_item = class
726
Menuaction "_Left to Right" "linear left-right brighten" {
731
tilt = Scale "Left-right tilt" (-1) 1 0;
734
= map_unary tilt_lr x
739
ramp = im_fgrey image.width image.height;
740
scale = (ramp - 0.5) * tilt + 1;
746
Top_bottom_item = class
747
Menuaction "_Top to Bottom" "linear top-bottom brighten" {
752
tilt = Scale "Top-bottom tilt" (-1) 1 0;
755
= map_unary tilt_tb x
761
(im_fgrey image.height image.width);
762
scale = (ramp - 0.5) * tilt + 1;
768
sep1 = Menuseparator;
770
Left_right_cos_item = class
771
Menuaction "Cosine Left-_right" "cosine left-right brighten" {
776
tilt = Scale "Left-right tilt" (-1) 1 0;
777
shift = Scale "Shift by" (-1) 1 0;
780
= map_unary tilt_lr x
785
ramp = im_fgrey image.width image.height - 0.5 -
787
scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
793
Top_bottom_cos_item = class
794
Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
799
tilt = Scale "Top-bottom tilt" (-1) 1 0;
800
shift = Scale "Shift by" (-1) 1 0;
803
= map_unary tilt_tb x
808
ramp = rot90 (im_fgrey image.height image.width) - 0.5 -
810
scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
816
sep2 = Menuseparator;
818
Circular_item = class
819
Menuaction "_Circular" "circular brighten" {
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;
829
= map_unary tilt_tb x
834
hramp = im_fgrey image.width image.height - 0.5 -
836
vramp = rot90 (im_fgrey image.height image.width) - 0.5 -
838
ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
839
scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
846
Filter_blend_item = class
847
Menupullright "_Blend" "blend objects together" {
848
Scale_blend_item = class
849
Menuaction "_Scale" "blend two objects together with a scale" {
854
p = Scale "Blend position" 0 1 0.5;
857
= map_binary process a b
859
process im1 im2 = im1 * (1 - p.value) + im2 * p.value;
864
Image_blend_item = class
865
Menuaction "_Image" "use an image to blend two objects" {
870
i = Toggle "Invert mask" false;
873
= map_trinary process a b c
876
= blend condition in1 in2, !i
877
= blend (invert condition) in1 in2
880
// prefer image as the condition
882
!has_image a && has_image b
883
// prefer mono images as the condition
885
has_bands a && has_bands b &&
886
get_bands a > 1 && get_bands b == 1
887
// prefer uchar as the condition
889
has_format a && has_format b &&
890
get_format a > Image_format.UCHAR &&
891
get_format b == Image_format.UCHAR
893
[condition, in1, in2] = sortc compare [a, b, c];
899
Line_blend_item = class
900
Menuaction "_Along Line"
901
"blend between image a and image b along a line" {
906
orientation = Option "Orientation" [
910
blend_position = Scale "Blend position" 0 1 0.5;
911
blend_width = Scale "Blend width" 0 1 0.05;
914
= map_binary process a b
917
= blend (Image condition) b a
919
output_width = max_pair a.width b.width;
920
output_height = max_pair a.height b.height;
922
= output_width, orientation == 0
925
= floor (range * blend_position.value);
927
= 1, blend_width.value == 0
928
= floor (range * blend_width.value);
929
start = blend_position' - blend_width' / 2;
931
background = (make_xy output_width output_height) >=
934
= im_grey blend_width' output_height, orientation == 0
935
= rot90 (im_grey blend_width' output_width);
937
= insert_noexpand start 0 ramp background?0,
939
= insert_noexpand 0 start ramp background?1;
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
952
scale_mask im opacity
953
= (unsigned char) (to_real opacity / 255 * im);
956
intensity = colour_transform_to Image_type.B_W;
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.
963
blend_normal mask opacity fg bg
964
= blend (scale_mask mask opacity) fg bg;
966
blend_iflighter mask opacity fg bg
967
= blend (if fg' > bg' then mask' else 0) fg bg
971
mask' = scale_mask mask opacity ;
974
blend_ifdarker mask opacity fg bg
975
= blend (if fg' < bg' then mask' else 0) fg bg
979
mask' = scale_mask mask opacity ;
982
blend_multiply mask opacity fg bg
983
= blend (scale_mask mask opacity) fg' bg
988
blend_add mask opacity fg bg
991
fg' = opacity / 255 * fg + bg;
994
blend_subtract mask opacity fg bg
997
fg' = bg - opacity / 255 * fg;
1000
blend_screen mask opacity fg bg
1003
fg' = 255 - (255 - bg) * (255 - (opacity / 255 * fg)) / 255;
1006
blend_burn mask opacity fg bg
1007
= blend mask fg'' bg
1009
// fades to white which has no effect.
1010
fg' = (255 - opacity) + opacity * fg / 255;
1011
fg'' = 255 - 255 * (255 - bg) / fg';
1014
blend_softlight mask opacity fg bg
1015
= blend mask' fg' bg
1017
mask' = scale_mask mask opacity;
1018
fg' = (2 * bg * fg + bg * bg * (1 - 2 * fg / 255)) / 255;
1021
blend_hardlight mask opacity fg bg
1022
= blend mask' fg' bg
1024
mask' = scale_mask mask opacity;
1026
= 2 / 255 * fg * bg, bg < 129
1027
= 255 - 2 * (255 - bg) * (255 - fg) / 255;
1030
blend_lighten mask opacity fg bg
1031
= blend mask' fg' bg
1033
mask' = scale_mask mask opacity;
1034
fg' = if bg < fg then fg else bg;
1037
blend_darken mask opacity fg bg
1038
= blend mask' fg' bg
1040
mask' = scale_mask mask opacity;
1041
fg' = if bg > fg then fg else bg;
1044
blend_dodge mask opacity fg bg
1045
= blend mask fg'' bg
1047
// one added to avoid divide by zero
1048
fg' = 1 + 255 - (opacity / 255 * fg);
1049
fg'' = bg * 255 / fg';
1052
blend_reflect mask opacity fg bg
1053
= blend mask' fg' bg
1055
mask' = scale_mask mask opacity;
1056
fg' = bg * bg / (255 - fg);
1059
blend_freeze mask opacity fg bg
1060
= blend mask' fg' bg
1062
mask' = scale_mask mask opacity;
1063
fg' = 255 - (255 - bg) * (255 - bg) / (1 + fg);
1066
blend_or mask opacity fg bg
1067
= bg | (unsigned char) fg'
1069
mask' = scale_mask mask opacity;
1070
fg' = fg * mask' / 255;
1073
blend_and mask opacity fg bg
1074
= bg & (unsigned char) fg'
1076
mask' = scale_mask mask opacity;
1077
fg' = fg * mask' / 255;
1099
// names we show the user for blend types
1101
[_ "Normal", NORMAL],
1102
[_ "If Lighter", IFLIGHTER],
1103
[_ "If Darker", IFDARKER],
1104
[_ "Multiply", MULTIPLY],
1106
[_ "Subtract", SUBTRACT],
1107
[_ "Screen", SCREEN],
1109
[_ "Soft Light", SOFTLIGHT],
1110
[_ "Hard Light", HARDLIGHT],
1111
[_ "Lighten", LIGHTEN],
1112
[_ "Darken", DARKEN],
1114
[_ "Reflect", REFLECT],
1115
[_ "Freeze", FREEZE],
1116
[_ "Bitwise OR", OR],
1117
[_ "Bitwise AND", AND]
1120
// functions we call for each blend type
1122
[NORMAL, blend_normal],
1123
[IFLIGHTER, blend_iflighter],
1124
[IFDARKER, blend_ifdarker],
1125
[MULTIPLY, blend_multiply],
1127
[SUBTRACT, blend_subtract],
1128
[SCREEN, blend_screen],
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],
1141
// make sure im has an alpha channel (set opaque if it hasn't)
1143
= im, im.bands == 4 || im.bands == 2
1146
// make sure im has no alpha channel
1148
= extract_bands 0 3 im, im.bands == 4
1149
= im?0, im.bands == 2
1152
// does im have al alpha channel?
1153
has_alpha im = im.bands == 2 || im.bands == 4;
1155
// get the alpha (set opaque if no alpha)
1157
= img'?3, img.bands == 4
1160
img' = put_alpha img;
1163
// add an alpha ... cast the alpha image to match the main image
1164
append_alpha im alpha
1165
= im ++ clip2fmt im.format alpha;
1167
// makes fg the same size as bg, displaced with u, v pixel offset
1169
= insert_noexpand u v fg bg'
1171
bg' = image_new bg.width bg.height
1172
fg.bands fg.format fg.coding fg.type 0 0 0;
1175
action bg fg = class
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;
1185
= append_alpha blended merged_alpha, has_alpha bg
1188
// displace and resize fg (need to displace alpha too)
1189
fg' = moveit (put_alpha fg) bg hmove vmove;
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);
1196
merged_alpha = get_alpha bg | get_alpha fg';
1199
blended = (actions.lookup 0 1 method.value_thing)
1200
(get_alpha fg') opacity.value fg'' bg';
1206
Filter_overlay_header_item = class
1207
Menuaction "_Overlay"
1208
"make a colour overlay of two monochrome images" {
1213
colour = Option "Colour overlay as" [
1218
_ "Blue over Green",
1223
= map_binary overlay a b
1226
= image_set_type Image_type.sRGB
1232
(0 ++ b' ++ a')]?colour
1234
a' = colour_transform_to Image_type.B_W a;
1235
b' = colour_transform_to Image_type.B_W b;
1241
Filter_colourize_item = class
1242
Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
1247
tint = Scale "Tint" 0 1 0.6;
1250
= map_binary tintit a b
1253
= colour_transform_to (get_type colour) colourized'
1255
// get the mono thing first
1257
sortc (const (is_colour_type @ get_type)) [a, b];
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;
1268
Filter_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
1276
band = Scale "Band" 0 (image.bands - 1) 0;
1277
display = Option "Display as" [
1283
_ "Blue over Green",
1290
down = (int) band.value;
1292
remainder = band.value - down;
1295
= Vector [0], x == 0
1298
a = fade remainder image?up;
1299
b = fade (1 - remainder) image?down;
1314
Bitwise_item = class
1315
Menuaction "Bi_twise" "browse through the bits of an image" {
1321
= Islider "Bit" 0 (nbits - 1) (nbits - 1)
1324
= x.bits, is_Image x
1326
Islider c f t v = class
1327
scope.Scale c f t ((int) v) {
1333
= map_unary process x
1335
process im = (im & (0x1 << bit.value)) != 0;
1343
Filter_negative_item = class
1344
Menuaction "Photographic _Negative" "swap black and white" {
1346
= map_unary invert x
1349
= clip2fmt in.format (colour_transform_to (get_type in) rgb')
1351
rgb = colour_transform_to Image_type.sRGB in;
1357
Filter_solarize_item = class
1358
Menuaction "_Solarise" "invert colours above a threshold" {
1363
kink = Scale "Kink" 0 1 0.5;
1366
= map_unary process x
1369
= hist_map tab'''' image
1371
// max pixel value for this format
1372
mx = Image_format.maxval image.format;
1374
// make a LUT ... just 8 and 16 bit
1376
= im_identity_ushort image.bands mx,
1379
= im_identity image.bands;
1382
// make basic ^ shape
1384
= tab' * (1 / kink), tab' < mx * kink
1385
= (mx - tab') / (1 - kink);
1386
tab''' = clip2fmt image.format tab'';
1389
mask = matrix_blur (tab'''.width / 8);
1390
tab'''' = convsep mask tab''';
1396
Filter_diffuse_glow_item = class
1397
Menuaction "_Diffuse Glow" "add a halo to highlights" {
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;
1408
= map_unary process x
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);
1425
Filter_drop_shadow_item = class
1426
Menuaction "Drop S_hadow" "add a drop shadow to an image" {
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;
1440
= map_unary shadow x
1445
blur_size = ss.value * 2 + 1;
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;
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;
1458
// size and pos of final image, in input cods
1459
final_rect = image.rect.union fuzzy_shadow_rect;
1461
// hard part of shadow in output cods
1463
(shadow_rect.left - final_rect.left)
1464
(shadow_rect.top - final_rect.top)
1465
shadow_rect.width shadow_rect.height;
1467
// make the shadow mask ... true for parts which cast
1470
= (foldr1 bitwise_and @ bandsplit) (image.value != 0),
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';
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
1484
bg_image = mk_background bg_colour.expr;
1485
shadow_image = mk_background sd_colour.expr;
1486
bg = blend mask'' shadow_image bg_image;
1488
// make a full size mask
1490
(image.rect.left - final_rect.left)
1491
(image.rect.top - final_rect.top)
1492
final_rect.width final_rect.height mask;
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;
1502
// make a separate alpha
1503
= fg ++ mask'', alpha
1505
// paste image over shadow
1506
= if fg_mask then fg else bg;
1512
Filter_paint_text_item = class
1513
Menuaction "_Paint Text" "paint text into an image" {
1515
= paint_position, is_Group x
1521
[x, "x", check_Image]
1522
] ++ super._check_args;
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);
1534
= insert_noexpand place.left place.top (blend txt' fg place) x
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;
1545
paint_position = class
1549
text = Pattern_images_item.Text_item.action;
1550
colour = Expression "Text colour" 255;
1551
position = Option "Position" [
1561
_ "Specify in pixels"
1563
left = Expression "Pixels from left" 0;
1564
top = Expression "Pixels from top" 0;
1570
= insert_noexpand x' y' place' image
1572
xr = image.width - text.width;
1573
yr = image.height - text.height;
1575
= left.expr, position == 9
1576
= [0, xr / 2, xr]?(position % 3);
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');
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;