Home | History | Annotate | Download | only in color
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 /**
     18  * @author Oleg V. Khaschansky
     19  * @version $Revision$
     20  */
     21 package java.awt.color;
     22 
     23 import org.apache.harmony.awt.gl.color.ColorConverter;
     24 import org.apache.harmony.awt.gl.color.ColorScaler;
     25 import org.apache.harmony.awt.gl.color.ICC_Transform;
     26 import org.apache.harmony.awt.internal.nls.Messages;
     27 
     28 import java.io.*;
     29 
     30 /**
     31  * This class implements the abstract class ColorSpace and represents device
     32  * independent and device dependent color spaces. This color space is based on
     33  * the International Color Consortium Specification (ICC) File Format for Color
     34  * Profiles: <a href="http://www.color.org">http://www.color.org</a>
     35  *
     36  * @since Android 1.0
     37  */
     38 public class ICC_ColorSpace extends ColorSpace {
     39 
     40     /**
     41      * The Constant serialVersionUID.
     42      */
     43     private static final long serialVersionUID = 3455889114070431483L;
     44 
     45     // Need to keep compatibility with serialized form
     46     /**
     47      * The Constant serialPersistentFields.
     48      */
     49     private static final ObjectStreamField[]
     50       serialPersistentFields = {
     51         new ObjectStreamField("thisProfile", ICC_Profile.class), //$NON-NLS-1$
     52         new ObjectStreamField("minVal", float[].class), //$NON-NLS-1$
     53         new ObjectStreamField("maxVal", float[].class), //$NON-NLS-1$
     54         new ObjectStreamField("diffMinMax", float[].class), //$NON-NLS-1$
     55         new ObjectStreamField("invDiffMinMax", float[].class), //$NON-NLS-1$
     56         new ObjectStreamField("needScaleInit", Boolean.TYPE) //$NON-NLS-1$
     57     };
     58 
     59 
     60    /**
     61      * According to ICC specification (from http://www.color.org) "For the
     62      * CIEXYZ encoding, each component (X, Y, and Z) is encoded as a
     63      * u1Fixed15Number". This means that max value for this encoding is 1 +
     64      * (32767/32768)
     65      */
     66     private static final float MAX_XYZ = 1f + (32767f/32768f);
     67 
     68     /**
     69      * The Constant MAX_SHORT.
     70      */
     71     private static final float MAX_SHORT = 65535f;
     72 
     73     /**
     74      * The Constant INV_MAX_SHORT.
     75      */
     76     private static final float INV_MAX_SHORT = 1f/MAX_SHORT;
     77 
     78     /**
     79      * The Constant SHORT2XYZ_FACTOR.
     80      */
     81     private static final float SHORT2XYZ_FACTOR = MAX_XYZ/MAX_SHORT;
     82 
     83     /**
     84      * The Constant XYZ2SHORT_FACTOR.
     85      */
     86     private static final float XYZ2SHORT_FACTOR = MAX_SHORT/MAX_XYZ;
     87 
     88     /**
     89      * The profile.
     90      */
     91     private ICC_Profile profile = null;
     92 
     93     /**
     94      * The min values.
     95      */
     96     private float minValues[] = null;
     97 
     98     /**
     99      * The max values.
    100      */
    101     private float maxValues[] = null;
    102 
    103     // cache transforms here - performance gain
    104     /**
    105      * The to rgb transform.
    106      */
    107     private ICC_Transform toRGBTransform = null;
    108 
    109     /**
    110      * The from rgb transform.
    111      */
    112     private ICC_Transform fromRGBTransform = null;
    113 
    114     /**
    115      * The to xyz transform.
    116      */
    117     private ICC_Transform toXYZTransform = null;
    118 
    119     /**
    120      * The from xyz transform.
    121      */
    122     private ICC_Transform fromXYZTransform = null;
    123 
    124     /**
    125      * The converter.
    126      */
    127     private final ColorConverter converter = new ColorConverter();
    128 
    129     /**
    130      * The scaler.
    131      */
    132     private final ColorScaler scaler = new ColorScaler();
    133 
    134     /**
    135      * The scaling data loaded.
    136      */
    137     private boolean scalingDataLoaded = false;
    138 
    139     /**
    140      * The resolved deserialized inst.
    141      */
    142     private ICC_ColorSpace resolvedDeserializedInst;
    143 
    144     /**
    145      * Instantiates a new ICC color space from an ICC_Profile object.
    146      *
    147      * @param pf
    148      *            the ICC_Profile object.
    149      */
    150     public ICC_ColorSpace(ICC_Profile pf) {
    151         super(pf.getColorSpaceType(), pf.getNumComponents());
    152 
    153         int pfClass = pf.getProfileClass();
    154 
    155         switch (pfClass) {
    156             case ICC_Profile.CLASS_COLORSPACECONVERSION:
    157             case ICC_Profile.CLASS_DISPLAY:
    158             case ICC_Profile.CLASS_OUTPUT:
    159             case ICC_Profile.CLASS_INPUT:
    160                 break; // OK, it is color conversion profile
    161             default:
    162                 // awt.168=Invalid profile class.
    163                 throw new IllegalArgumentException(Messages.getString("awt.168")); //$NON-NLS-1$
    164         }
    165 
    166         profile = pf;
    167         fillMinMaxValues();
    168     }
    169 
    170     /**
    171      * Gets the ICC_Profile for this ICC_ColorSpace.
    172      *
    173      * @return the ICC_Profile for this ICC_ColorSpace.
    174      */
    175     public ICC_Profile getProfile() {
    176         if (profile instanceof ICC_ProfileStub) {
    177             profile = ((ICC_ProfileStub) profile).loadProfile();
    178         }
    179 
    180         return profile;
    181     }
    182 
    183     /**
    184      * Performs the transformation of a color from this ColorSpace into the RGB
    185      * color space.
    186      *
    187      * @param colorvalue
    188      *            the color value in this ColorSpace.
    189      * @return the float array with color components in the RGB color space.
    190      */
    191     @Override
    192     public float[] toRGB(float[] colorvalue) {
    193         if (toRGBTransform == null) {
    194             ICC_Profile sRGBProfile =
    195                 ((ICC_ColorSpace) ColorSpace.getInstance(CS_sRGB)).getProfile();
    196             ICC_Profile[] profiles = {getProfile(), sRGBProfile};
    197             toRGBTransform = new ICC_Transform(profiles);
    198             if (!scalingDataLoaded) {
    199                 scaler.loadScalingData(this);
    200                 scalingDataLoaded = true;
    201             }
    202         }
    203 
    204         short[] data = new short[getNumComponents()];
    205 
    206         scaler.scale(colorvalue, data, 0);
    207 
    208         short[] converted =
    209             converter.translateColor(toRGBTransform, data, null);
    210 
    211         // unscale to sRGB
    212         float[] res = new float[3];
    213 
    214         res[0] = ((converted[0] & 0xFFFF)) * INV_MAX_SHORT;
    215         res[1] = ((converted[1] & 0xFFFF)) * INV_MAX_SHORT;
    216         res[2] = ((converted[2] & 0xFFFF)) * INV_MAX_SHORT;
    217 
    218         return res;
    219     }
    220 
    221     /**
    222      * Performs the transformation of a color from this ColorSpace into the
    223      * CS_CIEXYZ color space.
    224      *
    225      * @param colorvalue
    226      *            the color value in this ColorSpace.
    227      * @return the float array with color components in the CS_CIEXYZ color
    228      *         space.
    229      */
    230     @Override
    231     public float[] toCIEXYZ(float[] colorvalue) {
    232         if (toXYZTransform == null) {
    233             ICC_Profile xyzProfile =
    234                 ((ICC_ColorSpace) ColorSpace.getInstance(CS_CIEXYZ)).getProfile();
    235             ICC_Profile[] profiles = {getProfile(), xyzProfile};
    236             try {
    237                 int[] intents = {
    238                         ICC_Profile.icRelativeColorimetric,
    239                         ICC_Profile.icPerceptual};
    240                 toXYZTransform = new ICC_Transform(profiles, intents);
    241             } catch (CMMException e) { // No such tag, use what we can
    242                 toXYZTransform = new ICC_Transform(profiles);
    243             }
    244 
    245             if (!scalingDataLoaded) {
    246                 scaler.loadScalingData(this);
    247                 scalingDataLoaded = true;
    248             }
    249         }
    250 
    251         short[] data = new short[getNumComponents()];
    252 
    253         scaler.scale(colorvalue, data, 0);
    254 
    255         short[] converted =
    256             converter.translateColor(toXYZTransform, data, null);
    257 
    258         // unscale to XYZ
    259         float[] res = new float[3];
    260 
    261         res[0] = ((converted[0] & 0xFFFF)) * SHORT2XYZ_FACTOR;
    262         res[1] = ((converted[1] & 0xFFFF)) * SHORT2XYZ_FACTOR;
    263         res[2] = ((converted[2] & 0xFFFF)) * SHORT2XYZ_FACTOR;
    264 
    265         return res;
    266     }
    267 
    268     /**
    269      * Performs the transformation of a color from the RGB color space into this
    270      * ColorSpace.
    271      *
    272      * @param rgbvalue
    273      *            the float array representing a color in the RGB color space.
    274      * @return the float array with the transformed color components.
    275      */
    276     @Override
    277     public float[] fromRGB(float[] rgbvalue) {
    278         if (fromRGBTransform == null) {
    279             ICC_Profile sRGBProfile =
    280                 ((ICC_ColorSpace) ColorSpace.getInstance(CS_sRGB)).getProfile();
    281             ICC_Profile[] profiles = {sRGBProfile, getProfile()};
    282             fromRGBTransform = new ICC_Transform(profiles);
    283             if (!scalingDataLoaded) {
    284                 scaler.loadScalingData(this);
    285                 scalingDataLoaded = true;
    286             }
    287         }
    288 
    289         // scale rgb value to short
    290         short[] scaledRGBValue = new short[3];
    291         scaledRGBValue[0] = (short)(rgbvalue[0] * MAX_SHORT + 0.5f);
    292         scaledRGBValue[1] = (short)(rgbvalue[1] * MAX_SHORT + 0.5f);
    293         scaledRGBValue[2] = (short)(rgbvalue[2] * MAX_SHORT + 0.5f);
    294 
    295         short[] converted =
    296             converter.translateColor(fromRGBTransform, scaledRGBValue, null);
    297 
    298         float[] res = new float[getNumComponents()];
    299 
    300         scaler.unscale(res, converted, 0);
    301 
    302         return res;
    303     }
    304 
    305     /**
    306      * Performs the transformation of a color from the CS_CIEXYZ color space
    307      * into this ColorSpace.
    308      *
    309      * @param xyzvalue
    310      *            the float array representing a color in the CS_CIEXYZ color
    311      *            space.
    312      * @return the float array with the transformed color components.
    313      */
    314     @Override
    315     public float[] fromCIEXYZ(float[] xyzvalue) {
    316         if (fromXYZTransform == null) {
    317             ICC_Profile xyzProfile =
    318                 ((ICC_ColorSpace) ColorSpace.getInstance(CS_CIEXYZ)).getProfile();
    319             ICC_Profile[] profiles = {xyzProfile, getProfile()};
    320             try {
    321                 int[] intents = {
    322                         ICC_Profile.icPerceptual,
    323                         ICC_Profile.icRelativeColorimetric};
    324                 fromXYZTransform = new ICC_Transform(profiles, intents);
    325             } catch (CMMException e) { // No such tag, use what we can
    326                 fromXYZTransform = new ICC_Transform(profiles);
    327             }
    328 
    329             if (!scalingDataLoaded) {
    330                 scaler.loadScalingData(this);
    331                 scalingDataLoaded = true;
    332             }
    333 
    334         }
    335 
    336         // scale xyz value to short
    337         short[] scaledXYZValue = new short[3];
    338         scaledXYZValue[0] = (short)(xyzvalue[0] * XYZ2SHORT_FACTOR + 0.5f);
    339         scaledXYZValue[1] = (short)(xyzvalue[1] * XYZ2SHORT_FACTOR + 0.5f);
    340         scaledXYZValue[2] = (short)(xyzvalue[2] * XYZ2SHORT_FACTOR + 0.5f);
    341 
    342         short[] converted =
    343             converter.translateColor(fromXYZTransform, scaledXYZValue, null);
    344 
    345         float[] res = new float[getNumComponents()];
    346 
    347         scaler.unscale(res, converted, 0);
    348 
    349         return res;
    350     }
    351 
    352     /**
    353      * Gets the minimum normalized color component value for the specified
    354      * component.
    355      *
    356      * @param component
    357      *            the component to determine the minimum value.
    358      * @return the minimum normalized value of the component.
    359      */
    360     @Override
    361     public float getMinValue(int component) {
    362         if ((component < 0) || (component > this.getNumComponents() - 1)) {
    363             // awt.169=Component index out of range
    364             throw new IllegalArgumentException(Messages.getString("awt.169")); //$NON-NLS-1$
    365         }
    366 
    367         return minValues[component];
    368     }
    369 
    370     /**
    371      * Gets the maximum normalized color component value for the specified
    372      * component.
    373      *
    374      * @param component
    375      *            the component to determine the maximum value.
    376      * @return the maximum normalized value of the component.
    377      */
    378     @Override
    379     public float getMaxValue(int component) {
    380         if ((component < 0) || (component > this.getNumComponents() - 1)) {
    381             // awt.169=Component index out of range
    382             throw new IllegalArgumentException(Messages.getString("awt.169")); //$NON-NLS-1$
    383         }
    384 
    385         return maxValues[component];
    386     }
    387 
    388     /**
    389      * Fill min max values.
    390      */
    391     private void fillMinMaxValues() {
    392         int n = getNumComponents();
    393         maxValues = new float[n];
    394         minValues = new float[n];
    395         switch (getType()) {
    396             case ColorSpace.TYPE_XYZ:
    397                 minValues[0] = 0;
    398                 minValues[1] = 0;
    399                 minValues[2] = 0;
    400                 maxValues[0] = MAX_XYZ;
    401                 maxValues[1] = MAX_XYZ;
    402                 maxValues[2] = MAX_XYZ;
    403                 break;
    404             case ColorSpace.TYPE_Lab:
    405                 minValues[0] = 0;
    406                 minValues[1] = -128;
    407                 minValues[2] = -128;
    408                 maxValues[0] = 100;
    409                 maxValues[1] = 127;
    410                 maxValues[2] = 127;
    411                 break;
    412             default:
    413                 for(int i=0; i<n; i++) {
    414                     minValues[i] = 0;
    415                     maxValues[i] = 1;
    416                 }
    417         }
    418     }
    419 
    420     /**
    421      * Write object.
    422      *
    423      * @param out
    424      *            the out
    425      * @throws IOException
    426      *             Signals that an I/O exception has occurred.
    427      */
    428     private void writeObject(ObjectOutputStream out) throws IOException {
    429         ObjectOutputStream.PutField fields = out.putFields();
    430 
    431         fields.put("thisProfile", profile); //$NON-NLS-1$
    432         fields.put("minVal", null); //$NON-NLS-1$
    433         fields.put("maxVal", null); //$NON-NLS-1$
    434         fields.put("diffMinMax", null); //$NON-NLS-1$
    435         fields.put("invDiffMinMax", null); //$NON-NLS-1$
    436         fields.put("needScaleInit", true); //$NON-NLS-1$
    437 
    438         out.writeFields();
    439     }
    440 
    441     /**
    442      * Read object.
    443      *
    444      * @param in
    445      *            the in
    446      * @throws IOException
    447      *             Signals that an I/O exception has occurred.
    448      * @throws ClassNotFoundException
    449      *             the class not found exception
    450      */
    451     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    452         ObjectInputStream.GetField fields = in.readFields();
    453         resolvedDeserializedInst =
    454                 new ICC_ColorSpace((ICC_Profile) fields.get("thisProfile", null)); //$NON-NLS-1$
    455     }
    456 
    457     /**
    458      * Read resolve.
    459      *
    460      * @return the object
    461      * @throws ObjectStreamException
    462      *             the object stream exception
    463      */
    464     Object readResolve() throws ObjectStreamException {
    465         return resolvedDeserializedInst;
    466     }
    467 }
    468 
    469