1 2 /* 3 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 28 package java.awt.image; 29 30 import java.awt.color.ColorSpace; 31 import java.awt.geom.Rectangle2D; 32 import java.awt.Rectangle; 33 import java.awt.RenderingHints; 34 import java.awt.geom.Point2D; 35 import sun.awt.image.ImagingLib; 36 37 /** 38 * This class implements a lookup operation from the source 39 * to the destination. The LookupTable object may contain a single array 40 * or multiple arrays, subject to the restrictions below. 41 * <p> 42 * For Rasters, the lookup operates on bands. The number of 43 * lookup arrays may be one, in which case the same array is 44 * applied to all bands, or it must equal the number of Source 45 * Raster bands. 46 * <p> 47 * For BufferedImages, the lookup operates on color and alpha components. 48 * The number of lookup arrays may be one, in which case the 49 * same array is applied to all color (but not alpha) components. 50 * Otherwise, the number of lookup arrays may 51 * equal the number of Source color components, in which case no 52 * lookup of the alpha component (if present) is performed. 53 * If neither of these cases apply, the number of lookup arrays 54 * must equal the number of Source color components plus alpha components, 55 * in which case lookup is performed for all color and alpha components. 56 * This allows non-uniform rescaling of multi-band BufferedImages. 57 * <p> 58 * BufferedImage sources with premultiplied alpha data are treated in the same 59 * manner as non-premultiplied images for purposes of the lookup. That is, 60 * the lookup is done per band on the raw data of the BufferedImage source 61 * without regard to whether the data is premultiplied. If a color conversion 62 * is required to the destination ColorModel, the premultiplied state of 63 * both source and destination will be taken into account for this step. 64 * <p> 65 * Images with an IndexColorModel cannot be used. 66 * <p> 67 * If a RenderingHints object is specified in the constructor, the 68 * color rendering hint and the dithering hint may be used when color 69 * conversion is required. 70 * <p> 71 * This class allows the Source to be the same as the Destination. 72 * 73 * @see LookupTable 74 * @see java.awt.RenderingHints#KEY_COLOR_RENDERING 75 * @see java.awt.RenderingHints#KEY_DITHERING 76 */ 77 78 public class LookupOp implements BufferedImageOp, RasterOp { 79 private LookupTable ltable; 80 private int numComponents; 81 RenderingHints hints; 82 83 /** 84 * Constructs a <code>LookupOp</code> object given the lookup 85 * table and a <code>RenderingHints</code> object, which might 86 * be <code>null</code>. 87 * @param lookup the specified <code>LookupTable</code> 88 * @param hints the specified <code>RenderingHints</code>, 89 * or <code>null</code> 90 */ 91 public LookupOp(LookupTable lookup, RenderingHints hints) { 92 this.ltable = lookup; 93 this.hints = hints; 94 numComponents = ltable.getNumComponents(); 95 } 96 97 /** 98 * Returns the <code>LookupTable</code>. 99 * @return the <code>LookupTable</code> of this 100 * <code>LookupOp</code>. 101 */ 102 public final LookupTable getTable() { 103 return ltable; 104 } 105 106 /** 107 * Performs a lookup operation on a <code>BufferedImage</code>. 108 * If the color model in the source image is not the same as that 109 * in the destination image, the pixels will be converted 110 * in the destination. If the destination image is <code>null</code>, 111 * a <code>BufferedImage</code> will be created with an appropriate 112 * <code>ColorModel</code>. An <code>IllegalArgumentException</code> 113 * might be thrown if the number of arrays in the 114 * <code>LookupTable</code> does not meet the restrictions 115 * stated in the class comment above, or if the source image 116 * has an <code>IndexColorModel</code>. 117 * @param src the <code>BufferedImage</code> to be filtered 118 * @param dst the <code>BufferedImage</code> in which to 119 * store the results of the filter operation 120 * @return the filtered <code>BufferedImage</code>. 121 * @throws IllegalArgumentException if the number of arrays in the 122 * <code>LookupTable</code> does not meet the restrictions 123 * described in the class comments, or if the source image 124 * has an <code>IndexColorModel</code>. 125 */ 126 public final BufferedImage filter(BufferedImage src, BufferedImage dst) { 127 ColorModel srcCM = src.getColorModel(); 128 int numBands = srcCM.getNumColorComponents(); 129 ColorModel dstCM; 130 if (srcCM instanceof IndexColorModel) { 131 throw new 132 IllegalArgumentException("LookupOp cannot be "+ 133 "performed on an indexed image"); 134 } 135 int numComponents = ltable.getNumComponents(); 136 if (numComponents != 1 && 137 numComponents != srcCM.getNumComponents() && 138 numComponents != srcCM.getNumColorComponents()) 139 { 140 throw new IllegalArgumentException("Number of arrays in the "+ 141 " lookup table ("+ 142 numComponents+ 143 " is not compatible with the "+ 144 " src image: "+src); 145 } 146 147 148 boolean needToConvert = false; 149 150 int width = src.getWidth(); 151 int height = src.getHeight(); 152 153 if (dst == null) { 154 dst = createCompatibleDestImage(src, null); 155 dstCM = srcCM; 156 } 157 else { 158 if (width != dst.getWidth()) { 159 throw new 160 IllegalArgumentException("Src width ("+width+ 161 ") not equal to dst width ("+ 162 dst.getWidth()+")"); 163 } 164 if (height != dst.getHeight()) { 165 throw new 166 IllegalArgumentException("Src height ("+height+ 167 ") not equal to dst height ("+ 168 dst.getHeight()+")"); 169 } 170 171 dstCM = dst.getColorModel(); 172 if (srcCM.getColorSpace().getType() != 173 dstCM.getColorSpace().getType()) 174 { 175 needToConvert = true; 176 dst = createCompatibleDestImage(src, null); 177 } 178 179 } 180 181 BufferedImage origDst = dst; 182 183 if (ImagingLib.filter(this, src, dst) == null) { 184 // Do it the slow way 185 WritableRaster srcRaster = src.getRaster(); 186 WritableRaster dstRaster = dst.getRaster(); 187 188 if (srcCM.hasAlpha()) { 189 if (numBands-1 == numComponents || numComponents == 1) { 190 int minx = srcRaster.getMinX(); 191 int miny = srcRaster.getMinY(); 192 int[] bands = new int[numBands-1]; 193 for (int i=0; i < numBands-1; i++) { 194 bands[i] = i; 195 } 196 srcRaster = 197 srcRaster.createWritableChild(minx, miny, 198 srcRaster.getWidth(), 199 srcRaster.getHeight(), 200 minx, miny, 201 bands); 202 } 203 } 204 if (dstCM.hasAlpha()) { 205 int dstNumBands = dstRaster.getNumBands(); 206 if (dstNumBands-1 == numComponents || numComponents == 1) { 207 int minx = dstRaster.getMinX(); 208 int miny = dstRaster.getMinY(); 209 int[] bands = new int[numBands-1]; 210 for (int i=0; i < numBands-1; i++) { 211 bands[i] = i; 212 } 213 dstRaster = 214 dstRaster.createWritableChild(minx, miny, 215 dstRaster.getWidth(), 216 dstRaster.getHeight(), 217 minx, miny, 218 bands); 219 } 220 } 221 222 filter(srcRaster, dstRaster); 223 } 224 225 if (needToConvert) { 226 // ColorModels are not the same 227 ColorConvertOp ccop = new ColorConvertOp(hints); 228 ccop.filter(dst, origDst); 229 } 230 231 return origDst; 232 } 233 234 /** 235 * Performs a lookup operation on a <code>Raster</code>. 236 * If the destination <code>Raster</code> is <code>null</code>, 237 * a new <code>Raster</code> will be created. 238 * The <code>IllegalArgumentException</code> might be thrown 239 * if the source <code>Raster</code> and the destination 240 * <code>Raster</code> do not have the same 241 * number of bands or if the number of arrays in the 242 * <code>LookupTable</code> does not meet the 243 * restrictions stated in the class comment above. 244 * @param src the source <code>Raster</code> to filter 245 * @param dst the destination <code>WritableRaster</code> for the 246 * filtered <code>src</code> 247 * @return the filtered <code>WritableRaster</code>. 248 * @throws IllegalArgumentException if the source and destinations 249 * rasters do not have the same number of bands, or the 250 * number of arrays in the <code>LookupTable</code> does 251 * not meet the restrictions described in the class comments. 252 * 253 */ 254 public final WritableRaster filter (Raster src, WritableRaster dst) { 255 int numBands = src.getNumBands(); 256 int dstLength = dst.getNumBands(); 257 int height = src.getHeight(); 258 int width = src.getWidth(); 259 int srcPix[] = new int[numBands]; 260 261 // Create a new destination Raster, if needed 262 263 if (dst == null) { 264 dst = createCompatibleDestRaster(src); 265 } 266 else if (height != dst.getHeight() || width != dst.getWidth()) { 267 throw new 268 IllegalArgumentException ("Width or height of Rasters do not "+ 269 "match"); 270 } 271 dstLength = dst.getNumBands(); 272 273 if (numBands != dstLength) { 274 throw new 275 IllegalArgumentException ("Number of channels in the src (" 276 + numBands + 277 ") does not match number of channels" 278 + " in the destination (" 279 + dstLength + ")"); 280 } 281 int numComponents = ltable.getNumComponents(); 282 if (numComponents != 1 && numComponents != src.getNumBands()) { 283 throw new IllegalArgumentException("Number of arrays in the "+ 284 " lookup table ("+ 285 numComponents+ 286 " is not compatible with the "+ 287 " src Raster: "+src); 288 } 289 290 291 if (ImagingLib.filter(this, src, dst) != null) { 292 return dst; 293 } 294 295 // Optimize for cases we know about 296 if (ltable instanceof ByteLookupTable) { 297 byteFilter ((ByteLookupTable) ltable, src, dst, 298 width, height, numBands); 299 } 300 else if (ltable instanceof ShortLookupTable) { 301 shortFilter ((ShortLookupTable) ltable, src, dst, width, 302 height, numBands); 303 } 304 else { 305 // Not one we recognize so do it slowly 306 int sminX = src.getMinX(); 307 int sY = src.getMinY(); 308 int dminX = dst.getMinX(); 309 int dY = dst.getMinY(); 310 for (int y=0; y < height; y++, sY++, dY++) { 311 int sX = sminX; 312 int dX = dminX; 313 for (int x=0; x < width; x++, sX++, dX++) { 314 // Find data for all bands at this x,y position 315 src.getPixel(sX, sY, srcPix); 316 317 // Lookup the data for all bands at this x,y position 318 ltable.lookupPixel(srcPix, srcPix); 319 320 // Put it back for all bands 321 dst.setPixel(dX, dY, srcPix); 322 } 323 } 324 } 325 326 return dst; 327 } 328 329 /** 330 * Returns the bounding box of the filtered destination image. Since 331 * this is not a geometric operation, the bounding box does not 332 * change. 333 * @param src the <code>BufferedImage</code> to be filtered 334 * @return the bounds of the filtered definition image. 335 */ 336 public final Rectangle2D getBounds2D (BufferedImage src) { 337 return getBounds2D(src.getRaster()); 338 } 339 340 /** 341 * Returns the bounding box of the filtered destination Raster. Since 342 * this is not a geometric operation, the bounding box does not 343 * change. 344 * @param src the <code>Raster</code> to be filtered 345 * @return the bounds of the filtered definition <code>Raster</code>. 346 */ 347 public final Rectangle2D getBounds2D (Raster src) { 348 return src.getBounds(); 349 350 } 351 352 /** 353 * Creates a zeroed destination image with the correct size and number of 354 * bands. If destCM is <code>null</code>, an appropriate 355 * <code>ColorModel</code> will be used. 356 * @param src Source image for the filter operation. 357 * @param destCM the destination's <code>ColorModel</code>, which 358 * can be <code>null</code>. 359 * @return a filtered destination <code>BufferedImage</code>. 360 */ 361 public BufferedImage createCompatibleDestImage (BufferedImage src, 362 ColorModel destCM) { 363 BufferedImage image; 364 int w = src.getWidth(); 365 int h = src.getHeight(); 366 int transferType = DataBuffer.TYPE_BYTE; 367 if (destCM == null) { 368 ColorModel cm = src.getColorModel(); 369 Raster raster = src.getRaster(); 370 if (cm instanceof ComponentColorModel) { 371 DataBuffer db = raster.getDataBuffer(); 372 boolean hasAlpha = cm.hasAlpha(); 373 boolean isPre = cm.isAlphaPremultiplied(); 374 int trans = cm.getTransparency(); 375 int[] nbits = null; 376 if (ltable instanceof ByteLookupTable) { 377 if (db.getDataType() == DataBuffer.TYPE_USHORT) { 378 // Dst raster should be of type byte 379 if (hasAlpha) { 380 nbits = new int[2]; 381 if (trans == java.awt.Transparency.BITMASK) { 382 nbits[1] = 1; 383 } 384 else { 385 nbits[1] = 8; 386 } 387 } 388 else { 389 nbits = new int[1]; 390 } 391 nbits[0] = 8; 392 } 393 // For byte, no need to change the cm 394 } 395 else if (ltable instanceof ShortLookupTable) { 396 transferType = DataBuffer.TYPE_USHORT; 397 if (db.getDataType() == DataBuffer.TYPE_BYTE) { 398 if (hasAlpha) { 399 nbits = new int[2]; 400 if (trans == java.awt.Transparency.BITMASK) { 401 nbits[1] = 1; 402 } 403 else { 404 nbits[1] = 16; 405 } 406 } 407 else { 408 nbits = new int[1]; 409 } 410 nbits[0] = 16; 411 } 412 } 413 if (nbits != null) { 414 cm = new ComponentColorModel(cm.getColorSpace(), 415 nbits, hasAlpha, isPre, 416 trans, transferType); 417 } 418 } 419 image = new BufferedImage(cm, 420 cm.createCompatibleWritableRaster(w, h), 421 cm.isAlphaPremultiplied(), 422 null); 423 } 424 else { 425 image = new BufferedImage(destCM, 426 destCM.createCompatibleWritableRaster(w, 427 h), 428 destCM.isAlphaPremultiplied(), 429 null); 430 } 431 432 return image; 433 } 434 435 /** 436 * Creates a zeroed-destination <code>Raster</code> with the 437 * correct size and number of bands, given this source. 438 * @param src the <code>Raster</code> to be transformed 439 * @return the zeroed-destination <code>Raster</code>. 440 */ 441 public WritableRaster createCompatibleDestRaster (Raster src) { 442 return src.createCompatibleWritableRaster(); 443 } 444 445 /** 446 * Returns the location of the destination point given a 447 * point in the source. If <code>dstPt</code> is not 448 * <code>null</code>, it will be used to hold the return value. 449 * Since this is not a geometric operation, the <code>srcPt</code> 450 * will equal the <code>dstPt</code>. 451 * @param srcPt a <code>Point2D</code> that represents a point 452 * in the source image 453 * @param dstPt a <code>Point2D</code>that represents the location 454 * in the destination 455 * @return the <code>Point2D</code> in the destination that 456 * corresponds to the specified point in the source. 457 */ 458 public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) { 459 if (dstPt == null) { 460 dstPt = new Point2D.Float(); 461 } 462 dstPt.setLocation(srcPt.getX(), srcPt.getY()); 463 464 return dstPt; 465 } 466 467 /** 468 * Returns the rendering hints for this op. 469 * @return the <code>RenderingHints</code> object associated 470 * with this op. 471 */ 472 public final RenderingHints getRenderingHints() { 473 return hints; 474 } 475 476 private final void byteFilter(ByteLookupTable lookup, Raster src, 477 WritableRaster dst, 478 int width, int height, int numBands) { 479 int[] srcPix = null; 480 481 // Find the ref to the table and the offset 482 byte[][] table = lookup.getTable(); 483 int offset = lookup.getOffset(); 484 int tidx; 485 int step=1; 486 487 // Check if it is one lookup applied to all bands 488 if (table.length == 1) { 489 step=0; 490 } 491 492 int x; 493 int y; 494 int band; 495 int len = table[0].length; 496 497 // Loop through the data 498 for ( y=0; y < height; y++) { 499 tidx = 0; 500 for ( band=0; band < numBands; band++, tidx+=step) { 501 // Find data for this band, scanline 502 srcPix = src.getSamples(0, y, width, 1, band, srcPix); 503 504 for ( x=0; x < width; x++) { 505 int index = srcPix[x]-offset; 506 if (index < 0 || index > len) { 507 throw new 508 IllegalArgumentException("index ("+index+ 509 "(out of range: "+ 510 " srcPix["+x+ 511 "]="+ srcPix[x]+ 512 " offset="+ offset); 513 } 514 // Do the lookup 515 srcPix[x] = table[tidx][index]; 516 } 517 // Put it back 518 dst.setSamples(0, y, width, 1, band, srcPix); 519 } 520 } 521 } 522 523 private final void shortFilter(ShortLookupTable lookup, Raster src, 524 WritableRaster dst, 525 int width, int height, int numBands) { 526 int band; 527 int[] srcPix = null; 528 529 // Find the ref to the table and the offset 530 short[][] table = lookup.getTable(); 531 int offset = lookup.getOffset(); 532 int tidx; 533 int step=1; 534 535 // Check if it is one lookup applied to all bands 536 if (table.length == 1) { 537 step=0; 538 } 539 540 int x = 0; 541 int y = 0; 542 int index; 543 int maxShort = (1<<16)-1; 544 // Loop through the data 545 for (y=0; y < height; y++) { 546 tidx = 0; 547 for ( band=0; band < numBands; band++, tidx+=step) { 548 // Find data for this band, scanline 549 srcPix = src.getSamples(0, y, width, 1, band, srcPix); 550 551 for ( x=0; x < width; x++) { 552 index = srcPix[x]-offset; 553 if (index < 0 || index > maxShort) { 554 throw new 555 IllegalArgumentException("index out of range "+ 556 index+" x is "+x+ 557 "srcPix[x]="+srcPix[x] 558 +" offset="+ offset); 559 } 560 // Do the lookup 561 srcPix[x] = table[tidx][index]; 562 } 563 // Put it back 564 dst.setSamples(0, y, width, 1, band, srcPix); 565 } 566 } 567 } 568 }