| /* |
| * 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); |
| } |
| |
| } |