/* * @(#)CIELCHabColorSpace.java * * Copyright (c) 2010 The authors and contributors of JHotDraw. * * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.color; import java.awt.color.ColorSpace; import static java.lang.Math.*; /** * The 1976 CIE L*CHa*b* color space (CIELCH). * <p> * The L* coordinate of an object is the lightness intensity as measured on a * scale from 0 to 100, where 0 represents black and 100 represents white. * <p> * The C and H coordinates are projections of the a* and b* colors of the * CIE L*a*b* color space into polar coordinates. * <pre> * a = C * cos(H) * b = C * sin(H) * </pre> * * @author Werner Randelshofer * @version $Id$ */ public class CIELCHabColorSpace extends AbstractNamedColorSpace { private static final long serialVersionUID = 1L; /** The XYZ coordinates of the CIE Standard Illuminant D65 reference white.*/ private static final double[] D65 = {0.9505d, 1d, 1.0890d}; private double Xr; /** The Y coordinate of the D50 reference white.*/ private double Yr; /** The Z coordinate of the D50 reference white.*/ private double Zr; private static final double eps = 216d / 24389d; private static final double k = 24389d / 27d; private static final ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB); /** By default, clamps non-displayable RGB values. */ private boolean isClampRGB = true; public CIELCHabColorSpace() { super(ColorSpace.TYPE_Lab, 3); Xr = D65[0]; Yr = D65[1]; Zr = D65[2]; } @Override public float[] toRGB(float[] colorvalue, float[] rgb) { float[] ciexyz =rgb;//reuse array toCIEXYZ(colorvalue,ciexyz); // Convert to sRGB as described in // http://www.w3.org/Graphics/Color/sRGB.html double X = ciexyz[0]; double Y = ciexyz[1]; double Z = ciexyz[2]; double Rs = 3.2410 * X + -1.5374 * Y + -0.4986 * Z; double Gs = -0.9692 * X + 1.8760 * Y + -0.0416 * Z; double Bs = 0.0556 * X + -0.2040 * Y + 1.0570 * Z; if (Rs <= 0.00304) { Rs = 12.92 * Rs; } else { Rs = 1.055 * Math.pow(Rs, 1d / 2.4) - 0.055; } if (Gs <= 0.00304) { Gs = 12.92 * Gs; } else { Gs = 1.055 * Math.pow(Gs, 1d / 2.4) - 0.055; } if (Bs <= 0.00304) { Bs = 12.92 * Bs; } else { Bs = 1.055 * Math.pow(Bs, 1d / 2.4) - 0.055; } if (isClampRGB) { Rs = Math.min(1, Math.max(0, Rs)); Gs = Math.min(1, Math.max(0, Gs)); Bs = Math.min(1, Math.max(0, Bs)); } rgb[0]=(float)Rs; rgb[1]=(float)Gs; rgb[2]=(float)Bs; return rgb; // return sRGB.fromCIEXYZ(ciexyz); } @Override public float[] fromRGB(float[] rgb, float[] colorvalue) { return fromCIEXYZ(ColorUtil.RGBtoCIEXYZ(rgb,colorvalue),colorvalue); } /** * Lab to XYZ. * <pre> * X = xr*Xr; * Y = yr*Yr; * Z = zr*Zr; * </pre> * where * <pre> * xr = fx^3, if fx^3 > eps * = (116*fx - 16)/k, if fx^3 <= eps * * yr = ((L+16)/116)^3, if L > k*eps * = L/k, if L <= k*eps * * zr = fz^3, if fz^3 > eps * = (116*fz - 16)/k, if fz^3 <= eps * * fx = a/500+fy * * fz = fy - b / 200 * * fy = (L+16)/116 * * eps = 216/24389 * k = 24389/27 * </pre> * * Source: <a href="http://www.brucelindbloom.com/index.html?Equations.html" * >http://www.brucelindbloom.com/index.html?Equations.html</a> * * @param colorvalue Lab color value. * @return CIEXYZ color value. */ @Override public float[] toCIEXYZ(float[] colorvalue, float[] xyz) { double L = colorvalue[0]; double C = colorvalue[1]; double H = colorvalue[2]/180*PI; double a = C * Math.cos(H); double b = C * Math.sin(H); double fy = (L + 16d) / 116d; double fx = a / 500d + fy; double fz = fy - b / 200d; double xr, yr, zr; double fxp3 = fx * fx * fx; if (fxp3 > eps) { xr = fxp3; } else { xr = (116d * fx - 16d) / k; } if (L > k * eps) { yr = ((L + 16d) / 116d); yr = yr * yr * yr; } else { yr = L / k; } double fzp3 = fz * fz * fz; if (fzp3 > eps) { zr = fzp3; } else { zr = (116d * fz - 16f) / k; } double X = xr * Xr; double Y = yr * Yr; double Z = zr * Zr; xyz[0]=(float)X;xyz[1]=(float)Y;xyz[2]=(float)Z; return xyz; } /** * XYZ to Lab. * <pre> * L = 116*fy - 16 * a = 500 * (fx - fy) * b = 200 * (fy - fz) * </pre> * where * <pre> * fx = xr^(1/3), if xr > eps * = (k*xr + 16) / 116 if xr <= eps * * fy = yr^(1/3), if yr > eps * = (k*yr + 16) / 116 if yr <= eps * * fz = zr^(1/3), if zr > eps * = (k*zr + 16) / 116 if zr <= eps * * xr = X / Xr * yr = Y / Yr * zr = Z / Zr * * eps = 216/24389 * k = 24389/27 * </pre> * * Source: <a href="http://www.brucelindbloom.com/index.html?Equations.html" * >http://www.brucelindbloom.com/index.html?Equations.html</a> * * @param colorvalue CIEXYZ color value. * @return Lab color value. */ @Override public float[] fromCIEXYZ(float[] xyz, float[] colorvalue) { double X = xyz[0]; double Y = xyz[1]; double Z = xyz[2]; double xr = X / Xr; double yr = Y / Yr; double zr = Z / Zr; double fx, fy, fz; if (xr > eps) { fx = Math.pow(xr, 1d / 3d); } else { fx = (k * xr + 16d) / 116d; } if (yr > eps) { fy = Math.pow(yr, 1d / 3d); } else { fy = (k * yr + 16d) / 116d; } if (zr > eps) { fz = Math.pow(zr, 1d / 3d); } else { fz = (k * zr + 16) / 116; } double L = 116d * fy - 16; double a = 500d * (fx - fy); double b = 200d * (fy - fz); double C = Math.sqrt(a*a+b*b); double H = Math.atan2(b, a); colorvalue[0]=(float)L; colorvalue[1]=(float)C; colorvalue[2]=(float)(H*180/PI); return colorvalue; } @Override public String getName() { return "CIE 1976 L*CHa*b*"; } @Override public float getMinValue(int component) { switch (component) { case 0: return 0f; case 1: return 0f; case 2: return 0f; } throw new IllegalArgumentException("Illegal component:" + component); } @Override public float getMaxValue(int component) { switch (component) { case 0: return 100f; case 1: return 127f; case 2: return 360f; } throw new IllegalArgumentException("Illegal component:" + component); } @Override public String getName(int component) { switch (component) { case 0: return "L*"; case 1: return "a*"; case 2: return "b*"; } throw new IllegalArgumentException("Illegal component:" + component); } public void setClampRGBValues(boolean b) { isClampRGB = b; } public boolean isClampRGBValues() { return isClampRGB; } public static void main(String[] arg) { CIELCHabColorSpace cs = new CIELCHabColorSpace(); float[] lchab = cs.fromRGB(new float[]{1, 1, 1}); System.out.println("rgb->LCHab:" + lchab[0] + "," + lchab[1] + "," + lchab[2]); float[] xyz = cs.toCIEXYZ(new float[]{0.75f, 0.25f, 0.1f}); System.out.println(" lab->xyz:" + xyz[0] + "," + xyz[1] + "," + xyz[2]); lchab = cs.fromCIEXYZ(xyz); System.out.println("R xyz->LCHab:" + lchab[0] + "," + lchab[1] + "," + lchab[2]); lchab = cs.fromCIEXYZ(new float[]{1, 1, 1}); System.out.println("xyz->LCHab:" + lchab[0] + "," + lchab[1] + "," + lchab[2]); lchab = cs.fromCIEXYZ(new float[]{0.5f, 1, 1}); System.out.println("xyz->LCHab:" + lchab[0] + "," + lchab[1] + "," + lchab[2]); } }