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