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}