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 static imagingbook.pub.color.image.Illuminant.D50;
013import static imagingbook.pub.color.image.Illuminant.D65;
014
015import java.awt.color.ColorSpace;
016
017
018/**
019 * This class implements the CIELab color space.
020 * Conversion from/to sRGB is implemented directly through D65-based XYZ
021 * coordinates, i.e., without conversion to Java's D50-based profile 
022 * connection space. The methods fromCIEXYZ/toCIEXYZ still return D50-based XYZ 
023 * coordinates in Java's profile connection space.
024 * 
025 * @author W. Burger
026 * @version 2015/07/20
027 */
028@SuppressWarnings("serial")
029public class LabColorSpace extends ColorSpace {
030        
031        // D65 reference white point:
032        static final double Xref = D65.X;       // 0.950456
033        static final double Yref = D65.Y;       // 1.000000
034        static final double Zref = D65.Z;       // 1.088754
035        
036        // chromatic adaptation objects:
037        static final ChromaticAdaptation catD65toD50 = new BradfordAdaptation(D65, D50);
038        static final ChromaticAdaptation catD50toD65 = new BradfordAdaptation(D50, D65);
039        
040        // the only constructor:
041        public LabColorSpace() {
042                super(TYPE_Lab, 3);
043        }
044
045        // XYZ50->CIELab: returns Lab values from XYZ (relative to D50)
046        public float[] fromCIEXYZ(float[] XYZ50) {      
047                float[] XYZ65 = catD50toD65.apply(XYZ50);       
048                return fromCIEXYZ65(XYZ65);
049        }
050        
051        // XYZ65->CIELab: returns Lab values from XYZ (relative to D65)
052        public float[] fromCIEXYZ65(float[] XYZ65) {
053                double xx = f1(XYZ65[0] / Xref);        
054                double yy = f1(XYZ65[1] / Yref);
055                double zz = f1(XYZ65[2] / Zref);
056                float L = (float)(116.0 * yy - 16.0);
057                float a = (float)(500.0 * (xx - yy));
058                float b = (float)(200.0 * (yy - zz));
059                return new float[] {L, a, b};
060        }
061        
062        // CIELab->XYZ50: returns XYZ values (relative to D50) from Lab
063        public float[] toCIEXYZ(float[] Lab) {
064                float[] XYZ65 = toCIEXYZ65(Lab);
065                return catD65toD50.apply(XYZ65);
066        }
067        
068        // CIELab->XYZ65: returns XYZ values (relative to D65) from Lab
069        public float[] toCIEXYZ65(float[] Lab) {
070                double ll = ( Lab[0] + 16.0 ) / 116.0;
071                float Y65 = (float) (Yref * f2(ll));
072                float X65 = (float) (Xref * f2(ll + Lab[1] / 500.0));
073                float Z65 = (float) (Zref * f2(ll - Lab[2] / 200.0));
074                return new float[] {X65, Y65, Z65};
075        }
076
077        //sRGB->CIELab (direct, without adaptation to D50)
078        public float[] fromRGB(float[] srgb) {
079                // get linear rgb components:
080                double r = sRgbUtil.gammaInv(srgb[0]);
081                double g = sRgbUtil.gammaInv(srgb[1]);
082                double b = sRgbUtil.gammaInv(srgb[2]);
083                
084                // convert to XYZ (D65-based, Poynton/ITU709) 
085                float X = (float) (0.412453 * r + 0.357580 * g + 0.180423 * b);
086                float Y = (float) (0.212671 * r + 0.715160 * g + 0.072169 * b);
087                float Z = (float) (0.019334 * r + 0.119193 * g + 0.950227 * b);
088                
089                float[] XYZ65 = new float[] {X, Y, Z}; 
090                return fromCIEXYZ65(XYZ65);
091        }
092        
093        //CIELab->sRGB (direct, without adaptation to D50)
094        public float[] toRGB(float[] Lab) {
095                float[] XYZ65 = toCIEXYZ65(Lab);
096                double X = XYZ65[0];
097                double Y = XYZ65[1];
098                double Z = XYZ65[2];
099                // XYZ -> RGB (linear components)
100                double r = (3.240479 * X + -1.537150 * Y + -0.498535 * Z);
101                double g = (-0.969256 * X + 1.875992 * Y + 0.041556 * Z);
102                double b = (0.055648 * X + -0.204043 * Y + 1.057311 * Z);
103                
104                // RGB -> sRGB (nonlinear components)
105                float rr = (float) sRgbUtil.gammaFwd(r);
106                float gg = (float) sRgbUtil.gammaFwd(g);
107                float bb = (float) sRgbUtil.gammaFwd(b);
108                                
109                return new float[] {rr,gg,bb} ; //sRGBcs.fromCIEXYZ(XYZ50);
110        }
111        
112        //---------------------------------------------------------------------
113        
114        static final double epsilon = 216.0/24389;
115        static final double kappa = 841.0/108;
116        
117        // Gamma correction for L* (forward)
118        double f1 (double c) {
119                if (c > epsilon) // 0.008856
120                        return Math.cbrt(c);
121                else
122                        return (kappa * c) + (16.0 / 116);
123        }
124        
125        // Gamma correction for L* (inverse)
126        double f2 (double c) {
127                double c3 = c * c * c; //Math.pow(c, 3.0);
128                if (c3 > epsilon)
129                        return c3;
130                else
131                        return (c - 16.0 / 116) / kappa;
132        }
133        
134   
135    public static void main(String[] args) {
136        int sr = 128;
137        int sg = 1;
138        int sb = 128;
139        System.out.format("Input (sRGB) = %d, %d, %d\n", sr, sg, sb);
140        System.out.format("XYZref = %10g, %10g, %10g\n", Xref,Yref,Zref);
141        
142        ColorSpace cs = new LabColorSpace();
143        //float[] luv = cs.fromCIEXYZ(new float[] {.1f,.5f,.9f});
144        float[] lab = cs.fromRGB(new float[] {sr/255f, sg/255f, sb/255f});
145
146        System.out.format("Lab = %8f, %8f, %8f\n", lab[0],lab[2],lab[2]);
147        //float[] xyz = cs.toCIEXYZ(luv);
148        float[] srgb = cs.toRGB(lab);
149        System.out.format("sRGB = %8f, %8f, %8f\n", 
150                        Math.rint(255*srgb[0]), Math.rint(255*srgb[1]), Math.rint(255*srgb[2]));
151    }
152}