001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 *  image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause 
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 
006 * Copyright (c) 2006-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 
007 * Visit http://imagingbook.com for additional details.
008 *******************************************************************************/
009
010package imagingbook.pub.color.image;
011
012import java.awt.color.ColorSpace;
013
014
015/*
016 * This class implements the CIELuv color space.
017 * Conversion from/to sRGB is implemented directly through D65-based XYZ
018 * coordinates, i.e., without chromatic adaptation to Java's D50-based profile 
019 * connection space. The methods fromCIEXYZ/toCIEXYZ still return D50-based XYZ 
020 * coordinates in Java's profile connection space.
021 */
022
023@SuppressWarnings("serial")
024public class LuvColorSpace extends ColorSpace {
025                
026        // D65 reference white point:
027        static final double Xref = Illuminant.D65.X;    // 0.950456
028        static final double Yref = Illuminant.D65.Y;    // 1.000000
029        static final double Zref = Illuminant.D65.Z;    // 1.088754
030        
031        static final double uuref = fu(Xref, Yref, Zref); // u'_n
032        static final double vvref = fv(Xref, Yref, Zref); // v'_n
033        
034        // chromatic adaptation objects:
035        static final ChromaticAdaptation catD65toD50 = new BradfordAdaptation(Illuminant.D65, Illuminant.D50);
036        static final ChromaticAdaptation catD50toD65 = new BradfordAdaptation(Illuminant.D50, Illuminant.D65);
037        
038        public LuvColorSpace() {
039                super(TYPE_Lab,3);
040        }
041        
042        // XYZ50->CIELuv: returns Luv values from XYZ (relative to D50)
043        public float[] fromCIEXYZ(float[] XYZ50) {      
044                float[] XYZ65 = catD50toD65.apply(XYZ50);
045                return fromCIEXYZ65(XYZ65);
046        }
047        
048        // XYZ65->CIELuv: returns Luv values from XYZ (relative to D65)
049        private float[] fromCIEXYZ65(float[] XYZ65) {   
050                double X = XYZ65[0];
051                double Y = XYZ65[1];    
052                double Z = XYZ65[2];
053                double YY = f1(Y / Yref);       // Y'
054                double uu = fu(X,Y,Z);          // u'
055                double vv = fv(X,Y,Z);          // v'
056                float L = (float)(116.0 * YY - 16.0);           //L*
057                float u = (float)(13 * L * (uu - uuref));       //u*
058                float v = (float)(13 * L * (vv - vvref));       //v*
059                return new float[] {L, u, v};
060        }
061        
062        // CIELab->XYZ50: returns XYZ values (relative to D50) from Luv
063        public float[] toCIEXYZ(float[] Luv) {
064                float[] XYZ65 = toCIEXYZ65(Luv);
065                return catD65toD50.apply(XYZ65);
066        }
067        
068        private float[] toCIEXYZ65(float[] Luv) {
069                double L = Luv[0];
070                double u = Luv[1];
071                double v = Luv[2];
072                float Y = (float) (Yref * f2((L + 16) / 116.0));
073                double uu = (L<0.00001) ? uuref : u / (13 * L) + uuref; // u'
074                double vv = (L<0.00001) ? vvref : v / (13 * L) + vvref; // v'
075                float X = (float) (Y * ((9*uu)/(4*vv)));
076                float Z = (float) (Y * ((12 - 3 * uu - 20 * vv) / (4 * vv)));
077                float[] XYZ65 = new float[] {X, Y, Z};
078                return XYZ65;
079        }
080        
081        //sRGB->CIELuv
082        public float[] fromRGB(float[] srgb) {
083                // get linear rgb components:
084                double r = sRgbUtil.gammaInv(srgb[0]);
085                double g = sRgbUtil.gammaInv(srgb[1]);
086                double b = sRgbUtil.gammaInv(srgb[2]);
087                
088                // convert to XYZ (Poynton / ITU 709) 
089                float X = (float) (0.412453 * r + 0.357580 * g + 0.180423 * b);
090                float Y = (float) (0.212671 * r + 0.715160 * g + 0.072169 * b);
091                float Z = (float) (0.019334 * r + 0.119193 * g + 0.950227 * b);
092                
093                float[] XYZ65 = new float[] {X, Y, Z}; 
094                return fromCIEXYZ65(XYZ65);
095        }
096        
097        //CIELuv->sRGB
098        public float[] toRGB(float[] Luv) {
099                float[] XYZ65 = toCIEXYZ65(Luv);
100                double X = XYZ65[0];
101                double Y = XYZ65[1];
102                double Z = XYZ65[2];
103                // XYZ -> RGB (linear components)
104                double r = ( 3.240479 * X + -1.537150 * Y + -0.498535 * Z);
105                double g = (-0.969256 * X +  1.875992 * Y +  0.041556 * Z);
106                double b =  (0.055648 * X + -0.204043 * Y +  1.057311 * Z);
107
108//              if (RgbGamutChecker.checkOutOfGamut(r, g, b) && RgbGamutChecker.markOutOfGamutColors) { // REMOVE ************************** !! 
109//                      r = RgbGamutChecker.oGred; 
110//                      g = RgbGamutChecker.oGgrn; 
111//                      b = RgbGamutChecker.oGblu;
112//              }
113                
114                // RGB -> sRGB (nonlinear components)
115                float rr = (float) sRgbUtil.gammaFwd(r);
116                float gg = (float) sRgbUtil.gammaFwd(g);
117                float bb = (float) sRgbUtil.gammaFwd(b);                        
118                return new float[] {rr,gg,bb} ; //sRGBcs.fromCIEXYZ(XYZ50);
119        }
120        
121        //---------------------------------------------------------------------
122        
123        static final double epsilon = 216.0/24389;
124        static final double kappa = 841.0/108;
125        
126        // Gamma correction for L* (forward)
127        double f1 (double c) {
128                if (c > epsilon) // 0.008856
129                        return Math.cbrt(c);
130                else
131                        return (kappa * c) + (16.0 / 116);
132        }
133        
134        // Gamma correction for L* (inverse)
135        double f2 (double c) {
136                double c3 = c * c * c; //Math.pow(c, 3.0);
137                if (c3 > epsilon)
138                        return c3;
139                else
140                        return (c - 16.0 / 116) / kappa;
141        }
142        
143        static double fu (double X, double Y, double Z) { // X,Y,Z must be positive
144                if (X < 0.00001)        // fails if 0.001 is used!
145                        return 0;
146                else
147                        return (4 * X) / (X + 15 * Y + 3 * Z);
148        }
149        
150        static double fv (double X, double Y, double Z) { // X,Y,Z must be positive
151                if (Y < 0.00001)
152                        return 0;
153                else
154                        return (9 * Y) / (X + 15 * Y + 3 * Z);
155        }
156        
157        //---------------------------------------------------------------------
158        
159    public static void main(String[] args) {
160        int sr = 128;
161        int sg = 1;
162        int sb = 128;
163        System.out.format("Input (sRGB) = %d, %d, %d\n", sr, sg, sb);
164        System.out.format("XYZref = %f, %f, %f\n", Xref, Yref, Zref);
165        
166        LuvColorSpace cs = new LuvColorSpace();
167        //float[] luv = cs.fromCIEXYZ(new float[] {.1f,.5f,.9f});
168        float[] luv = cs.fromRGB(new float[] {sr/255f, sg/255f, sb/255f});
169
170        System.out.format("Luv = %.2f, %.2f, %.2f\n", luv[0],luv[2],luv[2]);
171        //float[] xyz = cs.toCIEXYZ(luv);
172        float[] srgb = cs.toRGB(luv);
173        System.out.format("sRGB = %.2f, %.2f, %.2f\n", 
174                        Math.rint(255*srgb[0]), Math.rint(255*srgb[1]), Math.rint(255*srgb[2]));
175        
176    }
177
178}