    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.imaging.color;
    018
    019public final class ColorConversions {
    020
    021    // White reference
    022    /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
    023    private static final double REF_X = 95.047; // Observer= 2°, Illuminant= D65
    024
    025    /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
    026    private static final double REF_Y = 100.000;
    027
    028    /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
    029    private static final double REF_Z = 108.883;
    030
    031    /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
    032    private static final double XYZ_m = 7.787037; // match in slope. Note commonly seen 7.787 gives worse results
    033
    034    /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
    035    private static final double XYZ_t0 = 0.008856;
    036
    037    public static int convertCieLabToArgbTest(final int cieL, final int cieA, final int cieB) {
    038        final double x;
    039        final double y;
    040        final double z;
    041        {
    042
    043            double varY = (cieL * 100.0 / 255.0 + 16.0) / 116.0;
    044            double varX = cieA / 500.0 + varY;
    045            double varZ = varY - cieB / 200.0;
    046
    047            varX = unPivotXyz(varX);
    048            varY = unPivotXyz(varY);
    049            varZ = unPivotXyz(varZ);
    050
    051            x = REF_X * varX; // REF_X = 95.047 Observer= 2°, Illuminant= D65
    052            y = REF_Y * varY; // REF_Y = 100.000
    053            z = REF_Z * varZ; // REF_Z = 108.883
    054
    055        }
    056
    057        final double r;
    058        final double g;
    059        final double b;
    060        {
    061            final double varX = x / 100; // X = From 0 to REF_X
    062            final double varY = y / 100; // Y = From 0 to REF_Y
    063            final double varZ = z / 100; // Z = From 0 to REF_Y
    064
    065            double varR = varX * 3.2406 + varY * -1.5372 + varZ * -0.4986;
    066            double varG = varX * -0.9689 + varY * 1.8758 + varZ * 0.0415;
    067            double varB = varX * 0.0557 + varY * -0.2040 + varZ * 1.0570;
    068
    069            varR = pivotRgb(varR);
    070            varG = pivotRgb(varG);
    071            varB = pivotRgb(varB);
    072
    073            r = varR * 255;
    074            g = varG * 255;
    075            b = varB * 255;
    076        }
    077
    078        return convertRgbToRgb(r, g, b);
    079    }
    080
    081    public static ColorCieLch convertCieLabToCieLch(final ColorCieLab cielab) {
    082        return convertCieLabToCieLch(cielab.l, cielab.a, cielab.b);
    083    }
    084
    085    public static ColorCieLch convertCieLabToCieLch(final double l, final double a, final double b) {
    086        // atan2(y,x) returns atan(y/x)
    087        final double atanba = Math.atan2(b, a); // Quadrant by signs
    088
    089        final double h = atanba > 0 //
    090                ? Math.toDegrees(atanba) //
    091                : Math.toDegrees(atanba) + 360;
    092
    093        // L = L;
    094        final double C = Math.sqrt(square(a) + square(b));
    095
    096        return new ColorCieLch(l, C, h);
    097    }
    098
    099    public static ColorDin99Lab convertCieLabToDin99bLab(final ColorCieLab cie) {
    100        return convertCieLabToDin99bLab(cie.l, cie.a, cie.b);
    101    }
    102
    103    public static ColorDin99Lab convertCieLabToDin99bLab(final double l, final double a, final double b) {
    104        final double fac1 = 100.0 / Math.log(129.0 / 50.0); // = 105.51
    105        final double kE = 1.0; // brightness factor, 1.0 for CIE reference conditions
    106        final double kCH = 1.0; // chroma and hue factor, 1.0 for CIE reference conditions
    107        final double ang = Math.toRadians(16.0);
    108
    109        final double l99 = kE * fac1 * Math.log(1. + 0.0158 * l);
    110        double a99 = 0.0;
    111        double b99 = 0.0;
    112        if (a != 0.0 || b != 0.0) {
    113            final double e = a * Math.cos(ang) + b * Math.sin(ang);
    114            final double f = 0.7 * (b * Math.cos(ang) - a * Math.sin(ang));
    115            final double G = Math.sqrt(e * e + f * f);
    116            if (G != 0.) {
    117                final double k = Math.log(1. + 0.045 * G) / (0.045 * kCH * kE * G);
    118                a99 = k * e;
    119                b99 = k * f;
    120            }
    121        }
    122        return new ColorDin99Lab(l99, a99, b99);
    123    }
    124
    125    /**
    126     * DIN99o.
    127     *
    128     * @param cie CIE color.
    129     * @return CIELab colors converted to DIN99oLab color space.
    130     * @see <a href=
    131     *      "https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum">https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum</a>
    132     */
    133    public static ColorDin99Lab convertCieLabToDin99oLab(final ColorCieLab cie) {
    134        return convertCieLabToDin99oLab(cie.l, cie.a, cie.b);
    135    }
    136
    137    /**
    138     * DIN99o.
    139     *
    140     * @param l lightness of color.
    141     * @param a position between red and green.
    142     * @param b position between yellow and blue.
    143     * @return CIBELab colors converted to DIN99oLab color space.
    144     * @see <a href=
    145     *      "https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum">https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum</a>
    146     */
    147    public static ColorDin99Lab convertCieLabToDin99oLab(final double l, final double a, final double b) {
    148        final double kE = 1.0; // brightness factor, 1.0 for CIE reference conditions
    149        final double kCH = 1.0; // chroma and hue factor, 1.0 for CIE reference conditions
    150        final double fac1 = 100.0 / Math.log(139.0 / 100.0); // L99 scaling factor = 303.67100547050995
    151        final double ang = Math.toRadians(26.0);
    152
    153        final double l99o = fac1 / kE * Math.log(1 + 0.0039 * l); // Lightness correction kE
    154        double a99o = 0.0;
    155        double b99o = 0.0;
    156        if (a != 0.0 || b != 0.0) {
    157            final double eo = a * Math.cos(ang) + b * Math.sin(ang); // a stretching
    158            final double fo = 0.83 * (b * Math.cos(ang) - a * Math.sin(ang)); // b rotation/stretching
    159            final double Go = Math.sqrt(eo * eo + fo * fo); // chroma
    160            final double C99o = Math.log(1.0 + 0.075 * Go) / (0.0435 * kCH * kE); // factor for chroma compression and viewing conditions
    161            final double heofo = Math.atan2(fo, eo); // arctan in four quadrants
    162            final double h99o = heofo + ang; // hue rotation
    163            a99o = C99o * Math.cos(h99o);
    164            b99o = C99o * Math.sin(h99o);
    165        }
    166        return new ColorDin99Lab(l99o, a99o, b99o);
    167    }
    168
    169    public static ColorXyz convertCieLabToXyz(final ColorCieLab cielab) {
    170        return convertCieLabToXyz(cielab.l, cielab.a, cielab.b);
    171    }
    172
    173    public static ColorXyz convertCieLabToXyz(final double l, final double a, final double b) {
    174        double varY = (l + 16) / 116.0;
    175        double varX = a / 500 + varY;
    176        double varZ = varY - b / 200.0;
    177
    178        varY = unPivotXyz(varY);
    179        varX = unPivotXyz(varX);
    180        varZ = unPivotXyz(varZ);
    181
    182        final double x = REF_X * varX; // REF_X = 95.047 Observer= 2°, Illuminant=
    183        // D65
    184        final double y = REF_Y * varY; // REF_Y = 100.000
    185        final double z = REF_Z * varZ; // REF_Z = 108.883
    186
    187        return new ColorXyz(x, y, z);
    188    }
    189
    190    public static ColorCieLab convertCieLchToCieLab(final ColorCieLch cielch) {
    191        return convertCieLchToCieLab(cielch.l, cielch.c, cielch.h);
    192    }
    193
    194    public static ColorCieLab convertCieLchToCieLab(final double l, final double c, final double h) {
    195        // Where CIE-H° = 0 ÷ 360°
    196
    197        // CIE-L* = CIE-L;
    198        final double a = Math.cos(degree2radian(h)) * c;
    199        final double b = Math.sin(degree2radian(h)) * c;
    200
    201        return new ColorCieLab(l, a, b);
    202    }
    203
    204    public static ColorXyz convertCieLuvToXyz(final ColorCieLuv cielch) {
    205        return convertCieLuvToXyz(cielch.l, cielch.u, cielch.v);
    206    }
    207
    208    public static ColorXyz convertCieLuvToXyz(final double l, final double u, final double v) {
    209        // problems here with div by zero
    210
    211        final double varY = unPivotXyz((l + 16) / 116.0);
    212
    213        final double refU = 4 * REF_X / (REF_X + 15 * REF_Y + 3 * REF_Z);
    214        final double refV = 9 * REF_Y / (REF_X + 15 * REF_Y + 3 * REF_Z);
    215        final double varU = u / (13 * l) + refU;
    216        final double varV = v / (13 * l) + refV;
    217
    218        final double y = varY * 100;
    219        final double x = -(9 * y * varU) / ((varU - 4) * varV - varU * varV);
    220        final double z = (9 * y - 15 * varV * y - varV * x) / (3 * varV);
    221
    222        return new ColorXyz(x, y, z);
    223    }
    224
    225    public static ColorCmy convertCmykToCmy(final ColorCmyk cmyk) {
    226        return convertCmykToCmy(cmyk.c, cmyk.m, cmyk.y, cmyk.k);
    227    }
    228
    229    public static ColorCmy convertCmykToCmy(double c, double m, double y, final double k) {
    230        // Where CMYK and CMY values = 0 ÷ 1
    231
    232        c = c * (1 - k) + k;
    233        m = m * (1 - k) + k;
    234        y = y * (1 - k) + k;
    235
    236        return new ColorCmy(c, m, y);
    237    }
    238
    239    public static int convertCmykToRgb(final int c, final int m, final int y, final int k) {
    240        final double C = c / 255.0;
    241        final double M = m / 255.0;
    242        final double Y = y / 255.0;
    243        final double K = k / 255.0;
    244
    245        return convertCmyToRgb(convertCmykToCmy(C, M, Y, K));
    246    }
    247
    248    public static int convertCmykToRgbAdobe(final int sc, final int sm, final int sy, final int sk) {
    249        final int red = 255 - (sc + sk);
    250        final int green = 255 - (sm + sk);
    251        final int blue = 255 - (sy + sk);
    252
    253        return convertRgbToRgb(red, green, blue);
    254    }
    255
    256    public static ColorCmyk convertCmyToCmyk(final ColorCmy cmy) {
    257        // Where CMYK and CMY values = 0 ÷ 1
    258
    259        double c = cmy.c;
    260        double m = cmy.m;
    261        double y = cmy.y;
    262
    263        double varK = 1.0;
    264
    265        if (c < varK) {
    266            varK = c;
    267        }
    268        if (m < varK) {
    269            varK = m;
    270        }
    271        if (y < varK) {
    272            varK = y;
    273        }
    274        if (varK == 1) { // Black
    275            c = 0;
    276            m = 0;
    277            y = 0;
    278        } else {
    279            c = (c - varK) / (1 - varK);
    280            m = (m - varK) / (1 - varK);
    281            y = (y - varK) / (1 - varK);
    282        }
    283        return new ColorCmyk(c, m, y, varK);
    284    }
    285
    286    public static int convertCmyToRgb(final ColorCmy cmy) {
    287        // From Ghostscript's gdevcdj.c:
    288        // * Ghostscript: R = (1.0 - C) * (1.0 - K)
    289        // * Adobe: R = 1.0 - min(1.0, C + K)
    290        // and similarly for G and B.
    291        // This is Ghostscript's formula with K = 0.
    292
    293        // CMY values = 0 ÷ 1
    294        // RGB values = 0 ÷ 255
    295
    296        final double r = (1 - cmy.c) * 255.0;
    297        final double g = (1 - cmy.m) * 255.0;
    298        final double b = (1 - cmy.y) * 255.0;
    299
    300        return convertRgbToRgb(r, g, b);
    301    }
    302
    303    public static ColorCieLab convertDin99bLabToCieLab(final ColorDin99Lab dinb) {
    304        return convertDin99bLabToCieLab(dinb.l99, dinb.a99, dinb.b99);
    305    }
    306
    307    public static ColorCieLab convertDin99bLabToCieLab(final double L99b, final double a99b, final double b99b) {
    308        final double kE = 1.0; // brightness factor, 1.0 for CIE reference conditions
    309        final double kCH = 1.0; // chroma and hue factor, 1.0 for CIE reference conditions
    310        final double fac1 = 100.0 / Math.log(129.0 / 50.0); // L99 scaling factor = 105.50867113783109
    311        final double ang = Math.toRadians(16.0);
    312
    313        final double hef = Math.atan2(b99b, a99b);
    314        final double c = Math.sqrt(a99b * a99b + b99b * b99b);
    315        final double g = (Math.exp(0.045 * c * kCH * kE) - 1.0) / 0.045;
    316        final double e = g * Math.cos(hef);
    317        final double f = g * Math.sin(hef) / 0.7;
    318
    319        final double l = (Math.exp(L99b * kE / fac1) - 1.) / 0.0158;
    320        final double a = e * Math.cos(ang) - f * Math.sin(ang);
    321        final double b = e * Math.sin(ang) + f * Math.cos(ang);
    322        return new ColorCieLab(l, a, b);
    323    }
    324
    325    /**
    326     * DIN99o.
    327     *
    328     * @param dino color in the DIN99 color space.
    329     * @return DIN99o colors converted to CIELab color space.
    330     * @see <a href=
    331     *      "https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum">https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum</a>
    332     */
    333    public static ColorCieLab convertDin99oLabToCieLab(final ColorDin99Lab dino) {
    334        return convertDin99oLabToCieLab(dino.l99, dino.a99, dino.b99);
    335    }
    336
    337    /**
    338     * DIN99o.
    339     *
    340     * @param l99o lightness of color.
    341     * @param a99o position between red and green.
    342     * @param b99o position between yellow and blue.
    343     * @return DIN99o colors converted to CIELab color space.
    344     * @see <a href=
    345     *      "https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum">https://de.wikipedia.org/w/index.php?title=Diskussion:DIN99-Farbraum</a>
    346     */
    347    public static ColorCieLab convertDin99oLabToCieLab(final double l99o, final double a99o, final double b99o) {
    348        final double kE = 1.0; // brightness factor, 1.0 for CIE reference conditions
    349        final double kCH = 1.0; // chroma and hue factor, 1.0 for CIE reference conditions
    350        final double fac1 = 100.0 / Math.log(139.0 / 100.0); // L99 scaling factor = 303.67100547050995
    351        final double ang = Math.toRadians(26.0);
    352
    353        final double l = (Math.exp(l99o * kE / fac1) - 1.0) / 0.0039;
    354
    355        final double h99ef = Math.atan2(b99o, a99o); // arctan in four quadrants
    356
    357        final double heofo = h99ef - ang; // backwards hue rotation
    358
    359        final double c99 = Math.sqrt(a99o * a99o + b99o * b99o); // DIN99 chroma
    360        final double g = (Math.exp(0.0435 * kE * kCH * c99) - 1.0) / 0.075; // factor for chroma decompression and viewing conditions
    361        final double e = g * Math.cos(heofo);
    362        final double f = g * Math.sin(heofo);
    363
    364        final double a = e * Math.cos(ang) - f / 0.83 * Math.sin(ang); // rotation by 26 degrees
    365        final double b = e * Math.sin(ang) + f / 0.83 * Math.cos(ang); // rotation by 26 degrees
    366
    367        return new ColorCieLab(l, a, b);
    368    }
    369
    370    public static int convertHslToRgb(final ColorHsl hsl) {
    371        return convertHslToRgb(hsl.h, hsl.s, hsl.l);
    372    }
    373
    374    public static int convertHslToRgb(final double h, final double s, final double l) {
    375        final double r;
    376        final double g;
    377        final double b;
    378        if (s == 0) {
    379            // HSL values = 0 ÷ 1
    380            r = l * 255; // RGB results = 0 ÷ 255
    381            g = l * 255;
    382            b = l * 255;
    383        } else {
    384            final double var2;
    385
    386            if (l < 0.5) {
    387                var2 = l * (1 + s);
    388            } else {
    389                var2 = l + s - s * l;
    390            }
    391
    392            final double var1 = 2 * l - var2;
    393
    394            r = 255 * convertHueToRgb(var1, var2, h + 1 / 3.0);
    395            g = 255 * convertHueToRgb(var1, var2, h);
    396            b = 255 * convertHueToRgb(var1, var2, h - 1 / 3.0);
    397        }
    398
    399        return convertRgbToRgb(r, g, b);
    400    }
    401
    402    public static int convertHsvToRgb(final ColorHsv HSV) {
    403        return convertHsvToRgb(HSV.h, HSV.s, HSV.v);
    404    }
    405
    406    public static int convertHsvToRgb(final double h, final double s, final double v) {
    407        final double r;
    408        final double g;
    409        final double b;
    410        if (s == 0) {
    411            // HSV values = 0 ÷ 1
    412            r = v * 255;
    413            g = v * 255;
    414            b = v * 255;
    415        } else {
    416            double varH = h * 6;
    417            if (varH == 6) {
    418                varH = 0; // H must be < 1
    419            }
    420            final double varI = Math.floor(varH); // Or ... varI = floor( varH )
    421            final double var1 = v * (1 - s);
    422            final double var2 = v * (1 - s * (varH - varI));
    423            final double var3 = v * (1 - s * (1 - (varH - varI)));
    424
    425            final double varR;
    426            final double varG;
    427            final double varB;
    428
    429            if (varI == 0) {
    430                varR = v;
    431                varG = var3;
    432                varB = var1;
    433            } else if (varI == 1) {
    434                varR = var2;
    435                varG = v;
    436                varB = var1;
    437            } else if (varI == 2) {
    438                varR = var1;
    439                varG = v;
    440                varB = var3;
    441            } else if (varI == 3) {
    442                varR = var1;
    443                varG = var2;
    444                varB = v;
    445            } else if (varI == 4) {
    446                varR = var3;
    447                varG = var1;
    448                varB = v;
    449            } else {
    450                varR = v;
    451                varG = var1;
    452                varB = var2;
    453            }
    454
    455            r = varR * 255; // RGB results = 0 ÷ 255
    456            g = varG * 255;
    457            b = varB * 255;
    458        }
    459
    460        return convertRgbToRgb(r, g, b);
    461    }
    462
    463    private static double convertHueToRgb(final double v1, final double v2, double vH) {
    464        if (vH < 0) {
    465            vH += 1;
    466        }
    467        if (vH > 1) {
    468            vH -= 1;
    469        }
    470        if (6 * vH < 1) {
    471            return v1 + (v2 - v1) * 6 * vH;
    472        }
    473        if (2 * vH < 1) {
    474            return v2;
    475        }
    476        if (3 * vH < 2) {
    477            return v1 + (v2 - v1) * (2 / 3.0 - vH) * 6;
    478        }
    479        return v1;
    480    }
    481
    482    public static ColorXyz convertHunterLabToXyz(final ColorHunterLab cielab) {
    483        return convertHunterLabToXyz(cielab.l, cielab.a, cielab.b);
    484    }
    485
    486    public static ColorXyz convertHunterLabToXyz(final double l, final double a, final double b) {
    487        final double varY = l / 10;
    488        final double varX = a / 17.5 * l / 10;
    489        final double varZ = b / 7 * l / 10;
    490
    491        final double y = Math.pow(varY, 2);
    492        final double x = (varX + y) / 1.02;
    493        final double z = -(varZ - y) / 0.847;
    494
    495        return new ColorXyz(x, y, z);
    496    }
    497
    498    public static ColorCmy convertRgbToCmy(final int rgb) {
    499        final int r = 0xff & rgb >> 16;
    500        final int g = 0xff & rgb >> 8;
    501        final int b = 0xff & rgb >> 0;
    502
    503        // RGB values = 0 ÷ 255
    504        // CMY values = 0 ÷ 1
    505
    506        final double c = 1 - r / 255.0;
    507        final double m = 1 - g / 255.0;
    508        final double y = 1 - b / 255.0;
    509
    510        return new ColorCmy(c, m, y);
    511    }
    512
    513    public static ColorHsl convertRgbToHsl(final int rgb) {
    514
    515        final int r = 0xff & rgb >> 16;
    516        final int g = 0xff & rgb >> 8;
    517        final int b = 0xff & rgb >> 0;
    518
    519        final double varR = r / 255.0; // Where RGB values = 0 ÷ 255
    520        final double varG = g / 255.0;
    521        final double varB = b / 255.0;
    522
    523        final double varMin = Math.min(varR, Math.min(varG, varB)); // Min. value
    524                                                                    // of RGB
    525        final double varMax;
    526        boolean maxIsR = false;
    527        boolean maxIsG = false;
    528        if (varR >= varG && varR >= varB) {
    529            varMax = varR;
    530            maxIsR = true;
    531        } else if (varG > varB) {
    532            varMax = varG;
    533            maxIsG = true;
    534        } else {
    535            varMax = varB;
    536        }
    537        final double delMax = varMax - varMin; // Delta RGB value
    538
    539        final double l = (varMax + varMin) / 2.0;
    540
    541        double h;
    542        final double s;
    543        // Debug.debug("del_Max", del_Max);
    544        if (delMax == 0) {
    545            // This is a gray, no chroma...
    546
    547            h = 0; // HSL results = 0 ÷ 1
    548            s = 0;
    549        } else {
    550            // Chromatic data...
    551
    552            // Debug.debug("L", L);
    553
    554            if (l < 0.5) {
    555                s = delMax / (varMax + varMin);
    556            } else {
    557                s = delMax / (2 - varMax - varMin);
    558            }
    559
    560            // Debug.debug("S", S);
    561
    562            final double delR = ((varMax - varR) / 6 + delMax / 2) / delMax;
    563            final double delG = ((varMax - varG) / 6 + delMax / 2) / delMax;
    564            final double delB = ((varMax - varB) / 6 + delMax / 2) / delMax;
    565
    566            if (maxIsR) {
    567                h = delB - delG;
    568            } else if (maxIsG) {
    569                h = 1 / 3.0 + delR - delB;
    570            } else {
    571                h = 2 / 3.0 + delG - delR;
    572            }
    573
    574            // Debug.debug("H1", H);
    575
    576            if (h < 0) {
    577                h += 1;
    578            }
    579            if (h > 1) {
    580                h -= 1;
    581            }
    582
    583            // Debug.debug("H2", H);
    584        }
    585
    586        return new ColorHsl(h, s, l);
    587    }
    588
    589    public static ColorHsv convertRgbToHsv(final int rgb) {
    590        final int r = 0xff & rgb >> 16;
    591        final int g = 0xff & rgb >> 8;
    592        final int b = 0xff & rgb >> 0;
    593
    594        final double varR = r / 255.0; // RGB values = 0 ÷ 255
    595        final double varG = g / 255.0;
    596        final double varB = b / 255.0;
    597
    598        final double varMin = Math.min(varR, Math.min(varG, varB)); // Min. value
    599                                                                    // of RGB
    600        boolean maxIsR = false;
    601        boolean maxIsG = false;
    602        final double varMax;
    603        if (varR >= varG && varR >= varB) {
    604            varMax = varR;
    605            maxIsR = true;
    606        } else if (varG > varB) {
    607            varMax = varG;
    608            maxIsG = true;
    609        } else {
    610            varMax = varB;
    611        }
    612        final double delMax = varMax - varMin; // Delta RGB value
    613
    614        final double v = varMax;
    615
    616        double h;
    617        final double s;
    618        if (delMax == 0) {
    619            // This is a gray, no chroma...
    620            h = 0; // HSV results = 0 ÷ 1
    621            s = 0;
    622        } else {
    623            // Chromatic data...
    624            s = delMax / varMax;
    625
    626            final double delR = ((varMax - varR) / 6 + delMax / 2) / delMax;
    627            final double delG = ((varMax - varG) / 6 + delMax / 2) / delMax;
    628            final double delB = ((varMax - varB) / 6 + delMax / 2) / delMax;
    629
    630            if (maxIsR) {
    631                h = delB - delG;
    632            } else if (maxIsG) {
    633                h = 1 / 3.0 + delR - delB;
    634            } else {
    635                h = 2 / 3.0 + delG - delR;
    636            }
    637
    638            if (h < 0) {
    639                h += 1;
    640            }
    641            if (h > 1) {
    642                h -= 1;
    643            }
    644        }
    645
    646        return new ColorHsv(h, s, v);
    647    }
    648
    649    private static int convertRgbToRgb(final double r, final double g, final double b) {
    650        int red = (int) Math.round(r);
    651        int green = (int) Math.round(g);
    652        int blue = (int) Math.round(b);
    653
    654        red = Math.min(255, Math.max(0, red));
    655        green = Math.min(255, Math.max(0, green));
    656        blue = Math.min(255, Math.max(0, blue));
    657
    658        final int alpha = 0xff;
    659
    660        return alpha << 24 | red << 16 | green << 8 | blue << 0;
    661    }
    662
    663    private static int convertRgbToRgb(int red, int green, int blue) {
    664        red = Math.min(255, Math.max(0, red));
    665        green = Math.min(255, Math.max(0, green));
    666        blue = Math.min(255, Math.max(0, blue));
    667
    668        final int alpha = 0xff;
    669
    670        return alpha << 24 | red << 16 | green << 8 | blue << 0;
    671    }
    672
    673    // See also c# implementation:
    674    // https://github.com/muak/ColorMinePortable/blob/master/ColorMinePortable/ColorSpaces/Conversions/XyzConverter.cs
    675    public static ColorXyz convertRgbToXyz(final int rgb) {
    676        final int r = 0xff & rgb >> 16;
    677        final int g = 0xff & rgb >> 8;
    678        final int b = 0xff & rgb >> 0;
    679
    680        double varR = r / 255.0; // Where R = 0 ÷ 255
    681        double varG = g / 255.0; // Where G = 0 ÷ 255
    682        double varB = b / 255.0; // Where B = 0 ÷ 255
    683
    684        // Pivot RGB:
    685        varR = unPivotRgb(varR);
    686        varG = unPivotRgb(varG);
    687        varB = unPivotRgb(varB);
    688
    689        varR *= 100;
    690        varG *= 100;
    691        varB *= 100;
    692
    693        // Observer. = 2°, Illuminant = D65
    694        // see: https://github.com/StanfordHCI/c3/blob/master/java/src/edu/stanford/vis/color/LAB.java
    695        final double X = varR * 0.4124564 + varG * 0.3575761 + varB * 0.1804375;
    696        final double Y = varR * 0.2126729 + varG * 0.7151522 + varB * 0.0721750;
    697        final double Z = varR * 0.0193339 + varG * 0.1191920 + varB * 0.9503041;
    698
    699        // Attention: A lot of sources do list these values with less precision. But it makes a visual difference:
    700        // final double X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805;
    701        // final double Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722;
    702        // final double Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505;
    703
    704        return new ColorXyz(X, Y, Z);
    705    }
    706
    707    public static ColorCieLuv convertXuzToCieLuv(final double x, final double y, final double z) {
    708        // problems here with div by zero
    709
    710        final double varU = 4 * x / (x + 15 * y + 3 * z);
    711        final double varV = 9 * y / (x + 15 * y + 3 * z);
    712
    713        // Debug.debug("var_U", var_U);
    714        // Debug.debug("var_V", var_V);
    715
    716        double varY = y / 100.0;
    717        // Debug.debug("var_Y", var_Y);
    718
    719        varY = pivotXyz(varY);
    720
    721        // Debug.debug("var_Y", var_Y);
    722
    723        final double refU = 4 * REF_X / (REF_X + 15 * REF_Y + 3 * REF_Z);
    724        final double refV = 9 * REF_Y / (REF_X + 15 * REF_Y + 3 * REF_Z);
    725
    726        // Debug.debug("ref_U", ref_U);
    727        // Debug.debug("ref_V", ref_V);
    728
    729        final double l = 116 * varY - 16;
    730        final double u = 13 * l * (varU - refU);
    731        final double v = 13 * l * (varV - refV);
    732
    733        return new ColorCieLuv(l, u, v);
    734    }
    735
    736    public static ColorCieLab convertXyzToCieLab(final ColorXyz xyz) {
    737        return convertXyzToCieLab(xyz.x, xyz.y, xyz.z);
    738    }
    739
    740    public static ColorCieLab convertXyzToCieLab(final double x, final double y, final double z) {
    741
    742        double varX = x / REF_X; // REF_X = 95.047 Observer= 2°, Illuminant= D65
    743        double varY = y / REF_Y; // REF_Y = 100.000
    744        double varZ = z / REF_Z; // REF_Z = 108.883
    745
    746        // Pivot XÝZ:
    747        varX = pivotXyz(varX);
    748        varY = pivotXyz(varY);
    749        varZ = pivotXyz(varZ);
    750
    751        // Math.max added from https://github.com/muak/ColorMinePortable/blob/master/ColorMinePortable/ColorSpaces/Conversions/LabConverter.cs
    752        final double l = Math.max(0, 116 * varY - 16);
    753        final double a = 500 * (varX - varY);
    754        final double b = 200 * (varY - varZ);
    755        return new ColorCieLab(l, a, b);
    756    }
    757
    758    public static ColorCieLuv convertXyzToCieLuv(final ColorXyz xyz) {
    759        return convertXuzToCieLuv(xyz.x, xyz.y, xyz.z);
    760    }
    761
    762    public static ColorHunterLab convertXyzToHunterLab(final ColorXyz xyz) {
    763        return convertXyzToHunterLab(xyz.x, xyz.y, xyz.z);
    764    }
    765
    766    public static ColorHunterLab convertXyzToHunterLab(final double x, final double y, final double z) {
    767        final double l = 10 * Math.sqrt(y);
    768        final double a = y == 0.0 ? 0.0 : 17.5 * ((1.02 * x - y) / Math.sqrt(y));
    769        final double b = y == 0.0 ? 0.0 : 7 * ((y - 0.847 * z) / Math.sqrt(y));
    770
    771        return new ColorHunterLab(l, a, b);
    772    }
    773
    774    public static int convertXyzToRgb(final ColorXyz xyz) {
    775        return convertXyzToRgb(xyz.x, xyz.y, xyz.z);
    776    }
    777
    778    public static int convertXyzToRgb(final double x, final double y, final double z) {
    779        // Observer = 2°, Illuminant = D65
    780        final double varX = x / 100.0; // Where X = 0 ÷ 95.047
    781        final double varY = y / 100.0; // Where Y = 0 ÷ 100.000
    782        final double varZ = z / 100.0; // Where Z = 0 ÷ 108.883
    783
    784        // see: https://github.com/StanfordHCI/c3/blob/master/java/src/edu/stanford/vis/color/LAB.java
    785        double varR = varX * 3.2404542 + varY * -1.5371385 + varZ * -0.4985314;
    786        double varG = varX * -0.9692660 + varY * 1.8760108 + varZ * 0.0415560;
    787        double varB = varX * 0.0556434 + varY * -0.2040259 + varZ * 1.0572252;
    788
    789        // Attention: A lot of sources do list these values with less precision. But it makes a visual difference:
    790        // double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986;
    791        // double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415;
    792        // double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570;
    793
    794        varR = pivotRgb(varR);
    795        varG = pivotRgb(varG);
    796        varB = pivotRgb(varB);
    797
    798        final double r = varR * 255;
    799        final double g = varG * 255;
    800        final double b = varB * 255;
    801        return convertRgbToRgb(r, g, b);
    802    }
    803
    804    public static double degree2radian(final double degree) {
    805        return degree * Math.PI / 180.0;
    806    }
    807
    808    private static double pivotRgb(double n) {
    809        if (n > 0.0031308) {
    810            n = 1.055 * Math.pow(n, 1 / 2.4) - 0.055;
    811        } else {
    812            n = 12.92 * n;
    813        }
    814        return n;
    815    }
    816
    817    private static double pivotXyz(double n) {
    818        if (n > XYZ_t0) {
    819            n = Math.pow(n, 1 / 3.0);
    820        } else {
    821            n = XYZ_m * n + 16 / 116.0;
    822        }
    823        return n;
    824    }
    825
    826    public static double radian2degree(final double radian) {
    827        return radian * 180.0 / Math.PI;
    828    }
    829
    830    private static double square(final double f) {
    831        return f * f;
    832    }
    833
    834    private static double unPivotRgb(double n) {
    835        if (n > 0.04045) {
    836            n = Math.pow((n + 0.055) / 1.055, 2.4);
    837        } else {
    838            n /= 12.92;
    839        }
    840        return n;
    841    }
    842
    843    private static double unPivotXyz(double n) {
    844        final double nCube = Math.pow(n, 3);
    845        if (nCube > XYZ_t0) {
    846            n = nCube;
    847        } else {
    848            n = (n - 16 / 116.0) / XYZ_m;
    849        }
    850        return n;
    851    }
    852
    853    private ColorConversions() {
    854    }
    855
    856}