View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.color;
18  
19  public final class ColorConversions {
20  
21      // White reference
22      /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
23      private static final double REF_X = 95.047; // Observer= 2°, Illuminant= D65
24  
25      /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
26      private static final double REF_Y = 100.000;
27  
28      /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
29      private static final double REF_Z = 108.883;
30  
31      /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
32      private static final double XYZ_m = 7.787037; // match in slope. Note commonly seen 7.787 gives worse results
33  
34      /** See: https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB[10] */
35      private static final double XYZ_t0 = 0.008856;
36  
37      public static int convertCieLabToArgbTest(final int cieL, final int cieA, final int cieB) {
38          double x, y, z;
39          {
40  
41              double varY = (cieL * 100.0 / 255.0 + 16.0) / 116.0;
42              double varX = cieA / 500.0 + varY;
43              double varZ = varY - cieB / 200.0;
44  
45              varX = unPivotXyz(varX);
46              varY = unPivotXyz(varY);
47              varZ = unPivotXyz(varZ);
48  
49              x = REF_X * varX; // REF_X = 95.047 Observer= 2°, Illuminant= D65
50              y = REF_Y * varY; // REF_Y = 100.000
51              z = REF_Z * varZ; // REF_Z = 108.883
52  
53          }
54  
55          double r, g, b;
56          {
57              final double varX = x / 100; // X = From 0 to REF_X
58              final double varY = y / 100; // Y = From 0 to REF_Y
59              final double varZ = z / 100; // Z = From 0 to REF_Y
60  
61              double varR = varX * 3.2406 + varY * -1.5372 + varZ * -0.4986;
62              double varG = varX * -0.9689 + varY * 1.8758 + varZ * 0.0415;
63              double varB = varX * 0.0557 + varY * -0.2040 + varZ * 1.0570;
64  
65              varR = pivotRgb(varR);
66              varG = pivotRgb(varG);
67              varB = pivotRgb(varB);
68  
69              r = varR * 255;
70              g = varG * 255;
71              b = varB * 255;
72          }
73  
74          return convertRgbToRgb(r, g, b);
75      }
76  
77      public static ColorCieLch convertCieLabToCieLch(final ColorCieLab cielab) {
78          return convertCieLabToCieLch(cielab.l, cielab.a, cielab.b);
79      }
80  
81      public static ColorCieLch convertCieLabToCieLch(final double l, final double a, final double b) {
82          // atan2(y,x) returns atan(y/x)
83          final double atanba = Math.atan2(b, a); // Quadrant by signs
84  
85          final double h = atanba > 0 //
86                  ? Math.toDegrees(atanba) //
87                  : Math.toDegrees(atanba) + 360;
88  
89          // L = L;
90          final double C = Math.sqrt(square(a) + square(b));
91  
92          return new ColorCieLch(l, C, h);
93      }
94  
95      public static ColorDin99Lab convertCieLabToDin99bLab(final ColorCieLab cie) {
96          return convertCieLabToDin99bLab(cie.l, cie.a, cie.b);
97      }
98  
99      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 }