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}