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