/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: CIELabColorSpace.java 1051421 2010-12-21 08:54:25Z jeremias $ */

package org.apache.xmlgraphics.java2d.color;

import java.awt.Color;
import java.awt.color.ColorSpace;

This class defines the CIE L*a*b* (CIE 1976) color space. Valid values for L* are between 0 and 100, for a* and b* between -127 and +127.
See Also:
/** * This class defines the CIE L*a*b* (CIE 1976) color space. Valid values for L* are between 0 * and 100, for a* and b* between -127 and +127. * @see <a href="http://en.wikipedia.org/wiki/Lab_color_space" * >http://en.wikipedia.org/wiki/Lab_color_space</a> */
public class CIELabColorSpace extends ColorSpace { private static final long serialVersionUID = -1821569090707520704L; //CIE XYZ tristimulus values of the reference white point: Observer= 2 degrees, Illuminant= D65 private static final float REF_X_D65 = 95.047f; private static final float REF_Y_D65 = 100.000f; private static final float REF_Z_D65 = 108.883f; //CIE XYZ tristimulus values of the reference white point: Illuminant= D50 private static final float REF_X_D50 = 96.42f; private static final float REF_Y_D50 = 100.00f; private static final float REF_Z_D50 = 82.49f; private static final double D = 6.0 / 29.0; private static final double REF_A = 1.0 / (3 * Math.pow(D, 2)); //7.787037... private static final double REF_B = 16.0 / 116.0; private static final double T0 = Math.pow(D, 3); //0.008856... private float wpX; private float wpY; private float wpZ;
Default constructor using the D65 white point.
/** * Default constructor using the D65 white point. */
public CIELabColorSpace() { this(getD65WhitePoint()); }
CIE Lab space constructor which allows to give an arbitrary white point.
Params:
  • whitePoint – the white point in XYZ coordinates (valid values: 0.0f to 1.0f, although values slightly larger than 1.0f are common)
/** * CIE Lab space constructor which allows to give an arbitrary white point. * @param whitePoint the white point in XYZ coordinates (valid values: 0.0f to 1.0f, although * values slightly larger than 1.0f are common) */
public CIELabColorSpace(float[] whitePoint) { super(ColorSpace.TYPE_Lab, 3); checkNumComponents(whitePoint, 3); this.wpX = whitePoint[0]; this.wpY = whitePoint[1]; this.wpZ = whitePoint[2]; }
Returns the D65 white point.
Returns:the D65 white point.
/** * Returns the D65 white point. * @return the D65 white point. */
public static float[] getD65WhitePoint() { return new float[] {REF_X_D65, REF_Y_D65, REF_Z_D65}; }
Returns the D50 white point.
Returns:the D50 white point.
/** * Returns the D50 white point. * @return the D50 white point. */
public static float[] getD50WhitePoint() { return new float[] {REF_X_D50, REF_Y_D50, REF_Z_D50}; } private void checkNumComponents(float[] colorvalue) { checkNumComponents(colorvalue, getNumComponents()); } private void checkNumComponents(float[] colorvalue, int expected) { if (colorvalue == null) { throw new NullPointerException("color value may not be null"); } if (colorvalue.length != expected) { throw new IllegalArgumentException("Expected " + expected + " components, but got " + colorvalue.length); } }
Returns the configured white point.
Returns:the white point in CIE XYZ coordinates
/** * Returns the configured white point. * @return the white point in CIE XYZ coordinates */
public float[] getWhitePoint() { return new float[] {wpX, wpY, wpZ}; } private static final String CIE_LAB_ONLY_HAS_3_COMPONENTS = "CIE Lab only has 3 components!";
{@inheritDoc}
/** {@inheritDoc} */
@Override public float getMinValue(int component) { switch (component) { case 0: //L* return 0f; case 1: //a* case 2: //b* return -128f; default: throw new IllegalArgumentException(CIE_LAB_ONLY_HAS_3_COMPONENTS); } }
{@inheritDoc}
/** {@inheritDoc} */
@Override public float getMaxValue(int component) { switch (component) { case 0: //L* return 100f; case 1: //a* case 2: //b* return 128f; default: throw new IllegalArgumentException(CIE_LAB_ONLY_HAS_3_COMPONENTS); } }
{@inheritDoc}
/** {@inheritDoc} */
@Override public String getName(int component) { switch (component) { case 0: return "L*"; case 1: return "a*"; case 2: return "b*"; default: throw new IllegalArgumentException(CIE_LAB_ONLY_HAS_3_COMPONENTS); } } //Note: the conversion functions used here were mostly borrowed from Apache Commons Sanselan //and adjusted to the local requirements.
{@inheritDoc}
/** {@inheritDoc} */
@Override public float[] fromCIEXYZ(float[] colorvalue) { checkNumComponents(colorvalue, 3); float x = colorvalue[0]; float y = colorvalue[1]; float z = colorvalue[2]; double varX = x / wpX; double varY = y / wpY; double varZ = z / wpZ; if (varX > T0) { varX = Math.pow(varX, (1 / 3.0)); } else { varX = (REF_A * varX) + REF_B; } if (varY > T0) { varY = Math.pow(varY, 1 / 3.0); } else { varY = (REF_A * varY) + REF_B; } if (varZ > T0) { varZ = Math.pow(varZ, 1 / 3.0); } else { varZ = (REF_A * varZ) + REF_B; } float l = (float)((116 * varY) - 16); float a = (float)(500 * (varX - varY)); float b = (float)(200 * (varY - varZ)); //Normalize to range 0.0..1.0 l = normalize(l, 0); a = normalize(a, 1); b = normalize(b, 2); return new float[] {l, a, b}; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public float[] fromRGB(float[] rgbvalue) { ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB); float[] xyz = sRGB.toCIEXYZ(rgbvalue); return fromCIEXYZ(xyz); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public float[] toCIEXYZ(float[] colorvalue) { checkNumComponents(colorvalue); //Scale to native value range float l = denormalize(colorvalue[0], 0); float a = denormalize(colorvalue[1], 1); float b = denormalize(colorvalue[2], 2); return toCIEXYZNative(l, a, b); }
Transforms a color value assumed to be in this ColorSpace into the CS_CIEXYZ conversion color space. This method uses component values in CIE Lab's native color ranges rather than the normalized values between 0 and 1.
Params:
  • l – the L* component (values between 0 and 100)
  • a – the a* component (usually between -128 and +128)
  • b – the b* component (usually between -128 and +128)
See Also:
Returns:the XYZ color values
/** * Transforms a color value assumed to be in this {@link ColorSpace} * into the CS_CIEXYZ conversion color space. This method uses component values * in CIE Lab's native color ranges rather than the normalized values between 0 and 1. * @param l the L* component (values between 0 and 100) * @param a the a* component (usually between -128 and +128) * @param b the b* component (usually between -128 and +128) * @return the XYZ color values * @see #toCIEXYZ(float[]) */
public float[] toCIEXYZNative(float l, float a, float b) { double varY = (l + 16) / 116.0; double varX = a / 500 + varY; double varZ = varY - b / 200.0; if (Math.pow(varY, 3) > T0) { varY = Math.pow(varY, 3); } else { varY = (varY - 16 / 116.0) / REF_A; } if (Math.pow(varX, 3) > T0) { varX = Math.pow(varX, 3); } else { varX = (varX - 16 / 116.0) / REF_A; } if (Math.pow(varZ, 3) > T0) { varZ = Math.pow(varZ, 3); } else { varZ = (varZ - 16 / 116.0) / REF_A; } float x = (float)(wpX * varX / 100); float y = (float)(wpY * varY / 100); float z = (float)(wpZ * varZ / 100); return new float[] {x, y, z}; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public float[] toRGB(float[] colorvalue) { ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB); float[] xyz = toCIEXYZ(colorvalue); return sRGB.fromCIEXYZ(xyz); } private float getNativeValueRange(int component) { return getMaxValue(component) - getMinValue(component); } private float normalize(float value, int component) { return (value - getMinValue(component)) / getNativeValueRange(component); } private float denormalize(float value, int component) { return value * getNativeValueRange(component) + getMinValue(component); }
Converts normalized (0..1) color components to CIE L*a*b*'s native value range.
Params:
  • comps – the normalized components.
Returns:the denormalized components
/** * Converts normalized (0..1) color components to CIE L*a*b*'s native value range. * @param comps the normalized components. * @return the denormalized components */
public float[] toNativeComponents(float[] comps) { checkNumComponents(comps); float[] nativeComps = new float[comps.length]; for (int i = 0, c = comps.length; i < c; i++) { nativeComps[i] = denormalize(comps[i], i); } return nativeComps; }
Creates a Color instance from color values usually used by the L*a*b* color space by scaling them to the 0.0..1.0 range expected by Color's constructor.
Params:
  • colorvalue – the original color values (native value range, i.e. not normalized to 0.0..1.0)
  • alpha – the alpha component
Returns:the requested color instance
/** * Creates a {@link Color} instance from color values usually used by the L*a*b* color space * by scaling them to the 0.0..1.0 range expected by Color's constructor. * @param colorvalue the original color values * (native value range, i.e. not normalized to 0.0..1.0) * @param alpha the alpha component * @return the requested color instance */
public Color toColor(float[] colorvalue, float alpha) { int c = colorvalue.length; float[] normalized = new float[c]; for (int i = 0; i < c; i++) { normalized[i] = normalize(colorvalue[i], i); } //Using ColorWithAlternatives for better equals() functionality return new ColorWithAlternatives(this, normalized, alpha, null); }
Creates a Color instance from color values usually used by the L*a*b* color space by scaling them to the 0.0..1.0 range expected by Color's constructor.
Params:
  • l – the L* component (values between 0 and 100)
  • a – the a* component (usually between -128 and +127)
  • b – the b* component (usually between -128 and +127)
  • alpha – the alpha component (values between 0 and 1)
Returns:the requested color instance
/** * Creates a {@link Color} instance from color values usually used by the L*a*b* color space * by scaling them to the 0.0..1.0 range expected by Color's constructor. * @param l the L* component (values between 0 and 100) * @param a the a* component (usually between -128 and +127) * @param b the b* component (usually between -128 and +127) * @param alpha the alpha component (values between 0 and 1) * @return the requested color instance */
public Color toColor(float l, float a, float b, float alpha) { return toColor(new float[] {l, a, b}, alpha); } }