Filter_conv_item = class
    Menupullright "_Convolution" "various spatial convolution filters" {
    /* Some useful masks.
     */
    filter_blur = Matrix_con 9 0 [[1, 1, 1], [1, 1, 1], [1, 1, 1]];
    filter_sharp = Matrix_con 8 0 [[-1, -1, -1], [-1, 16, -1], [-1, -1, -1]];
    filter_emboss = Matrix_con 1 128 [[-1, 0], [0, 1]];
    filter_laplacian = Matrix_con 1 128
        [[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]];
    filter_lindet = Matrix_con 1 0 [[1, 1, 1], [-2, -2, -2], [1, 1, 1]];

    Blur_item = class
        Menuaction "_Blur" "3x3 blur of image" {
        action x = map_unary (conv [$precision => "integer"] filter_blur) x;
    }

    Sharpen_item = class
        Menuaction "_Sharpen" "3x3 sharpen of image" {
        action x = map_unary (conv [$precision => "integer"] filter_sharp) x;
    }

    Usharp_item = class
        Menuaction "_Unsharp Mask" "cored sharpen of L only in LAB image" {
        action x = class
            _result {
            _vislevel = 3;

            size = Option "Radius" [
                "3 pixels",
                "5 pixels",
                "7 pixels",
                "9 pixels",
                "11 pixels",
                "51 pixels"
            ] 0;

            st = Scale "Smoothness threshold" 0 5 2;
            bm = Scale "Brighten by at most" 1 50 10;
            dm = Scale "Darken by at most" 1 50 20;
            fs = Scale "Sharpen flat areas by" 0 5 0.5;
            js = Scale "Sharpen jaggy areas by" 0 5 1;

            _result
                = map_unary process x
            {
                process in
                    = Image in'''
                {
                    in' = colourspace [] Interpretation.LABS in.value;
                    in'' = sharpen [
                        $sigma => [1, 1.5, 2, 3, 4, 10]?size,
                        $x1 => to_real st,
                        $y2 => to_real bm,
                        $y3 => to_real dm,
                        $m1 => to_real fs,
                        $m2 => to_real js
                    ] in';
                    in''' = colourspace [] (get_interpretation in) in'';
                }
            }
        }
    }

    Emboss_item = class
        Menuaction "_Emboss" "1 pixel displace emboss" {
        action x = map_unary (conv [$precision => "integer"] filter_emboss) x;
    }

    Laplacian_item = class
        Menuaction "_Laplacian" "3x3 laplacian edge detect" {
        action x
            = map_unary (conv [$precision => "integer"] filter_laplacian) x;
    }

    Sobel_item = class
        Menuaction "So_bel" "3x3 Sobel edge detect" {
        action x = map_unary sobel x;
    }

    Prewitt_item = class
        Menuaction "Prewitt" "3x3 Prewitt edge detect" {
        action x = map_unary prewitt x;
    }

    Scharr_item = class
        Menuaction "Scharr" "3x3 Scharr edge detect" {
        action x = map_unary scharr x;
    }

/* 3x3 line detect of image
diagonals should be scaled down by root(2) I guess
Kirk
*/
    Linedet_item = class
        Menuaction "Li_ne Detect" "3x3 line detect" {
        action x
            = map_unary lindet x
        {
            lindet im
                = foldr1 maxpair images
            {
                masks = take 4 (iterate rot45 filter_lindet);
                images
                    = map (converse (conv [$precision => $integer]) im) masks;
            }
        }
    }

    Canny_item = class
        Menuaction "Canny" "Canny edge detector" {
        action x = class
            _result {
            _vislevel = 3;

            sigma = Scale "Sigma" 0.1 10 2;
            prec = Precision.picker $INTEGER;

            _result
                = canny [
                    $sigma => to_real sigma,
                    $precision => prec.value_key
                  ] x;
        }
    }

    sep1 = Menuseparator;

    Custom_blur_item = class
        Menuaction "Custom Blur / Sharpen"
            "blur or sharpen with tuneable parameters" {
        action x = class
            _result {
            _vislevel = 3;

            type = Option "Type" ["Blur", "Sharpen"] 0;
            r = Scale "Radius" 1 100 1;
            fac = Scale "Amount" 0 1 1;
            shape = Option "Mask shape" [
                "Square",
                "Gaussian"
            ] 0;
            prec = Precision.picker $INTEGER;
            layers = Scale "Layers" 1 100 10;

            _result
                = map_unary process x
            {
                process in
                    = cast blur.format proc
                {
                    mask
                        = matrix_blur r, shape.value == 0
                        = gaussmat [
                            $precision => prec.value_key,
                            $separable => true
                          ] (r / 2) 0.1;

                    blur = convsep [
                        $precision => prec.value_key,
                        $layers => layers.value
                    ] mask in;

                    proc
                        = in + fac * (in - blur), type == 1
                        = blur * fac + in * (1 - fac);
                }
            }
        }
    }

    Custom_conv_item = class
        Menuaction "Custom C_onvolution"
            "convolution filter with tuneable parameters" {
        action x = class
            _result {
            _vislevel = 3;

            matrix = Matrix_con 1 0 [[0, 0, 0], [0, 1, 0], [0, 0, 0]];
            separable
                = Toggle "Seperable convolution" false,
                    matrix.width == 1 || matrix.height == 1
                = false;
            prec = Precision.picker $INTEGER;
            rotate = Option "Rotate" [
                "Don't rotate",
                "4 x 45 degrees",
                "8 x 45 degrees",
                "2 x 90 degrees"
            ] 0;

            _result
                = map_unary process x
            {
                process in
                    = in.Image in'
                {
                    conv_fn
                        = vips_compass [
                            $times => 4,
                            $angle => "d45",
                            $precision => prec.value_key
                          ],
                            !separable && rotate == 1
                        = vips_compass [
                            $times => 8,
                            $angle => "d45",
                            $precision => prec.value_key
                          ],
                            !separable && rotate == 2
                        = vips_compass [
                            $times => 2,
                            $angle => "d90",
                            $combine => "sum",
                            $precision => prec.value_key
                          ],
                            !separable && rotate == 3
                        = vips_conv [
                            $precision => prec.value_key
                          ],
                            !separable
                        = vips_convsep [
                            $precision => prec.value_key
                          ];

                    in' = conv_fn in.value matrix;
                }
            }
        }
    }
}

Filter_rank_item = class
    Menupullright "_Rank" "various rank filters" {
    Median_item = class
        Menuaction "_Median" "3x3 median rank filter" {
        action x = map_unary (rank 3 3 4) x;
    }

    Image_rank_item = class
        Menuaction "_Image Rank" "pixelwise rank a list or group of images" {
        action x = class
            _result {
            _vislevel = 3;

            select
                = Expression "Rank" ((int) (guess_size / 2))
            {
                guess_size
                    = len x, is_list x
                    = len x.value, is_Group x
                    = 0;
            }

            // can't really iterate over groups ... since we allow a group
            // argument
            _result = rank_image select x;
        }
    }

    Custom_rank_item = class
        Menuaction "Custom _Rank" "rank filter with tuneable parameters" {
        action x = class
            _result {
            _vislevel = 3;

            window_width = Expression "Window width" 3;
            window_height = Expression "Window height" 3;
            select = Expression "Rank"
                ((int) ((to_real window_width * to_real window_height) / 2));

            _result
                = map_unary process x
            {
                process in
                    = rank window_width window_height select in;
            }
        }
    }
}

Filter_morphology_item = class
    Menupullright "_Morphology" "various morphological filters" {
    /* Some useful masks.
     */
    mask8 = Matrix_mor [[255, 255, 255], [255, 255, 255], [255, 255, 255]];
    mask4 = Matrix_mor [[128, 255, 128], [255, 255, 255], [128, 255, 128]];
    mask1 = Matrix_mor [[0, 0, 0], [0, 255, 0], [0, 0, 0]];
    thin = Matrix_mor [[0, 0, 0], [128, 255, 128], [255, 255, 255]];

    Threshold_item = Image_select_item.Threshold_item;

    sep1 = Menuseparator;

    Dilate_item = class
        Menupullright "_Dilate" "morphological dilate" {
        Dilate8_item = class
            Menuaction "_8-connected" "dilate with an 8-connected mask" {
            action x = map_unary (dilate mask8) x;
        }

        Dilate4_item = class
            Menuaction "_4-connected" "dilate with a 4-connected mask" {
            action x = map_unary (dilate mask4) x;
        }
    }

    Erode_item = class
        Menupullright "_Erode" "morphological erode" {
        Erode8_item = class
            Menuaction "_8-connected" "erode with an 8-connected mask" {
            action x = map_unary (erode mask8) x;
        }

        Erode4_item = class
            Menuaction "_4-connected" "erode with a 4-connected mask" {
            action x = map_unary (erode mask4) x;
        }
    }

    Custom_morph_item = class
        Menuaction "Custom _Morphology"
            "convolution morphological operator" {
        action x = class
            _result {
            _vislevel = 3;

            mask = mask4;
            type = Option "Operation" ["Erode", "Dilate"] 1;
            apply = Expression "Number of times to apply mask" 1;

            _result
                = map_unary morph x
            {
                morph image
                    = Image value'
                {
                    fatmask = (iterate (dilate mask) mask)?(to_real apply - 1);

                    value'
                        = erode fatmask image.value, type.value == 0
                        = dilate fatmask image.value;
                }
            }
        }
    }

    sep2 = Menuseparator;

    Open_item = class
        Menuaction "_Open" "open with an 8-connected mask" {
        action x = map_unary (dilate mask8 @ erode mask8) x;
    }

    Close_item = class
        Menuaction "_Close" "close with an 8-connected mask" {
        action x = map_unary (erode mask8 @ dilate mask8) x;
    }

    Clean_item = class
        Menuaction "C_lean" "remove 8-connected isolated points" {
        action x
            = map_unary clean x
        {
            clean x = x ^ erode mask1 x;
        }
    }

    Thin_item = class
        Menuaction "_Thin" "thin once" {
        action x
            = map_unary thinall x
        {
            masks = take 8 (iterate rot45 thin);
            thin1 m x = x ^ erode m x;
            thinall x = foldr thin1 x masks;
        }
    }

}

Filter_fourier_item = class
    Menupullright "_Fourier" "various Fourier filters" {

    New_ideal_item = class
        Menupullright "_Ideal" "various ideal Fourier filters" {

        High_low_item = class
            Menuaction "_High or Low Pass"
                "highpass/lowpass ideal Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Ideal.High_low $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }

        Ring_item = class
            Menuaction "_Ring Pass or Ring Reject"
                "ring pass/reject ideal Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Ideal.Ring $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }

        Band_item = class
            Menuaction "_Band Pass or Band Reject"
                "band pass/reject ideal Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Ideal.Band $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }
    }

    New_gaussian_item = class
        Menupullright "_Gaussian" "various Gaussian Fourier filters" {
        High_low_item = class
            Menuaction "_High or Low Pass"
                "highpass/lowpass Gaussian Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Gaussian.High_low $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }

        Ring_item = class
            Menuaction "_Ring Pass or Ring Reject"
                "ring pass/reject Gaussian Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Gaussian.Ring $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }

        Band_item = class
            Menuaction "_Band Pass or Band Reject"
                "band pass/reject Gaussian Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Gaussian.Band $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }
    }

    New_butterworth_item = class
        Menupullright "_Butterworth"
            "various Butterworth Fourier filters" {
        High_low_item = class
            Menuaction "_High or Low Pass"
                "highpass/lowpass Butterworth Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Butterworth.High_low $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }

        Ring_item = class
            Menuaction "_Ring Pass or Ring Reject"
                "ring pass/reject Butterworth Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Butterworth.Ring $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }

        Band_item = class
            Menuaction "_Band Pass or Band Reject"
                "band pass/reject Butterworth Fourier filter" {
            action x = class
                _result {
                _vislevel = 3;

                visualize = Fourier.visualise (mask._generate 64 64);
                mask = Fourier.Butterworth.Band $PASS;

                _result = map_unary (Fourier.process mask._generate) x;
            }
        }
    }
}

Filter_enhance_item = class
    Menupullright "_Enhance" "various enhancement filters" {
    Falsecolour_item = class
        Menuaction "_False Colour" "false colour a mono image" {
        action x = class
            _result {
            _vislevel = 3;

            o = Scale "Offset" (-255) 255 0;
            clip = Toggle "Clip colour range" false;

            _result
                = map_unary process x
            {
                process im
                    = falsecolour mono''
                {
                    mono = colourspace [] Interpretation.B_W im;
                    mono' = mono + o;
                    mono''
                        = (unsigned char) mono', clip
                        = (unsigned char) (mono' & 0xff);
                }
            }
        }
    }

    Statistical_diff_item = class
        Menuaction "_Statistical Difference"
            "statistical difference of an image" {
        action x = class
            _result {
            _vislevel = 3;

            wsize = Expression "Window size" 11;
            tmean = Expression "Target mean" 128;
            tmean_weight = Scale "Mean weight" 0 1 0.8;
            tdev = Expression "Target deviation" 50;
            tdev_weight = Scale "Deviation weight" 0 1 0.8;

            _result
                = map_unary process x
            {
                process in
                    = Image in''
                {
                    in' = colourspace [] Interpretation.B_W in.value;
                    in'' = vips_stdif [
                        $s0 => to_real tdev,
                        $b => to_real tdev_weight,
                        $m0 => to_real tmean,
                        $a => to_real tmean_weight
                    ] in' wsize.expr wsize.expr;
                }
            }
        }
    }

    Hist_equal_item = class
        Menupullright "_Equalise Histogram" "equalise contrast" {
        Global_item = class
            Menuaction "_Global" "equalise contrast globally" {
            action x = map_unary hist_equalize x;
        }

        Local_item = class
            Menuaction "_Local" "equalise contrast within a roving window" {
            action x = class
                _result {
                _vislevel = 3;

                window_width = Expression "Window width" 20;
                window_height = Expression "Window height" 20;
                max_slope = Scale "Maxium slope" 0 10 0;

                _result
                    = map_unary process x
                {
                    process in
                        = hist_equalize_local [
                            $max_slope => to_real max_slope
                          ] window_width window_height in;
                }
            }
        }
    }
}

Filter_correlate_item = class
    Menupullright "Spatial _Correlation" "calculate correlation surfaces" {
    Correlate_item = class
        Menuaction "_Correlate" "calculate correlation coefficient" {
        action a b
            = map_binary corr a b
        {
            corr a b
                = correlate a b,
                    a.width <= b.width && a.height <= b.height
                = correlate b a;
        }
    }

    Correlate_fast_item = class
        Menuaction "_Simple Difference"
            "calculate sum of squares of differences" {
        action a b
            = map_binary corr a b
        {
            corr a b
                = correlate_fast a b,
                    a.width <= b.width && a.height <= b.height
                = correlate_fast b a;
        }
    }
}

Filter_hough_item = class
    Menupullright "_Hough Transform" "transform to parameter space" {
    Line_item = class
        Menuaction "_Line" "find straight line Hough transform" {
        action a = class
            _result {
            _vislevel = 3;

            pspace_width = Expression "Parameter space width" 64;
            pspace_height = Expression "Parameter space height" 64;

            _result
                = map_unary line a
            {
                line a
                    = hough_line
                        (to_real pspace_width) (to_real pspace_height) a;
            }
        }
    }

    Circle_item = class
        Menuaction "_Circle" "find circle Hough transform" {
        action a = class
            _result {
            _vislevel = 3;

            scale = Expression "Scale down parameter space by" 10;
            min_radius = Expression "Minimum radius" 10;
            max_radius = Expression "Maximum radius" 30;

            _result
                = map_unary circle a
            {
                circle a
                    = hough_circle (to_real scale) (to_real min_radius)
                        (to_real max_radius) a;
            }
        }
    }
}

Filter_coordinate_item = class
    Menupullright "_Coordinate Transform" "various coordinate transforms" {
    // run a function which wants a complex arg on a non-complex two-band
    // image
    run_cmplx fn x
        = re x' ++ im x'
    {
        x' = fn (x?0, x?1);
    }

    Polar_item = class
        Menuaction "_Polar" "transform to polar coordinates" {
        action a = class
            _result {
            _vislevel = 3;

            interp = Interpolate.picker $BILINEAR;

            _result
                = map_unary to_polar a
            {
                to_polar im
                    = mapim [$interpolate => inter] map' im
                {
                    inter = Interpolate._new [] interp.value_key;

                    // xy image, origin in the centre, scaled to fit image to
                    // a circle
                    xy = xyz [] im.width im.height;
                    xy' = xy - Vector [im.width / 2, im.height / 2];
                    scale = min [im.width, im.height] / im.width;
                    xy'' = 2 * xy' / scale;

                    // to polar, scale vertical axis to 360 degrees
                    map = run_cmplx polar xy'';
                    map' = map * Vector [1, im.height / 360];
                }
            }
        }
    }

    Rectangular_item = class
        Menuaction "_Rectangular" "transform to rectangular coordinates" {
        action a = class
            _result {
            _vislevel = 3;

            interp = Interpolate.picker $BILINEAR;

            _result
                = map_unary to_rect a
            {
                to_rect im
                    = mapim [$interpolate => inter] map'' im
                {
                    inter = Interpolate._new [] interp.value_key;

                    // xy image, vertical scaled to 360 degrees
                    xy = xyz [] im.width im.height;
                    xy' = xy * Vector [1, 360 / im.height];

                    // to rect, scale to image rect
                    map = run_cmplx rectangular xy';
                    scale = min [im.width, im.height] / im.width;
                    map' = map * scale / 2;

                    map'' = map' + Vector [im.width / 2, im.height / 2];
                }
            }
        }
    }
}

#separator

Filter_tilt_item = class
    Menupullright "Ti_lt Brightness" "tilt brightness" {
    Left_right_item = class
        Menuaction "_Left to Right" "linear left-right brighten" {
        action x = class
            _result {
            _vislevel = 3;

            tilt = Scale "Left-right tilt" (-1) 1 0;

            _result
                    = map_unary tilt_lr x
            {
                tilt_lr image
                    = image * scale
                {
                    ramp = grey [] image.width image.height;
                    scale = (ramp - 0.5) * tilt + 1;
                }
            }
        }
    }

    Top_bottom_item = class
        Menuaction "_Top to Bottom" "linear top-bottom brighten" {
        action x = class
            _result {
            _vislevel = 3;

            tilt = Scale "Top-bottom tilt" (-1) 1 0;

            _result
                = map_unary tilt_tb x
            {
                tilt_tb image
                    = image * scale
                {
                    ramp = rot90 (grey [] image.height image.width);
                    scale = (ramp - 0.5) * tilt + 1;
                }
            }
        }
    }

    sep1 = Menuseparator;

    Left_right_cos_item = class
        Menuaction "Cosine Left-_right" "cosine left-right brighten" {
        action x = class
            _result {
            _vislevel = 3;

            tilt = Scale "Left-right tilt" (-1) 1 0;
            shift = Scale "Shift by" (-1) 1 0;

            _result
                    = map_unary tilt_lr x
            {
                tilt_lr image
                    = image * scale
                {
                    ramp = grey [] image.width image.height - 0.5 - shift.value;
                    scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
                }
            }
        }
    }

    Top_bottom_cos_item = class
        Menuaction "Cosine Top-_bottom" "cosine top-bottom brighten" {
        action x = class
            _result {
            _vislevel = 3;

            tilt = Scale "Top-bottom tilt" (-1) 1 0;
            shift = Scale "Shift by" (-1) 1 0;

            _result
                = map_unary tilt_tb x
            {
                tilt_tb image
                    = image * scale
                {
                    ramp = rot90 (grey [] image.height image.width) - 0.5 -
                            shift.value;
                    scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
                }
            }
        }
    }

    sep2 = Menuseparator;

    Circular_item = class
        Menuaction "_Circular" "circular brighten" {
        action x = class
            _result {
            _vislevel = 3;

            tilt = Scale "Tilt" (-1) 1 0;
            hshift = Scale "Horizontal shift by" (-1) 1 0;
            vshift = Scale "Vertical shift by" (-1) 1 0;

            _result
                = map_unary tilt_tb x
            {
                tilt_tb image
                    = image * scale
                {
                    hramp = grey [] image.width image.height - 0.5 -
                        hshift.value;
                    vramp = rot90 (grey [] image.height image.width) - 0.5 -
                        vshift.value;
                    ramp = (hramp ** 2 + vramp ** 2) ** 0.5;
                    scale = 0.5 * tilt.value * cos (ramp * 180) + 1;
                }
            }
        }
    }
}

Filter_blend_item = class
    Menupullright "_Blend" "blend objects together" {
    Scale_blend_item = class
        Menuaction "_Scale" "blend two objects together with a scale" {
        action a b = class
            _result {
            _vislevel = 3;

            p = Scale "Blend position" 0 1 0.5;

            _result
                = map_binary process a b
            {
                process im1 im2 = im1 * (1 - p.value) + im2 * p.value;
            }
        }
    }

    Image_blend_item = class
        Menuaction "_Image" "use an image to blend two objects" {
        action a b c = class
            _result {
            _vislevel = 3;

            i = Toggle "Invert mask" false;

            _result
                = map_trinary process a b c
            {
                process a b c
                    = blend condition in1 in2, !i
                    = blend (invert condition) in1 in2
                {
                    compare a b
                        // prefer image as the condition
                        = false,
                            !has_image a && has_image b
                        // prefer mono images as the condition
                        = false,
                            has_bands a && has_bands b &&
                            get_bands a > 1 && get_bands b == 1
                        // prefer uchar as the condition
                        = false,
                            has_format a && has_format b &&
                            get_format a > Format.UCHAR &&
                                get_format b == Format.UCHAR
                        = true;
                    [condition, in1, in2] = sortc compare [a, b, c];
                }
            }
        }
    }

    Line_blend_item = class
        Menuaction "_Along Line"
            "blend between image a and image b along a line" {
        action a b = class
            _result {
            _vislevel = 3;

            direction = Direction.picker $HORIZONTAL;
            blend_position = Scale "Blend position" 0 1 0.5;
            blend_width = Scale "Blend width" 0 1 0.05;

            _result
                = map_binary process a b
            {
                process a b
                    = blend (Image condition) b a
                {
                    output_width = maxpair a.width b.width;
                    output_height = maxpair a.height b.height;
                    is_horizontal = direction.value_key == Direction.HORIZONTAL;
                    range
                        = output_width, is_horizontal
                        = output_height;
                    blend_position'
                        = floor (range * blend_position.value);
                    blend_width'
                        = 1, blend_width.value == 0
                        = floor (range * blend_width.value);
                    start = blend_position' - blend_width' / 2;

                    background = (xyz [] output_width output_height) >=
                        blend_position';
                    ramp
                        = grey [$uchar => true] blend_width' output_height, is_horizontal
                        = rot90 (grey [$uchar => true] blend_width' output_width);
                    condition
                        = insert [] start 0 ramp background?0, is_horizontal
                        = insert [] 0 start ramp background?1;
                }
            }
        }
    }
}

Filter_overlay_header_item = class
    Menuaction "_Overlay"
        "make a colour overlay of two monochrome images" {
    action  a b = class
        _result {
        _vislevel = 3;

        colour = Option "Colour overlay as" [
            _ "Green over Red",
            _ "Blue over Red",
            _ "Red over Green",
            _ "Red over Blue",
            _ "Blue over Green",
            _ "Green over Blue"
        ] 0;

        _result
            = map_binary overlay a b
        {
            overlay a b
                = set_header $interpretation $srgb
                    [(a' ++ b' ++ 0),
                        (a' ++ 0 ++ b'),
                        (b' ++ a' ++ 0),
                        (b' ++ 0 ++ a'),
                        (0 ++ a' ++ b'),
                        (0 ++ b' ++ a')]?colour
            {
                a' = colourspace [] Interpretation.B_W a;
                b' = colourspace [] Interpretation.B_W b;
            }
        }
    }
}

Filter_colourize_item = class
    Menuaction "_Colourize" "use a colour image or patch to tint a mono image" {
    action a b = class
        _result {
        _vislevel = 3;

        tint = Scale "Tint" 0 1 0.6;

        _result
            = map_binary tintit a b
        {
            tintit a b
                = colourspace [] (get_interpretation colour) colourized'
            {
                // get the mono thing first
                [mono, colour]
                    = sortc (const is_colour_interpretation) [a, b]
                {
                    is_colour_interpretation x
                        = Interpretation_3ary.has_value (get_interpretation x);
                }

                colour' = tint * colourspace [] Interpretation.LAB colour;
                mono' = colourspace [] Interpretation.B_W mono;
                colourized = (mono' / 2.55) ++ colour'?1 ++ colour'?2;
                colourized' = set_header $interpretation $lab colourized;
            }
        }
    }
}

Filter_browse_multiband_item = class
    Menupullright "Bro_wse" "browse though an image, bitwise or bandwise" {
    Bandwise_item = class
        Menuaction "B_andwise" "browse through the bands of a multiband image" {
        action image = class
            _result {
            _vislevel = 3;

            band = Scale "Band" 0 (image.bands - 1) 0;
            display = Option "Display as" [
            _ "Grey",
            _ "Green over Red",
            _ "Blue over Red",
            _ "Red over Green",
            _ "Red over Blue",
            _ "Blue over Green",
            _ "Green over Blue"
        ] 0;

            _result
                = output
            {
                down = (int) band.value;
                up = down + 1;
                remainder = band.value - down;

                fade x a
                    = Vector [0], x == 0
                    = a * x;

                a = fade remainder image?up;
                b = fade (1 - remainder) image?down;

                output = [
                    a + b,
                    a ++ b ++ 0,
                    a ++ 0 ++ b,
                    b ++ a ++ 0,
                    b ++ 0 ++ a,
                    0 ++ a ++ b,
                    0 ++ b ++ a
                ] ? display;
            }
        }
    }

    Bitwise_item = class
        Menuaction "Bi_twise" "browse through the bits of an image" {
        action x = class
            _result {
            _vislevel = 3;

            bit
                = Islider "Bit" 0 (nbits - 1) (nbits - 1)
            {
                nbits
                    = log ((Format._maxval (get_format x)) + 1) / log 2,
                        has_format x
                    = 8;

                Islider c f t v = class
                    scope.Scale c f t ((int) v) {
                    Scale = Islider;
                }
            }

            _result
                = map_unary process x
            {
                process im = (im & (0x1 << bit.value)) != 0;
            }
        }
    }
}

#separator

Filter_negative_item = class
    Menuaction "Photographic _Negative" "swap black and white" {
    action x
        = map_unary invert x
    {
        invert in
            = cast in.format (colourspace [] (get_interpretation in) rgb')
        {
            rgb = colourspace [] Interpretation.sRGB in;
            rgb' = 255 - rgb;
        }
    }
}

Filter_solarize_item = class
    Menuaction "_Solarise" "invert colours above a threshold" {
    action x = class
        _result {
        _vislevel = 3;

        kink = Scale "Kink" 0 1 0.5;

        _result
            = map_unary process x
        {
            process image
                = maplut [] tab''' image
            {
                // max pixel value for this format
                mx = Format._maxval image.format;

                // make a LUT ... just 8 and 16 bit
                tab = Image (vips_identity [
                    $bands => image.bands,
                    $ushort => image.format == Format.USHORT,
                    $size => mx
                ]);

                // make basic ^ shape
                tab'
                    = tab * (1 / kink), tab < mx * kink
                    = (mx - tab) / (1 - kink);
                tab'' = cast image.format tab';

                // smooth a bit
                mask = matrix_blur (tab''.width / 8);
                tab''' = convsep [] mask tab'';
            }
        }
    }
}

Filter_diffuse_glow_item = class
    Menuaction "_Diffuse Glow" "add a halo to highlights" {
    action x = class
        _result {
        _vislevel = 3;

        r = Scale "Radius" 0 50 5;
        highlights = Scale "Highlights" 0 100 95;
        glow = Scale "Glow" 0 1 0.5;
        colour = Colour_new_item.Widget_colour_item.action;

        _result
            = map_unary process x
        {
            process image
                = image'
            {
                mono = (unsigned char) (colourspace []
                    Interpretation.B_W image);
                thresh = hist_thresh (highlights.value / 100) mono;
                mask = mono > thresh;
                gmat = gaussmat [
                    $precision => "integer",
                    $separable => true
                ] (to_real r / 3) 0.1;
                blur = convsep [] gmat mask;
                colour' = colourspace [] image.interpretation colour;
                image' = image + colour' * glow * (blur / 255);
            }
        }
    }
}

Filter_drop_shadow_item = class
    Menuaction "Drop S_hadow" "add a drop shadow to an image" {
    action x = class
        _result {
        _vislevel = 3;

        sx = Scale "Horizontal shadow" (-50) 50 5;
        sy = Scale "Vertical shadow" (-50) 50 5;
        ss = Scale "Shadow softness" 0 20 5;
        bg_colour = Expression "Background colour" 255;
        sd_colour = Expression "Shadow colour" 128;
        alpha = Toggle "Shadow in alpha channel" false;
        transparent = Toggle "Zero pixels are transparent" false;

        _result
            = map_unary shadow x
        {
            shadow image
                = Image final
            {
                // matrix we blur with to soften shadows
                blur_size = ss.value * 2 + 1;
                blur_matrix = gaussmat [
                    $precision => "integer",
                    $separable => true
                ] blur_size 0.1;
                matrix_size = blur_matrix.width;
                matrix_radius = (int) (matrix_size / 2) + 1;

                // position and size of shadow image in input cods
                // before and after fuzzing
                shadow_rect = Rect sx.value sy.value image.width image.height;
                fuzzy_shadow_rect = shadow_rect.margin_adjust matrix_radius;

                // size and pos of final image, in input cods
                image_rect = Rect 0 0 image.width image.height;
                final_rect = image_rect + fuzzy_shadow_rect;

                // hard part of shadow in output cods
                shadow_rect' = Rect
                    (shadow_rect.left - final_rect.left)
                    (shadow_rect.top - final_rect.top)
                    shadow_rect.width shadow_rect.height;

                // make the shadow mask ... true for parts which cast
                // a shadow
                mask
                    = (foldr1 bitwise_and @ bandsplit) (image.value != 0),
                        transparent
                    = image_new [
                        $interpretation => Interpretation.B_W,
                        $pixel => 255
                    ] image.width image.height;
                mask' = embed [] shadow_rect'.left shadow_rect'.top
                        final_rect.width final_rect.height mask;
                mask'' = convsep [] blur_matrix mask';

                // use mask to fade between bg and shadow colour
                mk_background colour = image_new_from_image [
                    $pixel => colour
                ] image final_rect.width final_rect.height;

                bg_image = mk_background bg_colour.expr;
                shadow_image = mk_background sd_colour.expr;
                bg = blend mask'' shadow_image bg_image;

                // make a full size mask
                fg_mask = embed []
                    (image_rect.left - final_rect.left)
                    (image_rect.top - final_rect.top)
                    final_rect.width final_rect.height mask;

                // wrap up the input image ... put the shadow colour
                // around it, so if we are outputting a separate
                // alpha the shadow colour will be set correctly
                fg = insert [$expand => true]
                    (image_rect.left - final_rect.left)
                    (image_rect.top - final_rect.top)
                    image.value shadow_image;

                final
                    // make a separate alpha
                    = fg ++ mask'', alpha
                    // paste image over shadow
                    = if fg_mask then fg else bg;
            }
        }
    }
}

Filter_paint_text_item = class
    Menuaction "_Paint text" "paint text into an image" {
    action x = class
        _result {
        _vislevel = 3;

        place = Region x (x.width / 4) (x.height / 4)
            (x.width / 2) (x.height / 2);
        text = Text true "<span color='red'>Hello</span>, world!";

        _result
            = composite [
                $x => place.left,
                $y => place.top
            ] Blend.OVER x
                (Image (text._generate place.width place.height));
    }
}
