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