
Colour_new_item = class
    Menupullright (_ "New") (_ "make a patch of colour") {
    Widget_colour_item = class
        Menuaction (_ "Colour") (_ "make a patch of colour") {
        action = Colour_picker $LAB [50,0,0];
    }

    sep1 = Menuseparator;

    LAB_colour = class
        Menuaction (_ "CIE Lab Picker") (_ "pick colour in CIE Lab space") {
        action = widget $LAB [50, 0, 0];

        // ab_slice size
        size = 512;

        // range of values ... +/- 128 for ab
        range = 256;

        // map xy in slice image to ab and back
        xy2ab x = x / (size / range) - 128;
        ab2xy a = (a + 128) * (size / range);

        widget space default_value = class
            Colour space _result {
            _vislevel = 3;

            [_L, _a, _b] = default_value;
            L = Scale "Lightness" 0 100 _L;
            ab_slice = Image (lab_slice size L.value);
            point = Mark ab_slice (ab2xy _a) (ab2xy _b);

            _result = [L.value, xy2ab point.left, xy2ab point.top];

            Colour_edit colour_space value = widget colour_space value;
        }
    }

    CCT_colour = class
        Menuaction (_ "Colour from CCT") (_ "pick colour by CCT") {
        action = widget 6500;

        widget x = class
            _result {
            _vislevel = 3;

            T = Scale "CCT" 1800 25000 x;

            _result = colour_from_temp (to_real T);

            Colour_edit space value
                = widget (temp_from_colour (Colour space value));
        }
    }
}

Colour_to_colour_item = class
    Menuaction (_ "Convert to Colour") (_ "convert anything to a Colour") {
    action x = to_colour x;
}

#separator

Colour_convert_item = class
    Menupullright (_ "Colourspace") (_ "convert between colour spaces") {
    conv dest x = class
        _result {
        _vislevel = 3;

        to = Interpretation_nary.picker (Interpretation.get_key dest);

        _result = map_unary (colourspace [] to.value_key) x;
    }

    Mono_item = class
        Menuaction (_ "B-W, 8-bit") (_ "convert to B-W colourspace") {
        action x = conv Interpretation.B_W x;
    }

    GREY16_item = class
        Menuaction (_ "B-W, 16-bit") (_ "convert to GREY16 colourspace") {
        action x = conv Interpretation.GREY16 x;
    }

    scRGB_item = class
        Menuaction (_ "_scRGB") (_ "convert to scRGB colourspace") {
        action x = conv Interpretation.scRGB x;
    }

    sRGB_item = class
        Menuaction (_ "_sRGB, 8-bit") (_ "convert to sRGB colourspace") {
        action x = conv Interpretation.sRGB x;
    }

    RGB16_item = class
        Menuaction (_ "sRGB, 16-bit") (_ "convert to RGB16 colourspace") {
        action x = conv Interpretation.RGB16 x;
    }

    HSV_item = class
        Menuaction (_ "HSV") (_ "convert to HSV colourspace") {
        action x = conv Interpretation.HSV x;
    }

    XYZ_item = class
        Menuaction (_ "_XYZ") (_ "convert to XYZ colourspace") {
        action x = conv Interpretation.XYZ x;
    }

    Yxy_item = class
        Menuaction (_ "_Yxy") (_ "convert to Yxy colourspace") {
        action x = conv Interpretation.YXY x;
    }

    Lab_item = class
        Menuaction (_ "_Lab") (_ "convert to Lab colourspace (float Lab)") {
        action x = conv Interpretation.LAB x;
    }

    OKLab_item = class
        Menuaction (_ "OKLab") (_ "convert to OKLab colourspace") {
        action x = conv Interpretation.OKLAB x;
    }

    LabQ_item = class
        Menuaction (_ "LabQ") (_ "convert to LabQ colourspace (32-bit Lab)") {
        action x = conv Interpretation.LABQ x;
    }

    LabS_item = class
        Menuaction (_ "LabS") (_ "convert to LabS colourspace (48-bit Lab)") {
        action x = conv Interpretation.LABS x;
    }

    LCh_item = class
        Menuaction (_ "LCh") (_ "convert to LCh colourspace") {
        action x = conv Interpretation.LCH x;
    }

    OKLCh_item = class
        Menuaction (_ "OKLCh") (_ "convert to OKLCh colourspace") {
        action x = conv Interpretation.OKLCH x;
    }

    CMC_item = class
        Menuaction (_ "_CMC") (_ "convert to CMC(l:c) colourspace") {
        action x = conv Interpretation.CMC x;
    }
}

/* mark objects as being in various colourspaces
 */
Colour_tag_item = class
    Menupullright (_ "Tag as")
        (_ "tag object as being in various colour spaces") {
    spaces = Interpretation_nary;

    tag dest x = class
        _result {
        _vislevel = 3;

        to = spaces.picker (spaces.get_key dest);

        _result = map_unary (set_header $interpretation to.value_key) x;
    }

    Mono_item = class
        Menuaction (_ "B-W, 8-bit") (_ "tag as being in b-w colourspace") {
        action x = tag Interpretation.B_W x;
    }

    GREY16_item = class
        Menuaction (_ "B-W, 16-bit") (_ "tag as being in GREY16 colourspace") {
        action x = tag Interpretation.GREY16 x;
    }

    scRGB_item = class
        Menuaction (_ "scRGB") (_ "tag as being in scRGB colourspace") {
        action x = tag Interpretation.scRGB x;
    }

    sRGB_item = class
        Menuaction (_ "sRGB, 8-bit") (_ "tag as being in sRGB colourspace") {
        action x = tag Interpretation.sRGB x;
    }

    RGB16_item = class
        Menuaction (_ "sRGB, 16-bit") (_ "tag as being in RGB16 colourspace") {
        action x = tag Interpretation.RGB16 x;
    }

    HSV_item = class
        Menuaction (_ "HSV") (_ "tag as being in HSV colourspace") {
        action x = tag Interpretation.HSV x;
    }

    XYZ_item = class
        Menuaction (_ "XYZ") (_ "tag as being in XYZ colourspace") {
        action x = tag Interpretation.XYZ x;
    }

    Yxy_item = class
        Menuaction (_ "Yxy") (_ "tag as being in Yxy colourspace") {
        action x = tag Interpretation.YXY x;
    }

    Lab_item = class
        Menuaction (_ "Lab")
            (_ "tag as being in Lab colourspace (float Lab)") {
        action x = tag Interpretation.LAB x;
    }

    OKLab_item = class
        Menuaction (_ "OKLab")
            (_ "tag as being in OKLab colourspace") {
        action x = tag Interpretation.OKLAB x;
    }

    LabQ_item = class
        Menuaction (_ "LabQ")
            (_ "tag as being in LabQ colourspace (32-bit Lab)") {
        action x = tag Interpretation.LABQ x;
    }

    LabS_item = class
        Menuaction (_ "LabS")
            (_ "tag as being in LabS colourspace (48-bit Lab)") {
        action x = tag Interpretation.LABS x;
    }

    LCh_item = class
        Menuaction (_ "LCh") (_ "tag as being in LCh colourspace") {
        action x = tag Interpretation.LCH x;
    }

    OKLCh_item = class
        Menuaction (_ "OKLCh") (_ "tag as being in OKLCh colourspace") {
        action x = tag Interpretation.OKLCH x;
    }

    CMC_item = class
        Menuaction (_ "CMC") (_ "tag as being in CMC(l:c) colourspace") {
        action x = tag Interpretation.CMC x;
    }
}

Colour_temperature_item = class
    Menupullright (_ "Temperature")
        (_ "colour temperature conversions") {
    Whitepoint_item = class
        Menuaction (_ "Move whitepoint") (_ "simple ratio move") {
        action x = class
            _result {
            _vislevel = 3;

            from = Whitepoint.picker "D65";
            to = Whitepoint.picker "D50";

            _result
                = map_unary process x
            {
                process im
                    = im''
                {
                    im' = colourspace [] Interpretation.XYZ im;
                    im'' = im' * (to.value_key / from.value_key);
                }
            }
        }
    }

    D65_to_D50_item = class
        Menupullright (_ "D65 to D50") (_ "complex conversion") {
        XYZ_minimal_item = class
            Menuaction (_ "Minimal")
                (_ "D65 to D50 using the minimal 3x3 matrix in XYZ") {
            action x
                = map_unary process x
            {
                process im
                    = im''
                {
                    im' = colourspace [] Interpretation.XYZ im;
                    im'' = recomb D652D50_direct im';
                }
            }
        }

        Bradford_item = class
            Menuaction (_ "Bradford") (_ "D65 to D50 in Bradford cone space") {
            action x
                = map_unary process x
            {
                process im
                    = im''
                {
                    im' = colourspace [] Interpretation.XYZ im;
                    im'' = D652D50brad im';
                }
            }
        }
    }

    D50_to_D65_item = class
        Menupullright (_ "D50 to D65") (_ "complex conversion") {
        XYZ_minimal_item = class
            Menuaction (_ "Minimal")
                (_ "D50 to D65 using the minimal 3x3 matrix in XYZ") {
            action x
                = map_unary process x
            {
                process im
                    = im''
                {
                    im' = colourspace [] Interpretation.XYZ im;
                    im'' = recomb D502D65_direct im';
                }
            }
        }

        Bradford_item = class
            Menuaction (_ "Bradford") (_ "D60 to D65 in Bradford cone space") {
            action x
                = map_unary process x
            {
                process im
                    = im''
                {
                    im' = colourspace [] Interpretation.XYZ im;
                    im'' = D502D65brad im';
                }
            }
        }
    }

    Lab_to_D50XYZ_item = class
        Menuaction (_ "Lab to D50 XYZ")
            (_ "Lab to XYZ with a D50 whitepoint") {
        action x
            = map_unary cvt x
        {
            // to D65 lab, then to XYZ with D50 whitepoint
            cvt = Image @
                vips_Lab2XYZ [$temp => Whitepoint.D50.value] @
                colourspace [] Interpretation.LAB;
        }
    }

    D50XYZ_to_Lab_item = class
        Menuaction (_ "D50 XYZ to Lab")
            (_ "XYZ to Lab with a D50 whitepoint") {
        action x
            = map_unary cvt x
        {
            // from XYZ with D50 whitepoint to Lab (which is always D65 in
            // libvips)
            cvt = Image @ vips_XYZ2Lab [$temp => Whitepoint.D50.value];
        }
    }

    sep1 = Menuseparator;

    CCT_item = class
        Menuaction (_ "Calculate temperature")
            (_ "estimate CCT using the McCamy approximation") {
        action z = map_unary temp_from_colour z;
    }

    Colour_item = Colour_new_item.CCT_colour;
}

Colour_icc_item = class
    Menupullright (_ "ICC") (_ "transform with ICC profiles") {
    print_profile =
        "$VIPSHOME/share/$PACKAGE/data/cmyk.icm";
    monitor_profile =
        "$VIPSHOME/share/$PACKAGE/data/sRGB.icm";
    guess_profile image
        = print_profile,
            has_interpretation image &&
            get_interpretation image == Interpretation.CMYK &&
            has_bands image &&
            get_bands image >= 4
        = monitor_profile;
    render_intents = Intent.picker $ABSOLUTE;

    Export_item = class
        Menuaction (_ "Export") (_ "export from PCS to device space") {
        action x = class
            _result {
            _vislevel = 3;

            profile = Pathname (_ "Output profile") print_profile;
            depth = Option (_ "Output depth") [_ "8 bit", _ "16 bit"] 0;
            intent = render_intents;
            bpc = Toggle (_ "Black point compensation") false;

            _result
                = map_unary process x
            {
                process image
                    = icc_export [
                        $output_profile => expand profile.value,
                        $depth => [8, 16]?depth,
                        $black_point_compensation => bpc.value,
                        $intent => intent.value_key
                      ] lab
                {
                    lab = colourspace [] Interpretation.LABQ image;
                }
            }
        }
    }

    Import_item = class
        Menuaction (_ "Import") (_ "import from device space to PCS") {
        action x = class
            _result {
            _vislevel = 3;

            embedded = Toggle (_ "Use embedded profile if possible") false;
            profile = Pathname (_ "Default input profile") (guess_profile x);
            intent = render_intents;
            pcs = PCS.picker $LAB;
            bpc = Toggle (_ "Black point compensation") false;

            _result
                = icc_import [
                    $input_profile => expand profile.value,
                    $intent => intent.value_key,
                    $pcs => pcs.value_key,
                    $black_point_compensation => bpc.value,
                    $embedded => to_bool embedded
                  ] x;
        }
    }

    Transform_item = class
        Menuaction (_ "Transform") (_ "transform between two device spaces") {
        action x = class
            _result {
            _vislevel = 3;

            embedded = Toggle (_ "Use embedded profile if possible") false;
            in_profile = Pathname (_ "Input profile") (guess_profile x);
            out_profile = Pathname (_ "Output profile") print_profile;
            depth = Option (_ "Output depth") [_ "8 bit", _ "16 bit"] 0;
            intent = render_intents;
            bpc = Toggle (_ "Black point compensation") false;

            _result
                = icc_transform [
                    $embedded => to_bool embedded,
                    $input_profile => expand in_profile.value,
                    $output_profile => expand out_profile.value,
                    $depth => [8, 16]?depth,
                    $black_point_compensation => bpc.value,
                    $intent => intent.value_key
                  ] out_profile.value x;
        }
    }
}

Colour_rad_item = class
    Menupullright (_ "Radiance")
        (_ "convert to and from Radiance packed format") {
    Unpack_item = class
        Menuaction (_ "Unpack")
            (_ "unpack Radiance format to float") {
        action x = map_unary rad2float x;
    }

    Pack_item = class
        Menuaction (_ "Pack")
            (_ "pack 3-band float to Radiance format") {
        action x = map_unary float2rad x;
    }
}

Colour_uhdr_item = class
    Menupullright (_ "UHDR")
        (_ "convert to and from UHDR format") {

    Apply_gainmap_item = class
        Menuaction (_ "Apply embedded gainmap") (_ "apply gainmap to image") {
        action x = map_unary uhdr2scRGB x;
    }

    sep1 = Menuseparator;

    Get_gainmap_item = class
        Menuaction (_ "Get embedded gainmap") (_ "get gainmap from image") {
        action x = map_unary (get_header "gainmap") x;
    }

    Set_gainmap_item = class
        Menuaction (_ "Set embedded gainmap") (_ "set gainmap on image") {
        action x y
            = result
        {
            result = map_binary (set_meta "gainmap") x' y';

            // get the wider (probably the main one) image second
            [x', y'] = sortc (\i\j get_width i <= get_width j) [x, y];
        }
    }

    sep2 = Menuseparator;

    Get_uhdr_metadata_item = class
        Menuaction (_ "Get UHDR metadata") (_ "get UHDR metadata from image") {
        action x = class {
            _vislevel = 2;

            mincb = Expression "Max content boost"
                (get_header "gainmap-max-content-boost" x);
            maxcb = Expression "Min content boost"
                (get_header "gainmap-min-content-boost" x);
            gamma = Expression "Gamma"
                (get_header "gainmap-gamma" x);
            offsetsdr = Expression "Offset SDR"
                (get_header "gainmap-offset-sdr" x);
            offsethdr = Expression "Offset HDR"
                (get_header "gainmap-offset-hdr" x);
            hdrcapmin = Expression "HDR capacity min"
                (get_header "gainmap-hdr-capacity-min" x);
            hdrcapmax = Expression "HDR capacity max"
                (get_header "gainmap-hdr-capacity-max" x);
            basecg = Expression "Use base cg"
                (get_header "gainmap-use-base-cg" x);
            sf = Expression "Scale factor"
                (get_header "gainmap-scale-factor" x);

        }
    }

    Set_uhdr_metadata_item = class
        Menuaction (_ "Set UHDR metadata") (_ "set UHDR metadata from image") {
        action x y = class _result {
            _vislevel = 3;

            _result
                = foldr set image fields
            {
                [meta, image] = sortc (const is_Image) [x, y];

                set [member, field] = set_meta field meta.(member).expr;

                fields = [
                    $mincb => "gainmap-max-content-boost",
                    $maxcb => "gainmap-min-content-boost",
                    $gamma => "gainmap-gamma",
                    $offsetsdr => "gainmap-offset-sdr",
                    $offsethdr => "gainmap-offset-hdr",
                    $hdrcapmin => "gainmap-hdr-capacity-min",
                    $hdrcapmax => "gainmap-hdr-capacity-max",
                    $basecg => "gainmap-use-base-cg",
                    $sf => "gainmap-scale-factor"
                ];
            }
        }
    }
}

#separator

Colour_dE_item = class
    Menupullright (_ "Difference") (_ "calculate colour difference") {
    CIEdE76_item = class
        Menuaction (_ "CIE dE 1976")
            (_ "calculate CIE dE 1976 for two objects") {
        action a b = map_binary dE76 a b;
    }

    CIEdE00_item = class
        Menuaction (_ "CIE dE 2000")
            (_ "calculate CIE dE 2000 for two objects") {
        action a b = map_binary dE00 a b;
    }

    CMC_item = class
        Menuaction (_ "CMC(l:c)") (_ "calculate CMC(l:c) for two objects") {
        action a b = map_binary dECMC a b;
    }
}

Colour_adjust_item = class
    Menupullright (_ "Adjust") (_ "alter colours in various ways") {
    Recombination_item = class
        Menuaction (_ "Recombination")
            (_ "recombine colour with an editable matrix") {
        action x = class
            _result {
            _vislevel = 3;

            matrix
                = Matrix_rec (identity_matrix (bands x))
            {
                // try to guess a sensible value for the size of the
                // matrix
                bands x
                    = x.bands, is_Image x || is_Colour x
                    = x.width, is_Matrix x
                    = bands x.value?0, is_Group x
                    = x.bands, has_member "bands" x
                    = 3;
            }

            _result = map_unary (recomb matrix) x;
        }
    }

    Cast_item = class
        Menuaction (_ "Cast") (_ "displace neutral axis in CIE Lab") {
        action x = class
            _result {
            _vislevel = 3;

            gr = Scale "Green-red" (-20) 20 0;
            by = Scale "Blue-yellow" (-20) 20 0;

            _result
                = map_unary adjust_cast x
            {
                adjust_cast in
                    = colourspace [] (get_interpretation in) in''
                {
                    in' = colourspace [] Interpretation.LAB in;
                    in'' = in' +
                        Vector [0, gr.value, by.value];
                }
            }
        }
    }

    HSB_item = class
        Menuaction (_ "HSB") (_ "adjust hue-saturation-brightness in LCh") {
        action x = class
            _result {
            _vislevel = 3;

            h = Scale "Hue" 0 360 0;
            s = Scale "Saturation" 0.01 5 1;
            b = Scale "Brightness" 0.01 5 1;

            _result
                = map_unary adjust_hsb x
            {
                adjust_hsb in
                    = colourspace [] (get_interpretation in) in''
                {
                    in' = colourspace [] Interpretation.LCH in;
                    in'' = in' * Vector [b.value, s.value, 1] +
                        Vector [0, 0, h.value];
                }
            }
        }
    }
}

Colour_similar_item = class
    Menuaction (_ "Similar colour") (_ "find pixels with a similar colour") {
    action x = class
        _result {
        _vislevel = 3;

        target_colour = Colour_new_item.LAB_colour.action;
        t = Scale "dE threshold" 0 100 10;

        _result
            = map_unary match x
        {
            match in
                = abs_vec (in' - target) < t
            {
                target = colourspace [] Interpretation.LAB target_colour;
                in' = colourspace [] Interpretation.LAB in;
            }
        }
    }
}

#separator

Colour_chart_to_matrix_item = class
    Menuaction (_ "Measure colour chart")
        (_ "measure average pixel values for a colour chart image") {
    action x = class
        _result {
        _vislevel = 3;

        pacross = Expression (_ "Patches across chart") 6;
        pdown = Expression (_ "Patches down chart") 4;
        measure = Scale (_ "Measure area (%)") 1 100 50;

        // get a representative image from an arg
        get_image x
            = get_image x.value?0, is_Group x
            = x;

        _im = get_image x;
        sample = measure_draw (to_real pacross) (to_real pdown)
                (to_real measure) _im;

        _result
            = map_unary chart x
        {
            chart in
                = measure_sample (to_real pacross) (to_real pdown)
                    (to_real measure) in;
        }
    }
}

Colour_matrix_to_chart_item = class
    Menuaction (_ "Make synthetic colour chart")
        (_ "make a colour chart image from a matrix of measurements") {
    action x = class
        _result {
        _vislevel = 3;

        pacross = Expression (_ "Patches across chart") 6;
        pdown = Expression (_ "Patches down chart") 4;
        pwidth = Expression (_ "Patch width in pixels") 50;
        pheight = Expression (_ "Patch height in pixels") 50;
        bwidth = Expression (_ "Border between patches") 0;

        _result
            = map_unary build_chart x
        {
            build_chart in
                = Image (imagearray_assemble
                    (to_real bwidth) (to_real bwidth) patch_table)
            {
                // patch numbers for row starts
                rowstart = map (multiply (to_real pacross))
                    [0 .. to_real pdown - 1];

                // assemble patches ... each one a pixel value
                patches = map (take (to_real pacross))
                    (map (converse drop in.value) rowstart);

                // make an n-band constant image from eg. [1,2,3]
                // we don't know the format .. use sRGB (well, why not?)
                patch v = image_new [
                    $interpretation => Interpretation.sRGB,
                    $bands => len v,
                    $format => Format.FLOAT,
                    $pixel => Vector v
                ] (to_real pwidth) (to_real pheight);

                // make an image for each patch
                patch_table = map (map patch) patches;
            }
        }
    }
}

Colour_plot_ab_scatter_item = class
    Menuaction (_ "_Plot ab scatter") (_ "plot an ab scatter histogram") {
    action x = class
        _result {
        _vislevel = 3;

        bins = Expression (_ "Number of bins on each axis") 8;

        _result
            = map_unary plot_scatter x
        {
            plot_scatter in
                = Image (bg * (((90 / mx) * hist) ++ blk))
            {
                lab = colourspace [] Interpretation.LAB in.value;
                ab = (unsigned char) ((lab?1 ++ lab?2) + 128);
                hist = hist_find_nD bins.expr ab;
                mx = max hist;
                bg = lab_slice bins.expr 1;
                blk = 1 +
                    vips_black [$bands => 2] (to_real bins) (to_real bins);
            }
        }
    }
}
