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