1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 /* 4 * $RCSfile: LCColorConvertOpImage.java,v $ 5 * 6 * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved. 7 * 8 * Use is subject to license terms. 9 * 10 * $Revision: 1.4 $ 11 * $Date: 2005/03/28 17:45:12 $ 12 * $State: Exp $ 13 */ 14 15 package com.lightcrafts.jai.opimage; 16 17 import com.lightcrafts.jai.operator.LCColorConvertDescriptor; 18 19 import java.awt.Point; 20 import java.awt.Rectangle; 21 import java.awt.color.ColorSpace; 22 import java.awt.color.ICC_ColorSpace; 23 import java.awt.color.ICC_Profile; 24 import java.awt.image.*; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.Map; 28 import com.lightcrafts.mediax.jai.ColorSpaceJAI; 29 import com.lightcrafts.mediax.jai.ImageLayout; 30 import com.lightcrafts.mediax.jai.PointOpImage; 31 import com.lightcrafts.mediax.jai.RasterFactory; 32 import java.lang.ref.SoftReference; 33 34 /** 35 * An <code>OpImage</code> implementing the "LCColorConvert" operation as 36 * described in <code>com.lightcrafts.mediax.jai.operator.ColorConvertDescriptor</code>. 37 * 38 * @since EA4 39 * 40 * @see com.lightcrafts.mediax.jai.PointOpImage 41 * @see com.lightcrafts.mediax.jai.operator.ColorConvertDescriptor 42 * 43 */ 44 final class LCColorConvertOpImage extends PointOpImage { 45 /** Cache a rgb color space */ 46 private static final ColorSpace rgbColorSpace 47 = ColorSpace.getInstance(ColorSpace.CS_sRGB); 48 49 private static SoftReference<Map<ArrayList<?>, ColorConvertOp>> softRef = null; 50 51 /** The source image parameters */ 52 private ImageParameters srcParam = null; 53 54 /** The source image parameters */ 55 private ImageParameters dstParam = null; 56 57 /** The intermediate image parameters */ 58 private ImageParameters tempParam = null; 59 60 /** The Java 2D LCColorConvertOp instance for converting integer type */ 61 private ColorConvertOp colorConvertOp = null; 62 63 /** case number */ 64 private int caseNumber; 65 intFromBigEndian(byte[] array, int index)66 private static int intFromBigEndian(byte[] array, int index) { 67 return (((array[index] & 0xff) << 24) | 68 ((array[index+1] & 0xff) << 16) | 69 ((array[index+2] & 0xff) << 8) | 70 (array[index+3] & 0xff)); 71 } 72 73 intToBigEndian(int value, byte[] array, int index)74 private static void intToBigEndian(int value, byte[] array, int index) { 75 array[index] = (byte) (value >> 24); 76 array[index+1] = (byte) (value >> 16); 77 array[index+2] = (byte) (value >> 8); 78 array[index+3] = (byte) (value); 79 } 80 81 /** 82 * Sets the rendering intent of the profile. 83 * This is used to select the proper transform from a profile that 84 * has multiple transforms. 85 */ setRenderingIntent(ICC_Profile profile, int renderingIntent)86 private static void setRenderingIntent(ICC_Profile profile, int renderingIntent) { 87 byte[] theHeader = profile.getData(ICC_Profile.icSigHead); /* getData will activate deferred 88 profiles if necessary */ 89 intToBigEndian (renderingIntent, theHeader, ICC_Profile.icHdrRenderingIntent); 90 /* set the rendering intent */ 91 profile.setData (ICC_Profile.icSigHead, theHeader); 92 } 93 94 /** 95 * Returns the rendering intent of the profile. 96 * This is used to select the proper transform from a profile that 97 * has multiple transforms. It is typically set in a source profile 98 * to select a transform from an output profile. 99 */ getRenderingIntent(ICC_Profile profile)100 private static int getRenderingIntent(ICC_Profile profile) { 101 byte[] theHeader = profile.getData(ICC_Profile.icSigHead); /* getData will activate deferred 102 profiles if necessary */ 103 int renderingIntent = intFromBigEndian(theHeader, ICC_Profile.icHdrRenderingIntent); 104 /* set the rendering intent */ 105 return renderingIntent; 106 } 107 108 /** 109 * Retrive/cache the ColorConvertOp. Because instantiate a ColorConvertOp 110 * is a time-consuming step, create a hashtable referred to by a 111 * SoftReference to cache the ColorConvertOp for using repeatedly. 112 * 113 * @param src the color space of the source image 114 * dst the color space of the destination image 115 * @return The ColorConvertOp to convert from the source color space to 116 * the destination color space. 117 */ 118 private static synchronized ColorConvertOp getColorConvertOp(ColorSpace src, ColorSpace dst, LCColorConvertDescriptor.RenderingIntent renderingIntent)119 getColorConvertOp(ColorSpace src, ColorSpace dst, LCColorConvertDescriptor.RenderingIntent renderingIntent) { 120 Map<ArrayList<?>, ColorConvertOp> colorConvertOpBuf; 121 122 if (softRef == null || ((colorConvertOpBuf = softRef.get()) == null)) { 123 colorConvertOpBuf = new HashMap<ArrayList<?>, ColorConvertOp>(); 124 softRef = new SoftReference<Map<ArrayList<?>, ColorConvertOp>>(colorConvertOpBuf); 125 } 126 127 ArrayList<Object> hashcode = new ArrayList<Object>(2); 128 hashcode.add(0, src); 129 hashcode.add(1, dst); 130 ColorConvertOp op = colorConvertOpBuf.get(hashcode); 131 132 if (op == null) { 133 if (src instanceof ICC_ColorSpace && dst instanceof ICC_ColorSpace) { 134 // ICC_Profile srcProfile = ((ICC_ColorSpace) src).getProfile(); 135 ICC_Profile dstProfile = ((ICC_ColorSpace) dst).getProfile(); 136 137 // srcProfile = ICC_Profile.getInstance(srcProfile.getData()); 138 dstProfile = ICC_Profile.getInstance(dstProfile.getData()); 139 140 /*if (getRenderingIntent(srcProfile) != renderingIntent.getValue()) { 141 srcProfile = ICC_Profile.getInstance(srcProfile.getData()); 142 setRenderingIntent(srcProfile, renderingIntent.getValue()); 143 src = new ICC_ColorSpace(srcProfile); 144 }*/ 145 146 if (renderingIntent != LCColorConvertDescriptor.DEFAULT 147 && getRenderingIntent(dstProfile) != renderingIntent.getValue()) 148 { 149 dstProfile = ICC_Profile.getInstance(dstProfile.getData()); 150 setRenderingIntent(dstProfile, renderingIntent.getValue()); 151 dst = new ICC_ColorSpace(dstProfile); 152 } 153 } 154 155 op = new ColorConvertOp(src, dst, null); 156 colorConvertOpBuf.put(hashcode, op); 157 } 158 159 return op; 160 } 161 162 /** 163 * Retrieve the minimum value of a data type. 164 * 165 * @param dataType The data type as in DataBuffer.TYPE_*. 166 * @return The minimum value of the specified data type. 167 */ getMinValue(int dataType)168 private static float getMinValue(int dataType) { 169 final float minValue; 170 switch (dataType) { 171 case DataBuffer.TYPE_BYTE: 172 minValue = 0; 173 break; 174 case DataBuffer.TYPE_SHORT: 175 minValue = Short.MIN_VALUE; 176 break; 177 case DataBuffer.TYPE_USHORT: 178 minValue = 0; 179 break; 180 case DataBuffer.TYPE_INT: 181 minValue = Integer.MIN_VALUE; 182 break; 183 default: 184 minValue = 0; 185 } 186 187 return minValue; 188 } 189 190 /** 191 * Retrieve the range of a data type. 192 * 193 * @param dataType The data type as in DataBuffer.TYPE_*. 194 * @return The range of the specified data type. 195 */ getRange(int dataType)196 private static float getRange(int dataType) { 197 final float range; 198 switch (dataType) { 199 case DataBuffer.TYPE_BYTE: 200 range = 255; 201 break; 202 case DataBuffer.TYPE_SHORT: 203 range = Short.MAX_VALUE - (int) Short.MIN_VALUE; 204 break; 205 case DataBuffer.TYPE_USHORT: 206 range = Short.MAX_VALUE - (int)Short.MIN_VALUE; 207 break; 208 case DataBuffer.TYPE_INT: 209 range = Integer.MAX_VALUE - (long) Integer.MIN_VALUE; 210 break; 211 default: 212 range = 1; 213 } 214 215 return range; 216 } 217 218 /** 219 * Constructor. 220 * 221 * @param source The source image. 222 * @param config Configurable attributes of the image including 223 * configuration variables indexed by 224 * <code>RenderingHints.Key</code>s and image properties indexed 225 * by <code>String</code>s or <code>CaselessStringKey</code>s. 226 * This is simply forwarded to the superclass constructor. 227 * @param layout The destination image layout. 228 * @param colorModel The destination color model. 229 */ LCColorConvertOpImage(RenderedImage source, Map config, ImageLayout layout, ColorModel colorModel, LCColorConvertDescriptor.RenderingIntent renderingIntent)230 public LCColorConvertOpImage(RenderedImage source, 231 Map config, 232 ImageLayout layout, 233 ColorModel colorModel, 234 LCColorConvertDescriptor.RenderingIntent renderingIntent) { 235 super(source, layout, config, true); 236 this.colorModel = colorModel; 237 238 // Cache the ColorModels. 239 srcParam = new ImageParameters(source.getColorModel(), source.getSampleModel()); 240 dstParam = new ImageParameters(colorModel, sampleModel); 241 242 ColorSpace srcColorSpace = srcParam.getColorModel().getColorSpace(); 243 ColorSpace dstColorSpace = dstParam.getColorModel().getColorSpace(); 244 245 // for each case, define the case number; create tempParam 246 // and/or ColorConvertOp if necessary 247 if (srcColorSpace instanceof ColorSpaceJAI && 248 dstColorSpace instanceof ColorSpaceJAI) { 249 250 // when both are ColorSpaceJAI, convert via RGB 251 caseNumber = 1; 252 tempParam = createTempParam(); 253 } else if (srcColorSpace instanceof ColorSpaceJAI) { 254 255 // when source is ColorSpaceJAI, 1. convert via RGB if 256 // the dest isn't RGB; 2. convert to RGB 257 if (dstColorSpace != rgbColorSpace) { 258 caseNumber = 2; 259 tempParam = createTempParam(); 260 colorConvertOp = getColorConvertOp(rgbColorSpace, dstColorSpace, renderingIntent); 261 } else { 262 caseNumber = 3; 263 } 264 } else if (dstColorSpace instanceof ColorSpaceJAI) { 265 266 // when destination is ColorSpaceJAI, 1. convert via RGB if 267 // source isn't RGB; 2. convert from RGB 268 if (srcColorSpace != rgbColorSpace) { 269 caseNumber = 4; 270 tempParam = createTempParam(); 271 colorConvertOp = getColorConvertOp(srcColorSpace, rgbColorSpace, renderingIntent); 272 } else { 273 caseNumber = 5; 274 } 275 } else { 276 // if all the color space are not ColorSpaceJAI 277 caseNumber = 6; 278 colorConvertOp = getColorConvertOp(srcColorSpace, dstColorSpace, renderingIntent); 279 } 280 281 // Set flag to permit in-place operation. 282 permitInPlaceOperation(); 283 } 284 285 /** 286 * Computes a tile of the destination image in the destination color space. 287 * 288 * @param sources Cobbled sources, guaranteed to provide all the 289 * source data necessary for computing the rectangle. 290 * @param dest The tile containing the rectangle to be computed. 291 * @param destRect The rectangle within the tile to be computed. 292 */ computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect)293 protected void computeRect(Raster[] sources, 294 WritableRaster dest, 295 Rectangle destRect) { 296 WritableRaster tempRas; 297 298 switch (caseNumber) { 299 // 1. When source and destination color spaces are all ColorSpaceJAI, 300 // convert via RGB color space 301 case 1: 302 tempRas = computeRectColorSpaceJAIToRGB(sources[0], srcParam, 303 null, tempParam); 304 computeRectColorSpaceJAIFromRGB(tempRas, tempParam, 305 dest, dstParam); 306 break; 307 // when only the source color space is ColorSpaceJAI, 308 // 2. if the destination is not RGB, convert to RGB using 309 // ColorSpaceJAI; then convert RGB to the destination 310 // 3. if the destination is RGB, convert using ColorSpaceJAI 311 case 2: 312 tempRas = computeRectColorSpaceJAIToRGB(sources[0], srcParam, 313 null, tempParam); 314 computeRectNonColorSpaceJAI(tempRas, tempParam, 315 dest, dstParam, destRect); 316 break; 317 case 3: 318 computeRectColorSpaceJAIToRGB(sources[0], srcParam, 319 dest, dstParam); 320 break; 321 // 4, 5. When only the destination color space is ColorSpaceJAI, 322 // similar to the case above. 323 case 4: 324 tempRas =createTempWritableRaster(sources[0]); 325 computeRectNonColorSpaceJAI(sources[0], srcParam, 326 tempRas, tempParam, destRect); 327 computeRectColorSpaceJAIFromRGB(tempRas, tempParam, 328 dest, dstParam); 329 break; 330 case 5: 331 computeRectColorSpaceJAIFromRGB(sources[0], srcParam, 332 dest, dstParam); 333 break; 334 // 6. If all the color space are not ColorSpaceJAI 335 case 6: 336 computeRectNonColorSpaceJAI(sources[0], srcParam, 337 dest, dstParam, destRect); 338 default : 339 break; 340 } 341 } 342 343 // when the source color space is ColorSpaceJAI, convert it to RGB. 344 // 1. If the source data type is short/int, shift the data to [0, 345 // MAX-MIN] 346 // 2. Convert to RGB. 347 // 3. Shift back to [MIN, MAX] computeRectColorSpaceJAIToRGB(Raster src, ImageParameters srcParam, WritableRaster dest, ImageParameters dstParam)348 private WritableRaster computeRectColorSpaceJAIToRGB(Raster src, 349 ImageParameters srcParam, 350 WritableRaster dest, 351 ImageParameters dstParam) { 352 src = convertRasterToUnsigned(src); 353 354 ColorSpaceJAI colorSpaceJAI 355 = (ColorSpaceJAI) srcParam.getColorModel().getColorSpace(); 356 dest = colorSpaceJAI.toRGB(src, srcParam.getComponentSize(), dest, 357 dstParam.getComponentSize()); 358 359 dest = convertRasterToSigned(dest); 360 return dest; 361 } 362 363 // when the source color space is ColorSpaceJAI, convert it from RGB. 364 // 1. If the source data type is short/int, shift the data to [0, 365 // MAX-MIN] 366 // 2. Convert from RGB. 367 // 3. Shift back to [MIN, MAX] computeRectColorSpaceJAIFromRGB(Raster src, ImageParameters srcParam, WritableRaster dest, ImageParameters dstParam)368 private WritableRaster computeRectColorSpaceJAIFromRGB(Raster src, 369 ImageParameters srcParam, 370 WritableRaster dest, 371 ImageParameters dstParam){ 372 src = convertRasterToUnsigned(src); 373 ColorSpaceJAI colorSpaceJAI 374 = (ColorSpaceJAI) dstParam.getColorModel().getColorSpace(); 375 dest = colorSpaceJAI.fromRGB(src, srcParam.getComponentSize(), dest, 376 dstParam.getComponentSize()); 377 378 dest = convertRasterToSigned(dest); 379 return dest; 380 } 381 382 // When the source and destination color spaces are not ColorSpaceJAI, 383 // convert using ColorConvertOp of Java 2D for integer type. For the 384 // floating point, use the following method. computeRectNonColorSpaceJAI(Raster src, ImageParameters srcParam, WritableRaster dest, ImageParameters dstParam, Rectangle destRect)385 private void computeRectNonColorSpaceJAI(Raster src, 386 ImageParameters srcParam, 387 WritableRaster dest, 388 ImageParameters dstParam, 389 Rectangle destRect) { 390 if (!srcParam.isFloat() && !dstParam.isFloat()) { 391 // Create a ColorConvertOp if there are only integral data. 392 // Integral type: use the ColorConvertOp. 393 394 // Ensure that the Rasters are the same size as apparently 395 // required by ColorConvertOp although not so documented. 396 Raster s = src; 397 if (s.getMinX() != destRect.x || 398 s.getMinY() != destRect.y || 399 s.getWidth() != destRect.width || 400 s.getHeight() != destRect.height) { 401 s = s.createChild(destRect.x, destRect.y, 402 destRect.width, destRect.height, 403 destRect.x, destRect.y, null); 404 } 405 WritableRaster d = dest; 406 if (d.getMinX() != destRect.x || 407 d.getMinY() != destRect.y || 408 d.getWidth() != destRect.width || 409 d.getHeight() != destRect.height) { 410 d = d.createWritableChild(destRect.x, destRect.y, 411 destRect.width, destRect.height, 412 destRect.x, destRect.y, null); 413 } 414 415 // Perform the color conversion on the (possible child) Rasters. 416 synchronized (ColorSpace.class) { 417 colorConvertOp.filter(s, d); 418 } 419 } else { 420 //For the floating point data types, convert via CIEXYZ color space. 421 //Do it pixel-by-pixel (slow!). 422 ColorSpace srcColorSpace = srcParam.getColorModel().getColorSpace(); 423 ColorSpace dstColorSpace = dstParam.getColorModel().getColorSpace(); 424 boolean srcFloat = srcParam.isFloat(); 425 float srcMinValue = srcParam.getMinValue(); 426 float srcRange = srcParam.getRange(); 427 428 boolean dstFloat = dstParam.isFloat(); 429 float dstMinValue = dstParam.getMinValue(); 430 float dstRange = dstParam.getRange(); 431 432 int rectYMax = destRect.y + destRect.height; 433 int rectXMax = destRect.x + destRect.width; 434 int numComponents = srcColorSpace.getNumComponents(); 435 float[] srcPixel = new float[numComponents]; 436 float[] xyzPixel; 437 float[] dstPixel; 438 for (int y = destRect.y; y < rectYMax; y++) { 439 for (int x = destRect.x; x < rectXMax; x++) { 440 srcPixel = src.getPixel(x, y, srcPixel); 441 if (!srcFloat) { 442 // Normalize the source samples. 443 for (int i = 0; i < numComponents; i++) { 444 srcPixel[i] = (srcPixel[i] - srcMinValue)/srcRange; 445 } 446 } 447 448 // Convert src to dst via CIEXYZ. 449 synchronized (ColorSpace.class) { 450 xyzPixel = srcColorSpace.toCIEXYZ(srcPixel); 451 dstPixel = dstColorSpace.fromCIEXYZ(xyzPixel); 452 } 453 454 if (!dstFloat) { 455 // Scale the destination samples. 456 for (int i = 0; i < numComponents; i++) { 457 dstPixel[i] = (dstPixel[i]*dstRange + dstMinValue); 458 } 459 } 460 dest.setPixel(x, y, dstPixel); 461 } 462 } 463 } 464 } 465 466 // Back up the destination parameters. Set the destination to the 467 // bridge color space RGB. createTempParam()468 private ImageParameters createTempParam() { 469 ColorModel cm; 470 SampleModel sm; 471 472 if (srcParam.getDataType() > dstParam.getDataType()) { 473 cm = srcParam.getColorModel(); 474 sm = srcParam.getSampleModel(); 475 } else { 476 cm = dstParam.getColorModel(); 477 sm = dstParam.getSampleModel(); 478 } 479 480 cm = new ComponentColorModel(rgbColorSpace, 481 cm.getComponentSize(), 482 cm.hasAlpha() , 483 cm.isAlphaPremultiplied(), 484 cm.getTransparency(), 485 sm.getDataType()); 486 return new ImageParameters(cm, sm); 487 } 488 489 // Create an WritableRaster with the same SampleModel and location 490 // as the passed Raster parameter. createTempWritableRaster(Raster src)491 private WritableRaster createTempWritableRaster(Raster src) { 492 Point origin = new Point(src.getMinX(), src.getMinY()); 493 return RasterFactory.createWritableRaster(src.getSampleModel(), 494 origin); 495 } 496 497 // Shift the sample value to [0, MAX-MIN] convertRasterToUnsigned(Raster ras)498 private Raster convertRasterToUnsigned(Raster ras) { 499 int type = ras.getSampleModel().getDataType(); 500 501 if ((type == DataBuffer.TYPE_INT 502 || type == DataBuffer.TYPE_SHORT)) { 503 int minX = ras.getMinX(), minY = ras.getMinY(); 504 int w = ras.getWidth() , h = ras.getHeight(); 505 506 int[] buf = ras.getPixels(minX, minY, w, h, (int[])null); 507 convertBufferToUnsigned(buf, type); 508 509 WritableRaster tempRas = createTempWritableRaster(ras); 510 tempRas.setPixels(minX, minY, w, h, buf); 511 return tempRas; 512 } 513 return ras; 514 } 515 516 // Shift the sample value back to [MIN, MAX] convertRasterToSigned(WritableRaster ras)517 private WritableRaster convertRasterToSigned(WritableRaster ras) { 518 int type = ras.getSampleModel().getDataType(); 519 520 if ((type == DataBuffer.TYPE_INT 521 || type == DataBuffer.TYPE_SHORT)) { 522 int minX = ras.getMinX(), minY = ras.getMinY(); 523 int w = ras.getWidth() , h = ras.getHeight(); 524 525 int[] buf = ras.getPixels(minX, minY, w, h, (int[])null); 526 convertBufferToSigned(buf, type); 527 528 ras.setPixels(minX, minY, w, h, buf); 529 } 530 return ras; 531 } 532 533 // Shift the value to [MIN, MAX] convertBufferToSigned(int[] buf, int type)534 private void convertBufferToSigned(int[] buf, int type) { 535 if (buf == null) return; 536 537 if (type == DataBuffer.TYPE_SHORT) 538 for (int i=0; i < buf.length; i++) { 539 buf[i] += Short.MIN_VALUE; 540 } 541 else if (type == DataBuffer.TYPE_INT) { 542 for (int i=0; i < buf.length; i++) { 543 buf[i] = (int) ((buf[i] & 0xFFFFFFFFL) + Integer.MIN_VALUE); 544 } 545 } 546 } 547 548 // Shift the value to [0, MAX-MIN] convertBufferToUnsigned(int[] buf, int type)549 private void convertBufferToUnsigned(int[] buf, int type) { 550 if (buf == null) return; 551 552 if (type == DataBuffer.TYPE_SHORT) 553 for (int i = 0; i < buf.length; i++) { 554 buf[i] -= Short.MIN_VALUE; 555 } 556 else if (type == DataBuffer.TYPE_INT) { 557 for (int i = 0; i < buf.length; i++) { 558 buf[i] = (int) ((buf[i] & 0xFFFFFFFFL) - Integer.MIN_VALUE); 559 } 560 } 561 } 562 563 564 // define a class to cache the parameters 565 private final class ImageParameters { 566 private boolean isFloat; 567 private ColorModel colorModel; 568 private SampleModel sampleModel; 569 private float minValue; 570 private float range; 571 private int[] componentSize; 572 private int dataType; 573 ImageParameters(ColorModel cm, SampleModel sm)574 ImageParameters(ColorModel cm, SampleModel sm) { 575 this.colorModel = cm; 576 this.sampleModel = sm; 577 this.dataType = sm.getDataType(); 578 this.isFloat = this.dataType == DataBuffer.TYPE_FLOAT 579 || this.dataType == DataBuffer.TYPE_DOUBLE; 580 this.minValue = LCColorConvertOpImage.getMinValue(this.dataType); 581 this.range = LCColorConvertOpImage.getRange(this.dataType); 582 this.componentSize = cm.getComponentSize(); 583 } 584 isFloat()585 public boolean isFloat() { 586 return isFloat; 587 } 588 getColorModel()589 public ColorModel getColorModel() { 590 return colorModel; 591 } 592 getSampleModel()593 public SampleModel getSampleModel() { 594 return sampleModel; 595 } 596 getMinValue()597 public float getMinValue() { 598 return minValue; 599 } 600 getRange()601 public float getRange() { 602 return range; 603 } 604 getComponentSize()605 public int[] getComponentSize() { 606 return componentSize; 607 } 608 getDataType()609 public int getDataType() { 610 return dataType; 611 } 612 } 613 } 614 615