blob: 5b4d7e99f5cb1ec72879ee34d2575cda8048c54c [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.
*/
/**
* @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;
}
}