/* * 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. */ /** * @author Oleg V. Khaschansky * @version $Revision$ */ package java.awt.color; import org.apache.harmony.awt.gl.color.ColorConverter; import org.apache.harmony.awt.gl.color.ColorScaler; import org.apache.harmony.awt.gl.color.ICC_Transform; import org.apache.harmony.awt.internal.nls.Messages; import java.io.*; /** * This class implements the abstract class ColorSpace and represents device * independent and device dependent color spaces. This color space is based on * the International Color Consortium Specification (ICC) File Format for Color * Profiles: <a href="http://www.color.org">http://www.color.org</a> * * @since Android 1.0 */ public class ICC_ColorSpace extends ColorSpace { /** * The Constant serialVersionUID. */ private static final long serialVersionUID = 3455889114070431483L; // Need to keep compatibility with serialized form /** * The Constant serialPersistentFields. */ private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("thisProfile", ICC_Profile.class), //$NON-NLS-1$ new ObjectStreamField("minVal", float[].class), //$NON-NLS-1$ new ObjectStreamField("maxVal", float[].class), //$NON-NLS-1$ new ObjectStreamField("diffMinMax", float[].class), //$NON-NLS-1$ new ObjectStreamField("invDiffMinMax", float[].class), //$NON-NLS-1$ new ObjectStreamField("needScaleInit", Boolean.TYPE) //$NON-NLS-1$ }; /** * According to ICC specification (from http://www.color.org) "For the * CIEXYZ encoding, each component (X, Y, and Z) is encoded as a * u1Fixed15Number". This means that max value for this encoding is 1 + * (32767/32768) */ private static final float MAX_XYZ = 1f + (32767f/32768f); /** * The Constant MAX_SHORT. */ private static final float MAX_SHORT = 65535f; /** * The Constant INV_MAX_SHORT. */ private static final float INV_MAX_SHORT = 1f/MAX_SHORT; /** * The Constant SHORT2XYZ_FACTOR. */ private static final float SHORT2XYZ_FACTOR = MAX_XYZ/MAX_SHORT; /** * The Constant XYZ2SHORT_FACTOR. */ private static final float XYZ2SHORT_FACTOR = MAX_SHORT/MAX_XYZ; /** * The profile. */ private ICC_Profile profile = null; /** * The min values. */ private float minValues[] = null; /** * The max values. */ private float maxValues[] = null; // cache transforms here - performance gain /** * The to rgb transform. */ private ICC_Transform toRGBTransform = null; /** * The from rgb transform. */ private ICC_Transform fromRGBTransform = null; /** * The to xyz transform. */ private ICC_Transform toXYZTransform = null; /** * The from xyz transform. */ private ICC_Transform fromXYZTransform = null; /** * The converter. */ private final ColorConverter converter = new ColorConverter(); /** * The scaler. */ private final ColorScaler scaler = new ColorScaler(); /** * The scaling data loaded. */ private boolean scalingDataLoaded = false; /** * The resolved deserialized inst. */ private ICC_ColorSpace resolvedDeserializedInst; /** * Instantiates a new ICC color space from an ICC_Profile object. * * @param pf * the ICC_Profile object. */ public ICC_ColorSpace(ICC_Profile pf) { super(pf.getColorSpaceType(), pf.getNumComponents()); int pfClass = pf.getProfileClass(); switch (pfClass) { case ICC_Profile.CLASS_COLORSPACECONVERSION: case ICC_Profile.CLASS_DISPLAY: case ICC_Profile.CLASS_OUTPUT: case ICC_Profile.CLASS_INPUT: break; // OK, it is color conversion profile default: // awt.168=Invalid profile class. throw new IllegalArgumentException(Messages.getString("awt.168")); //$NON-NLS-1$ } profile = pf; fillMinMaxValues(); } /** * Gets the ICC_Profile for this ICC_ColorSpace. * * @return the ICC_Profile for this ICC_ColorSpace. */ public ICC_Profile getProfile() { if (profile instanceof ICC_ProfileStub) { profile = ((ICC_ProfileStub) profile).loadProfile(); } return profile; } /** * Performs the transformation of a color from this ColorSpace into the RGB * color space. * * @param colorvalue * the color value in this ColorSpace. * @return the float array with color components in the RGB color space. */ @Override public float[] toRGB(float[] colorvalue) { if (toRGBTransform == null) { ICC_Profile sRGBProfile = ((ICC_ColorSpace) ColorSpace.getInstance(CS_sRGB)).getProfile(); ICC_Profile[] profiles = {getProfile(), sRGBProfile}; toRGBTransform = new ICC_Transform(profiles); if (!scalingDataLoaded) { scaler.loadScalingData(this); scalingDataLoaded = true; } } short[] data = new short[getNumComponents()]; scaler.scale(colorvalue, data, 0); short[] converted = converter.translateColor(toRGBTransform, data, null); // unscale to sRGB float[] res = new float[3]; res[0] = ((converted[0] & 0xFFFF)) * INV_MAX_SHORT; res[1] = ((converted[1] & 0xFFFF)) * INV_MAX_SHORT; res[2] = ((converted[2] & 0xFFFF)) * INV_MAX_SHORT; return res; } /** * Performs the transformation of a color from this ColorSpace into the * CS_CIEXYZ color space. * * @param colorvalue * the color value in this ColorSpace. * @return the float array with color components in the CS_CIEXYZ color * space. */ @Override public float[] toCIEXYZ(float[] colorvalue) { if (toXYZTransform == null) { ICC_Profile xyzProfile = ((ICC_ColorSpace) ColorSpace.getInstance(CS_CIEXYZ)).getProfile(); ICC_Profile[] profiles = {getProfile(), xyzProfile}; try { int[] intents = { ICC_Profile.icRelativeColorimetric, ICC_Profile.icPerceptual}; toXYZTransform = new ICC_Transform(profiles, intents); } catch (CMMException e) { // No such tag, use what we can toXYZTransform = new ICC_Transform(profiles); } if (!scalingDataLoaded) { scaler.loadScalingData(this); scalingDataLoaded = true; } } short[] data = new short[getNumComponents()]; scaler.scale(colorvalue, data, 0); short[] converted = converter.translateColor(toXYZTransform, data, null); // unscale to XYZ float[] res = new float[3]; res[0] = ((converted[0] & 0xFFFF)) * SHORT2XYZ_FACTOR; res[1] = ((converted[1] & 0xFFFF)) * SHORT2XYZ_FACTOR; res[2] = ((converted[2] & 0xFFFF)) * SHORT2XYZ_FACTOR; return res; } /** * Performs the transformation of a color from the RGB color space into this * ColorSpace. * * @param rgbvalue * the float array representing a color in the RGB color space. * @return the float array with the transformed color components. */ @Override public float[] fromRGB(float[] rgbvalue) { if (fromRGBTransform == null) { ICC_Profile sRGBProfile = ((ICC_ColorSpace) ColorSpace.getInstance(CS_sRGB)).getProfile(); ICC_Profile[] profiles = {sRGBProfile, getProfile()}; fromRGBTransform = new ICC_Transform(profiles); if (!scalingDataLoaded) { scaler.loadScalingData(this); scalingDataLoaded = true; } } // scale rgb value to short short[] scaledRGBValue = new short[3]; scaledRGBValue[0] = (short)(rgbvalue[0] * MAX_SHORT + 0.5f); scaledRGBValue[1] = (short)(rgbvalue[1] * MAX_SHORT + 0.5f); scaledRGBValue[2] = (short)(rgbvalue[2] * MAX_SHORT + 0.5f); short[] converted = converter.translateColor(fromRGBTransform, scaledRGBValue, null); float[] res = new float[getNumComponents()]; scaler.unscale(res, converted, 0); return res; } /** * Performs the transformation of a color from the CS_CIEXYZ color space * into this ColorSpace. * * @param xyzvalue * the float array representing a color in the CS_CIEXYZ color * space. * @return the float array with the transformed color components. */ @Override public float[] fromCIEXYZ(float[] xyzvalue) { if (fromXYZTransform == null) { ICC_Profile xyzProfile = ((ICC_ColorSpace) ColorSpace.getInstance(CS_CIEXYZ)).getProfile(); ICC_Profile[] profiles = {xyzProfile, getProfile()}; try { int[] intents = { ICC_Profile.icPerceptual, ICC_Profile.icRelativeColorimetric}; fromXYZTransform = new ICC_Transform(profiles, intents); } catch (CMMException e) { // No such tag, use what we can fromXYZTransform = new ICC_Transform(profiles); } if (!scalingDataLoaded) { scaler.loadScalingData(this); scalingDataLoaded = true; } } // scale xyz value to short short[] scaledXYZValue = new short[3]; scaledXYZValue[0] = (short)(xyzvalue[0] * XYZ2SHORT_FACTOR + 0.5f); scaledXYZValue[1] = (short)(xyzvalue[1] * XYZ2SHORT_FACTOR + 0.5f); scaledXYZValue[2] = (short)(xyzvalue[2] * XYZ2SHORT_FACTOR + 0.5f); short[] converted = converter.translateColor(fromXYZTransform, scaledXYZValue, null); float[] res = new float[getNumComponents()]; scaler.unscale(res, converted, 0); return res; } /** * Gets the minimum normalized color component value for the specified * component. * * @param component * the component to determine the minimum value. * @return the minimum normalized value of the component. */ @Override public float getMinValue(int component) { if ((component < 0) || (component > this.getNumComponents() - 1)) { // awt.169=Component index out of range throw new IllegalArgumentException(Messages.getString("awt.169")); //$NON-NLS-1$ } return minValues[component]; } /** * Gets the maximum normalized color component value for the specified * component. * * @param component * the component to determine the maximum value. * @return the maximum normalized value of the component. */ @Override public float getMaxValue(int component) { if ((component < 0) || (component > this.getNumComponents() - 1)) { // awt.169=Component index out of range throw new IllegalArgumentException(Messages.getString("awt.169")); //$NON-NLS-1$ } return maxValues[component]; } /** * Fill min max values. */ private void fillMinMaxValues() { int n = getNumComponents(); maxValues = new float[n]; minValues = new float[n]; switch (getType()) { case ColorSpace.TYPE_XYZ: minValues[0] = 0; minValues[1] = 0; minValues[2] = 0; maxValues[0] = MAX_XYZ; maxValues[1] = MAX_XYZ; maxValues[2] = MAX_XYZ; break; case ColorSpace.TYPE_Lab: minValues[0] = 0; minValues[1] = -128; minValues[2] = -128; maxValues[0] = 100; maxValues[1] = 127; maxValues[2] = 127; break; default: for(int i=0; i<n; i++) { minValues[i] = 0; maxValues[i] = 1; } } } /** * Write object. * * @param out * the out * @throws IOException * Signals that an I/O exception has occurred. */ private void writeObject(ObjectOutputStream out) throws IOException { ObjectOutputStream.PutField fields = out.putFields(); fields.put("thisProfile", profile); //$NON-NLS-1$ fields.put("minVal", null); //$NON-NLS-1$ fields.put("maxVal", null); //$NON-NLS-1$ fields.put("diffMinMax", null); //$NON-NLS-1$ fields.put("invDiffMinMax", null); //$NON-NLS-1$ fields.put("needScaleInit", true); //$NON-NLS-1$ out.writeFields(); } /** * Read object. * * @param in * the in * @throws IOException * Signals that an I/O exception has occurred. * @throws ClassNotFoundException * the class not found exception */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { ObjectInputStream.GetField fields = in.readFields(); resolvedDeserializedInst = new ICC_ColorSpace((ICC_Profile) fields.get("thisProfile", null)); //$NON-NLS-1$ } /** * Read resolve. * * @return the object * @throws ObjectStreamException * the object stream exception */ Object readResolve() throws ObjectStreamException { return resolvedDeserializedInst; } }