blob: 256445359b72bbf700dc4ee33707f264aecdde59 [file] [log] [blame]
/*
* 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$ */
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 <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.
*/
public CIELabColorSpace() {
this(getD65WhitePoint());
}
/**
* 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.
* @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.
* @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.
* @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} */
@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} */
@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} */
@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} */
@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} */
@Override
public float[] fromRGB(float[] rgbvalue) {
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
float[] xyz = sRGB.toCIEXYZ(rgbvalue);
return fromCIEXYZ(xyz);
}
/** {@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 {@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} */
@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.
* @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 {@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 {@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);
}
}