1 /* 2 * Copyright (c) 1997, 2014, 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 package java.awt.image; 27 28 import java.awt.color.ColorSpace; 29 import java.awt.geom.Rectangle2D; 30 import java.awt.Graphics2D; 31 import java.awt.Rectangle; 32 import java.awt.geom.Point2D; 33 import java.awt.RenderingHints; 34 import sun.awt.image.ImagingLib; 35 36 /** 37 * This class performs a pixel-by-pixel rescaling of the data in the 38 * source image by multiplying the sample values for each pixel by a scale 39 * factor and then adding an offset. The scaled sample values are clipped 40 * to the minimum/maximum representable in the destination image. 41 * <p> 42 * The pseudo code for the rescaling operation is as follows: 43 * <pre> 44 *for each pixel from Source object { 45 * for each band/component of the pixel { 46 * dstElement = (srcElement*scaleFactor) + offset 47 * } 48 *} 49 * </pre> 50 * <p> 51 * For Rasters, rescaling operates on bands. The number of 52 * sets of scaling constants may be one, in which case the same constants 53 * are applied to all bands, or it must equal the number of Source 54 * Raster bands. 55 * <p> 56 * For BufferedImages, rescaling operates on color and alpha components. 57 * The number of sets of scaling constants may be one, in which case the 58 * same constants are applied to all color (but not alpha) components. 59 * Otherwise, the number of sets of scaling constants may 60 * equal the number of Source color components, in which case no 61 * rescaling of the alpha component (if present) is performed. 62 * If neither of these cases apply, the number of sets of scaling constants 63 * must equal the number of Source color components plus alpha components, 64 * in which case all color and alpha components are rescaled. 65 * <p> 66 * BufferedImage sources with premultiplied alpha data are treated in the same 67 * manner as non-premultiplied images for purposes of rescaling. That is, 68 * the rescaling is done per band on the raw data of the BufferedImage source 69 * without regard to whether the data is premultiplied. If a color conversion 70 * is required to the destination ColorModel, the premultiplied state of 71 * both source and destination will be taken into account for this step. 72 * <p> 73 * Images with an IndexColorModel cannot be rescaled. 74 * <p> 75 * If a RenderingHints object is specified in the constructor, the 76 * color rendering hint and the dithering hint may be used when color 77 * conversion is required. 78 * <p> 79 * Note that in-place operation is allowed (i.e. the source and destination can 80 * be the same object). 81 * @see java.awt.RenderingHints#KEY_COLOR_RENDERING 82 * @see java.awt.RenderingHints#KEY_DITHERING 83 */ 84 public class RescaleOp implements BufferedImageOp, RasterOp { 85 float[] scaleFactors; 86 float[] offsets; 87 int length = 0; 88 RenderingHints hints; 89 90 private int srcNbits; 91 private int dstNbits; 92 93 94 /** 95 * Constructs a new RescaleOp with the desired scale factors 96 * and offsets. The length of the scaleFactor and offset arrays 97 * must meet the restrictions stated in the class comments above. 98 * The RenderingHints argument may be null. 99 * @param scaleFactors the specified scale factors 100 * @param offsets the specified offsets 101 * @param hints the specified {@code RenderingHints}, or 102 * {@code null} 103 */ 104 public RescaleOp (float[] scaleFactors, float[] offsets, 105 RenderingHints hints) { 106 length = scaleFactors.length; 107 if (length > offsets.length) length = offsets.length; 108 109 this.scaleFactors = new float[length]; 110 this.offsets = new float[length]; 111 for (int i=0; i < length; i++) { 112 this.scaleFactors[i] = scaleFactors[i]; 113 this.offsets[i] = offsets[i]; 114 } 115 this.hints = hints; 116 } 117 118 /** 119 * Constructs a new RescaleOp with the desired scale factor 120 * and offset. The scaleFactor and offset will be applied to 121 * all bands in a source Raster and to all color (but not alpha) 122 * components in a BufferedImage. 123 * The RenderingHints argument may be null. 124 * @param scaleFactor the specified scale factor 125 * @param offset the specified offset 126 * @param hints the specified {@code RenderingHints}, or 127 * {@code null} 128 */ 129 public RescaleOp (float scaleFactor, float offset, RenderingHints hints) { 130 length = 1; 131 this.scaleFactors = new float[1]; 132 this.offsets = new float[1]; 133 this.scaleFactors[0] = scaleFactor; 134 this.offsets[0] = offset; 135 this.hints = hints; 136 } 137 138 /** 139 * Returns the scale factors in the given array. The array is also 140 * returned for convenience. If scaleFactors is null, a new array 141 * will be allocated. 142 * @param scaleFactors the array to contain the scale factors of 143 * this {@code RescaleOp} 144 * @return the scale factors of this {@code RescaleOp}. 145 */ 146 public final float[] getScaleFactors (float scaleFactors[]) { 147 if (scaleFactors == null) { 148 return this.scaleFactors.clone(); 149 } 150 System.arraycopy (this.scaleFactors, 0, scaleFactors, 0, 151 Math.min(this.scaleFactors.length, 152 scaleFactors.length)); 153 return scaleFactors; 154 } 155 156 /** 157 * Returns the offsets in the given array. The array is also returned 158 * for convenience. If offsets is null, a new array 159 * will be allocated. 160 * @param offsets the array to contain the offsets of 161 * this {@code RescaleOp} 162 * @return the offsets of this {@code RescaleOp}. 163 */ 164 public final float[] getOffsets(float offsets[]) { 165 if (offsets == null) { 166 return this.offsets.clone(); 167 } 168 169 System.arraycopy (this.offsets, 0, offsets, 0, 170 Math.min(this.offsets.length, offsets.length)); 171 return offsets; 172 } 173 174 /** 175 * Returns the number of scaling factors and offsets used in this 176 * RescaleOp. 177 * @return the number of scaling factors and offsets of this 178 * {@code RescaleOp}. 179 */ 180 public final int getNumFactors() { 181 return length; 182 } 183 184 185 /** 186 * Creates a ByteLookupTable to implement the rescale. 187 * The table may have either a SHORT or BYTE input. 188 * @param nElems Number of elements the table is to have. 189 * This will generally be 256 for byte and 190 * 65536 for short. 191 */ 192 private ByteLookupTable createByteLut(float scale[], 193 float off[], 194 int nBands, 195 int nElems) { 196 197 byte[][] lutData = new byte[nBands][nElems]; 198 int band; 199 200 for (band=0; band<scale.length; band++) { 201 float bandScale = scale[band]; 202 float bandOff = off[band]; 203 byte[] bandLutData = lutData[band]; 204 for (int i=0; i<nElems; i++) { 205 int val = (int)(i*bandScale + bandOff); 206 if ((val & 0xffffff00) != 0) { 207 if (val < 0) { 208 val = 0; 209 } else { 210 val = 255; 211 } 212 } 213 bandLutData[i] = (byte)val; 214 } 215 216 } 217 int maxToCopy = (nBands == 4 && scale.length == 4) ? 4 : 3; 218 while (band < lutData.length && band < maxToCopy) { 219 System.arraycopy(lutData[band-1], 0, lutData[band], 0, nElems); 220 band++; 221 } 222 if (nBands == 4 && band < nBands) { 223 byte[] bandLutData = lutData[band]; 224 for (int i=0; i<nElems; i++) { 225 bandLutData[i] = (byte)i; 226 } 227 } 228 229 return new ByteLookupTable(0, lutData); 230 } 231 232 /** 233 * Creates a ShortLookupTable to implement the rescale. 234 * The table may have either a SHORT or BYTE input. 235 * @param nElems Number of elements the table is to have. 236 * This will generally be 256 for byte and 237 * 65536 for short. 238 */ 239 private ShortLookupTable createShortLut(float scale[], 240 float off[], 241 int nBands, 242 int nElems) { 243 244 short[][] lutData = new short[nBands][nElems]; 245 int band = 0; 246 247 for (band=0; band<scale.length; band++) { 248 float bandScale = scale[band]; 249 float bandOff = off[band]; 250 short[] bandLutData = lutData[band]; 251 for (int i=0; i<nElems; i++) { 252 int val = (int)(i*bandScale + bandOff); 253 if ((val & 0xffff0000) != 0) { 254 if (val < 0) { 255 val = 0; 256 } else { 257 val = 65535; 258 } 259 } 260 bandLutData[i] = (short)val; 261 } 262 } 263 int maxToCopy = (nBands == 4 && scale.length == 4) ? 4 : 3; 264 while (band < lutData.length && band < maxToCopy) { 265 System.arraycopy(lutData[band-1], 0, lutData[band], 0, nElems); 266 band++; 267 } 268 if (nBands == 4 && band < nBands) { 269 short[] bandLutData = lutData[band]; 270 for (int i=0; i<nElems; i++) { 271 bandLutData[i] = (short)i; 272 } 273 } 274 275 return new ShortLookupTable(0, lutData); 276 } 277 278 279 /** 280 * Determines if the rescale can be performed as a lookup. 281 * The dst must be a byte or short type. 282 * The src must be less than 16 bits. 283 * All source band sizes must be the same and all dst band sizes 284 * must be the same. 285 */ 286 private boolean canUseLookup(Raster src, Raster dst) { 287 288 // 289 // Check that the src datatype is either a BYTE or SHORT 290 // 291 int datatype = src.getDataBuffer().getDataType(); 292 if(datatype != DataBuffer.TYPE_BYTE && 293 datatype != DataBuffer.TYPE_USHORT) { 294 return false; 295 } 296 297 // 298 // Check dst sample sizes. All must be 8 or 16 bits. 299 // 300 SampleModel dstSM = dst.getSampleModel(); 301 dstNbits = dstSM.getSampleSize(0); 302 303 if (!(dstNbits == 8 || dstNbits == 16)) { 304 return false; 305 } 306 for (int i=1; i<src.getNumBands(); i++) { 307 int bandSize = dstSM.getSampleSize(i); 308 if (bandSize != dstNbits) { 309 return false; 310 } 311 } 312 313 // 314 // Check src sample sizes. All must be the same size 315 // 316 SampleModel srcSM = src.getSampleModel(); 317 srcNbits = srcSM.getSampleSize(0); 318 if (srcNbits > 16) { 319 return false; 320 } 321 for (int i=1; i<src.getNumBands(); i++) { 322 int bandSize = srcSM.getSampleSize(i); 323 if (bandSize != srcNbits) { 324 return false; 325 } 326 } 327 328 if (dstSM instanceof ComponentSampleModel) { 329 ComponentSampleModel dsm = (ComponentSampleModel)dstSM; 330 if (dsm.getPixelStride() != dst.getNumBands()) { 331 return false; 332 } 333 } 334 if (srcSM instanceof ComponentSampleModel) { 335 ComponentSampleModel csm = (ComponentSampleModel)srcSM; 336 if (csm.getPixelStride() != src.getNumBands()) { 337 return false; 338 } 339 } 340 341 return true; 342 } 343 344 /** 345 * Rescales the source BufferedImage. 346 * If the color model in the source image is not the same as that 347 * in the destination image, the pixels will be converted 348 * in the destination. If the destination image is null, 349 * a BufferedImage will be created with the source ColorModel. 350 * An IllegalArgumentException may be thrown if the number of 351 * scaling factors/offsets in this object does not meet the 352 * restrictions stated in the class comments above, or if the 353 * source image has an IndexColorModel. 354 * @param src the {@code BufferedImage} to be filtered 355 * @param dst the destination for the filtering operation 356 * or {@code null} 357 * @return the filtered {@code BufferedImage}. 358 * @throws IllegalArgumentException if the {@code ColorModel} 359 * of {@code src} is an {@code IndexColorModel}, 360 * or if the number of scaling factors and offsets in this 361 * {@code RescaleOp} do not meet the requirements 362 * stated in the class comments. 363 */ 364 public final BufferedImage filter (BufferedImage src, BufferedImage dst) { 365 ColorModel srcCM = src.getColorModel(); 366 ColorModel dstCM; 367 int numSrcColorComp = srcCM.getNumColorComponents(); 368 int scaleConst = length; 369 370 if (srcCM instanceof IndexColorModel) { 371 throw new 372 IllegalArgumentException("Rescaling cannot be "+ 373 "performed on an indexed image"); 374 } 375 if (scaleConst != 1 && scaleConst != numSrcColorComp && 376 scaleConst != srcCM.getNumComponents()) 377 { 378 throw new IllegalArgumentException("Number of scaling constants "+ 379 "does not equal the number of"+ 380 " of color or color/alpha "+ 381 " components"); 382 } 383 384 boolean needToConvert = false; 385 boolean needToDraw = false; 386 387 // Include alpha 388 if (scaleConst > numSrcColorComp && srcCM.hasAlpha()) { 389 scaleConst = numSrcColorComp+1; 390 } 391 392 int width = src.getWidth(); 393 int height = src.getHeight(); 394 395 BufferedImage origDst = dst; 396 if (dst == null) { 397 dst = createCompatibleDestImage(src, null); 398 dstCM = srcCM; 399 } 400 else { 401 if (width != dst.getWidth()) { 402 throw new 403 IllegalArgumentException("Src width ("+width+ 404 ") not equal to dst width ("+ 405 dst.getWidth()+")"); 406 } 407 if (height != dst.getHeight()) { 408 throw new 409 IllegalArgumentException("Src height ("+height+ 410 ") not equal to dst height ("+ 411 dst.getHeight()+")"); 412 } 413 414 dstCM = dst.getColorModel(); 415 if(srcCM.getColorSpace().getType() != 416 dstCM.getColorSpace().getType()) { 417 needToConvert = true; 418 dst = createCompatibleDestImage(src, null); 419 } 420 421 } 422 423 // 424 // Try to use a native BI rescale operation first 425 // 426 if (ImagingLib.filter(this, src, dst) == null) { 427 if (src.getRaster().getNumBands() != 428 dst.getRaster().getNumBands()) { 429 needToDraw = true; 430 dst = createCompatibleDestImage(src, null); 431 } 432 433 // 434 // Native BI rescale failed - convert to rasters 435 // 436 WritableRaster srcRaster = src.getRaster(); 437 WritableRaster dstRaster = dst.getRaster(); 438 439 // 440 // Call the raster filter method 441 // 442 filterRasterImpl(srcRaster, dstRaster, scaleConst, false); 443 } 444 445 if (needToDraw) { 446 Graphics2D g = origDst.createGraphics(); 447 g.drawImage(dst, 0, 0, width, height, null); 448 g.dispose(); 449 } 450 if (needToConvert) { 451 // ColorModels are not the same 452 ColorConvertOp ccop = new ColorConvertOp(hints); 453 dst = ccop.filter(dst, origDst); 454 } 455 return dst; 456 } 457 458 /** 459 * Rescales the pixel data in the source Raster. 460 * If the destination Raster is null, a new Raster will be created. 461 * The source and destination must have the same number of bands. 462 * Otherwise, an IllegalArgumentException is thrown. 463 * Note that the number of scaling factors/offsets in this object must 464 * meet the restrictions stated in the class comments above. 465 * Otherwise, an IllegalArgumentException is thrown. 466 * @param src the {@code Raster} to be filtered 467 * @param dst the destination for the filtering operation 468 * or {@code null} 469 * @return the filtered {@code WritableRaster}. 470 * @throws IllegalArgumentException if {@code src} and 471 * {@code dst} do not have the same number of bands, 472 * or if the number of scaling factors and offsets in this 473 * {@code RescaleOp} do not meet the requirements 474 * stated in the class comments. 475 */ 476 public final WritableRaster filter (Raster src, WritableRaster dst) { 477 return filterRasterImpl(src, dst, length, true); 478 } 479 480 private WritableRaster filterRasterImpl(Raster src, WritableRaster dst, 481 int scaleConst, boolean sCheck) { 482 int numBands = src.getNumBands(); 483 int width = src.getWidth(); 484 int height = src.getHeight(); 485 int[] srcPix = null; 486 int step = 0; 487 int tidx = 0; 488 489 // Create a new destination Raster, if needed 490 if (dst == null) { 491 dst = createCompatibleDestRaster(src); 492 } 493 else if (height != dst.getHeight() || width != dst.getWidth()) { 494 throw new 495 IllegalArgumentException("Width or height of Rasters do not "+ 496 "match"); 497 } 498 else if (numBands != dst.getNumBands()) { 499 // Make sure that the number of bands are equal 500 throw new IllegalArgumentException("Number of bands in src " 501 + numBands 502 + " does not equal number of bands in dest " 503 + dst.getNumBands()); 504 } 505 506 // Make sure that the arrays match 507 // Make sure that the low/high/constant arrays match 508 if (sCheck && scaleConst != 1 && scaleConst != src.getNumBands()) { 509 throw new IllegalArgumentException("Number of scaling constants "+ 510 "does not equal the number of"+ 511 " of bands in the src raster"); 512 } 513 514 // 515 // Try for a native raster rescale first 516 // 517 if (ImagingLib.filter(this, src, dst) != null) { 518 return dst; 519 } 520 521 // 522 // Native raster rescale failed. 523 // Try to see if a lookup operation can be used 524 // 525 if (canUseLookup(src, dst)) { 526 int srcNgray = (1 << srcNbits); 527 int dstNgray = (1 << dstNbits); 528 529 if (dstNgray == 256) { 530 ByteLookupTable lut = createByteLut(scaleFactors, offsets, 531 numBands, srcNgray); 532 LookupOp op = new LookupOp(lut, hints); 533 op.filter(src, dst); 534 } else { 535 ShortLookupTable lut = createShortLut(scaleFactors, offsets, 536 numBands, srcNgray); 537 LookupOp op = new LookupOp(lut, hints); 538 op.filter(src, dst); 539 } 540 } else { 541 // 542 // Fall back to the slow code 543 // 544 if (scaleConst > 1) { 545 step = 1; 546 } 547 548 int sminX = src.getMinX(); 549 int sY = src.getMinY(); 550 int dminX = dst.getMinX(); 551 int dY = dst.getMinY(); 552 int sX; 553 int dX; 554 555 // 556 // Determine bits per band to determine maxval for clamps. 557 // The min is assumed to be zero. 558 // REMIND: This must change if we ever support signed data types. 559 // 560 int nbits; 561 int dstMax[] = new int[numBands]; 562 int dstMask[] = new int[numBands]; 563 SampleModel dstSM = dst.getSampleModel(); 564 for (int z=0; z<numBands; z++) { 565 nbits = dstSM.getSampleSize(z); 566 dstMax[z] = (1 << nbits) - 1; 567 dstMask[z] = ~(dstMax[z]); 568 } 569 570 int val; 571 for (int y=0; y < height; y++, sY++, dY++) { 572 dX = dminX; 573 sX = sminX; 574 for (int x = 0; x < width; x++, sX++, dX++) { 575 // Get data for all bands at this x,y position 576 srcPix = src.getPixel(sX, sY, srcPix); 577 tidx = 0; 578 for (int z=0; z<numBands; z++, tidx += step) { 579 if ((scaleConst == 1 || scaleConst == 3) && (z == 3)) { 580 val = srcPix[z]; 581 } else { 582 val = (int)(srcPix[z]*scaleFactors[tidx] 583 + offsets[tidx]); 584 585 } 586 // Clamp 587 if ((val & dstMask[z]) != 0) { 588 if (val < 0) { 589 val = 0; 590 } else { 591 val = dstMax[z]; 592 } 593 } 594 srcPix[z] = val; 595 596 } 597 598 // Put it back for all bands 599 dst.setPixel(dX, dY, srcPix); 600 } 601 } 602 } 603 return dst; 604 } 605 606 /** 607 * Returns the bounding box of the rescaled destination image. Since 608 * this is not a geometric operation, the bounding box does not 609 * change. 610 */ 611 public final Rectangle2D getBounds2D (BufferedImage src) { 612 return getBounds2D(src.getRaster()); 613 } 614 615 /** 616 * Returns the bounding box of the rescaled destination Raster. Since 617 * this is not a geometric operation, the bounding box does not 618 * change. 619 * @param src the rescaled destination {@code Raster} 620 * @return the bounds of the specified {@code Raster}. 621 */ 622 public final Rectangle2D getBounds2D (Raster src) { 623 return src.getBounds(); 624 } 625 626 /** 627 * Creates a zeroed destination image with the correct size and number of 628 * bands. 629 * @param src Source image for the filter operation. 630 * @param destCM ColorModel of the destination. If null, the 631 * ColorModel of the source will be used. 632 * @return the zeroed-destination image. 633 */ 634 public BufferedImage createCompatibleDestImage (BufferedImage src, 635 ColorModel destCM) { 636 BufferedImage image; 637 if (destCM == null) { 638 ColorModel cm = src.getColorModel(); 639 image = new BufferedImage(cm, 640 src.getRaster().createCompatibleWritableRaster(), 641 cm.isAlphaPremultiplied(), 642 null); 643 } 644 else { 645 int w = src.getWidth(); 646 int h = src.getHeight(); 647 image = new BufferedImage (destCM, 648 destCM.createCompatibleWritableRaster(w, h), 649 destCM.isAlphaPremultiplied(), null); 650 } 651 652 return image; 653 } 654 655 /** 656 * Creates a zeroed-destination {@code Raster} with the correct 657 * size and number of bands, given this source. 658 * @param src the source {@code Raster} 659 * @return the zeroed-destination {@code Raster}. 660 */ 661 public WritableRaster createCompatibleDestRaster (Raster src) { 662 return src.createCompatibleWritableRaster(src.getWidth(), src.getHeight()); 663 } 664 665 /** 666 * Returns the location of the destination point given a 667 * point in the source. If dstPt is non-null, it will 668 * be used to hold the return value. Since this is not a geometric 669 * operation, the srcPt will equal the dstPt. 670 * @param srcPt a point in the source image 671 * @param dstPt the destination point or {@code null} 672 * @return the location of the destination point. 673 */ 674 public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) { 675 if (dstPt == null) { 676 dstPt = new Point2D.Float(); 677 } 678 dstPt.setLocation(srcPt.getX(), srcPt.getY()); 679 return dstPt; 680 } 681 682 /** 683 * Returns the rendering hints for this op. 684 * @return the rendering hints of this {@code RescaleOp}. 685 */ 686 public final RenderingHints getRenderingHints() { 687 return hints; 688 } 689 }