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}