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 * @date: Oct 6, 2005 22 */ 23 24package java.awt.image; 25 26import java.awt.geom.Point2D; 27import java.awt.geom.Rectangle2D; 28import java.awt.*; 29import java.util.Arrays; 30 31import org.apache.harmony.awt.gl.AwtImageBackdoorAccessor; 32import org.apache.harmony.awt.internal.nls.Messages; 33 34/** 35 * The Class RescaleOp performs rescaling of the source image data by 36 * multiplying the pixel values with a scale factor and then adding an offset. 37 * 38 * @since Android 1.0 39 */ 40public class RescaleOp implements BufferedImageOp, RasterOp { 41 42 /** 43 * The scale factors. 44 */ 45 private float scaleFactors[]; 46 47 /** 48 * The offsets. 49 */ 50 private float offsets[]; 51 52 /** 53 * The hints. 54 */ 55 private RenderingHints hints; 56 57 static { 58 // TODO 59 // System.loadLibrary("imageops"); 60 } 61 62 /** 63 * Instantiates a new RescaleOp object with the specified scale factors and 64 * offsets. 65 * 66 * @param scaleFactors 67 * the array of scale factor values. 68 * @param offsets 69 * the array of offset values. 70 * @param hints 71 * the RenderingHints or null. 72 */ 73 public RescaleOp(float[] scaleFactors, float[] offsets, RenderingHints hints) { 74 int numFactors = Math.min(scaleFactors.length, offsets.length); 75 76 this.scaleFactors = new float[numFactors]; 77 this.offsets = new float[numFactors]; 78 79 System.arraycopy(scaleFactors, 0, this.scaleFactors, 0, numFactors); 80 System.arraycopy(offsets, 0, this.offsets, 0, numFactors); 81 82 this.hints = hints; 83 } 84 85 /** 86 * Instantiates a new RescaleOp object with the specified scale factor and 87 * offset. 88 * 89 * @param scaleFactor 90 * the scale factor. 91 * @param offset 92 * the offset. 93 * @param hints 94 * the RenderingHints or null. 95 */ 96 public RescaleOp(float scaleFactor, float offset, RenderingHints hints) { 97 scaleFactors = new float[1]; 98 offsets = new float[1]; 99 100 scaleFactors[0] = scaleFactor; 101 offsets[0] = offset; 102 103 this.hints = hints; 104 } 105 106 /** 107 * Gets the number of scaling factors. 108 * 109 * @return the number of scaling factors. 110 */ 111 public final int getNumFactors() { 112 return scaleFactors.length; 113 } 114 115 public final RenderingHints getRenderingHints() { 116 return hints; 117 } 118 119 /** 120 * Gets the scale factors of this RescaleOp. 121 * 122 * @param scaleFactors 123 * the desired scale factors array will be copied to this array. 124 * @return the scale factors array. 125 */ 126 public final float[] getScaleFactors(float[] scaleFactors) { 127 if (scaleFactors == null) { 128 scaleFactors = new float[this.scaleFactors.length]; 129 } 130 131 int minLength = Math.min(scaleFactors.length, this.scaleFactors.length); 132 System.arraycopy(this.scaleFactors, 0, scaleFactors, 0, minLength); 133 return scaleFactors; 134 } 135 136 /** 137 * Gets the offsets array of this RescaleOp. 138 * 139 * @param offsets 140 * the desired offsets array will be copied to this array. 141 * @return the offsets array of this RescaleOp. 142 */ 143 public final float[] getOffsets(float[] offsets) { 144 if (offsets == null) { 145 offsets = new float[this.offsets.length]; 146 } 147 148 int minLength = Math.min(offsets.length, this.offsets.length); 149 System.arraycopy(this.offsets, 0, offsets, 0, minLength); 150 return offsets; 151 } 152 153 public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { 154 if (dstPt == null) { 155 dstPt = new Point2D.Float(); 156 } 157 158 dstPt.setLocation(srcPt); 159 return dstPt; 160 } 161 162 public final Rectangle2D getBounds2D(Raster src) { 163 return src.getBounds(); 164 } 165 166 public final Rectangle2D getBounds2D(BufferedImage src) { 167 return getBounds2D(src.getRaster()); 168 } 169 170 public WritableRaster createCompatibleDestRaster(Raster src) { 171 return src.createCompatibleWritableRaster(); 172 } 173 174 public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) { 175 if (dstCM == null) { 176 dstCM = src.getColorModel(); 177 } 178 179 if (dstCM instanceof IndexColorModel) { 180 dstCM = ColorModel.getRGBdefault(); 181 } 182 183 WritableRaster r = dstCM.isCompatibleSampleModel(src.getSampleModel()) ? src.getRaster() 184 .createCompatibleWritableRaster(src.getWidth(), src.getHeight()) : dstCM 185 .createCompatibleWritableRaster(src.getWidth(), src.getHeight()); 186 187 return new BufferedImage(dstCM, r, dstCM.isAlphaPremultiplied(), null); 188 } 189 190 public final WritableRaster filter(Raster src, WritableRaster dst) { 191 if (dst == null) { 192 dst = createCompatibleDestRaster(src); 193 } else { 194 if (src.getNumBands() != dst.getNumBands()) { 195 // awt.21D=Number of src bands ({0}) does not match number of 196 // dst bands ({1}) 197 throw new IllegalArgumentException(Messages.getString("awt.21D", //$NON-NLS-1$ 198 src.getNumBands(), dst.getNumBands())); 199 } 200 } 201 202 if (this.scaleFactors.length != 1 && this.scaleFactors.length != src.getNumBands()) { 203 // awt.21E=Number of scaling constants is not equal to the number of 204 // bands 205 throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$ 206 } 207 208 // TODO 209 // if (ippFilter(src, dst, BufferedImage.TYPE_CUSTOM, false) != 0) 210 if (slowFilter(src, dst, false) != 0) { 211 // awt.21F=Unable to transform source 212 throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$ 213 } 214 215 return dst; 216 } 217 218 /** 219 * Slow filter. 220 * 221 * @param src 222 * the src. 223 * @param dst 224 * the dst. 225 * @param skipAlpha 226 * the skip alpha. 227 * @return the int. 228 */ 229 private final int slowFilter(Raster src, WritableRaster dst, boolean skipAlpha) { 230 SampleModel sm = src.getSampleModel(); 231 232 int numBands = src.getNumBands(); 233 int srcHeight = src.getHeight(); 234 int srcWidth = src.getWidth(); 235 236 int srcMinX = src.getMinX(); 237 int srcMinY = src.getMinY(); 238 int dstMinX = dst.getMinX(); 239 int dstMinY = dst.getMinY(); 240 241 int[] maxValues = new int[numBands]; 242 int[] masks = new int[numBands]; 243 int[] sampleSizes = sm.getSampleSize(); 244 245 for (int i = 0; i < numBands; i++) { 246 maxValues[i] = (1 << sampleSizes[i]) - 1; 247 masks[i] = ~(maxValues[i]); 248 } 249 250 // Processing bounds 251 float[] pixels = null; 252 pixels = src.getPixels(srcMinX, srcMinY, srcWidth, srcHeight, pixels); 253 254 // Cycle over pixels to be calculated 255 if (skipAlpha) { // Always suppose that alpha channel is the last band 256 if (scaleFactors.length > 1) { 257 for (int i = 0; i < pixels.length;) { 258 for (int bandIdx = 0; bandIdx < numBands - 1; bandIdx++, i++) { 259 pixels[i] = pixels[i] * scaleFactors[bandIdx] + offsets[bandIdx]; 260 // Check for overflow now 261 if (((int)pixels[i] & masks[bandIdx]) != 0) { 262 if (pixels[i] < 0) { 263 pixels[i] = 0; 264 } else { 265 pixels[i] = maxValues[bandIdx]; 266 } 267 } 268 } 269 270 i++; 271 } 272 } else { 273 for (int i = 0; i < pixels.length;) { 274 for (int bandIdx = 0; bandIdx < numBands - 1; bandIdx++, i++) { 275 pixels[i] = pixels[i] * scaleFactors[0] + offsets[0]; 276 // Check for overflow now 277 if (((int)pixels[i] & masks[bandIdx]) != 0) { 278 if (pixels[i] < 0) { 279 pixels[i] = 0; 280 } else { 281 pixels[i] = maxValues[bandIdx]; 282 } 283 } 284 } 285 286 i++; 287 } 288 } 289 } else { 290 if (scaleFactors.length > 1) { 291 for (int i = 0; i < pixels.length;) { 292 for (int bandIdx = 0; bandIdx < numBands; bandIdx++, i++) { 293 pixels[i] = pixels[i] * scaleFactors[bandIdx] + offsets[bandIdx]; 294 // Check for overflow now 295 if (((int)pixels[i] & masks[bandIdx]) != 0) { 296 if (pixels[i] < 0) { 297 pixels[i] = 0; 298 } else { 299 pixels[i] = maxValues[bandIdx]; 300 } 301 } 302 } 303 } 304 } else { 305 for (int i = 0; i < pixels.length;) { 306 for (int bandIdx = 0; bandIdx < numBands; bandIdx++, i++) { 307 pixels[i] = pixels[i] * scaleFactors[0] + offsets[0]; 308 // Check for overflow now 309 if (((int)pixels[i] & masks[bandIdx]) != 0) { 310 if (pixels[i] < 0) { 311 pixels[i] = 0; 312 } else { 313 pixels[i] = maxValues[bandIdx]; 314 } 315 } 316 } 317 } 318 } 319 } 320 321 dst.setPixels(dstMinX, dstMinY, srcWidth, srcHeight, pixels); 322 323 return 0; 324 } 325 326 public final BufferedImage filter(BufferedImage src, BufferedImage dst) { 327 ColorModel srcCM = src.getColorModel(); 328 329 if (srcCM instanceof IndexColorModel) { 330 // awt.220=Source should not have IndexColorModel 331 throw new IllegalArgumentException(Messages.getString("awt.220")); //$NON-NLS-1$ 332 } 333 334 // Check if the number of scaling factors matches the number of bands 335 int nComponents = srcCM.getNumComponents(); 336 boolean skipAlpha; 337 if (srcCM.hasAlpha()) { 338 if (scaleFactors.length == 1 || scaleFactors.length == nComponents - 1) { 339 skipAlpha = true; 340 } else if (scaleFactors.length == nComponents) { 341 skipAlpha = false; 342 } else { 343 // awt.21E=Number of scaling constants is not equal to the 344 // number of bands 345 throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$ 346 } 347 } else if (scaleFactors.length == 1 || scaleFactors.length == nComponents) { 348 skipAlpha = false; 349 } else { 350 // awt.21E=Number of scaling constants is not equal to the number of 351 // bands 352 throw new IllegalArgumentException(Messages.getString("awt.21E")); //$NON-NLS-1$ 353 } 354 355 BufferedImage finalDst = null; 356 if (dst == null) { 357 finalDst = dst; 358 dst = createCompatibleDestImage(src, srcCM); 359 } else if (!srcCM.equals(dst.getColorModel())) { 360 // Treat BufferedImage.TYPE_INT_RGB and BufferedImage.TYPE_INT_ARGB 361 // as same 362 if (!((src.getType() == BufferedImage.TYPE_INT_RGB || src.getType() == BufferedImage.TYPE_INT_ARGB) && (dst 363 .getType() == BufferedImage.TYPE_INT_RGB || dst.getType() == BufferedImage.TYPE_INT_ARGB))) { 364 finalDst = dst; 365 dst = createCompatibleDestImage(src, srcCM); 366 } 367 } 368 369 // TODO 370 // if (ippFilter(src.getRaster(), dst.getRaster(), src.getType(), 371 // skipAlpha) != 0) 372 if (slowFilter(src.getRaster(), dst.getRaster(), skipAlpha) != 0) { 373 // awt.21F=Unable to transform source 374 throw new ImagingOpException(Messages.getString("awt.21F")); //$NON-NLS-1$ 375 } 376 377 if (finalDst != null) { 378 Graphics2D g = finalDst.createGraphics(); 379 g.setComposite(AlphaComposite.Src); 380 g.drawImage(dst, 0, 0, null); 381 } else { 382 finalDst = dst; 383 } 384 385 return finalDst; 386 } 387 388 // Don't forget to pass allocated arrays for levels and values, size should 389 // be numBands*4 390 /** 391 * Creates the levels. 392 * 393 * @param sm 394 * the sm. 395 * @param numBands 396 * the num bands. 397 * @param skipAlpha 398 * the skip alpha. 399 * @param levels 400 * the levels. 401 * @param values 402 * the values. 403 * @param channelsOrder 404 * the channels order. 405 */ 406 private final void createLevels(SampleModel sm, int numBands, boolean skipAlpha, int levels[], 407 int values[], int channelsOrder[]) { 408 // Suppose same sample size for all channels, otherwise use slow filter 409 int maxValue = (1 << sm.getSampleSize(0)) - 1; 410 411 // For simplicity introduce these arrays 412 float extScaleFactors[] = new float[numBands]; 413 float extOffsets[] = new float[numBands]; 414 415 if (scaleFactors.length != 1) { 416 System.arraycopy(scaleFactors, 0, extScaleFactors, 0, scaleFactors.length); 417 System.arraycopy(offsets, 0, extOffsets, 0, scaleFactors.length); 418 } else { 419 for (int i = 0; i < numBands; i++) { 420 extScaleFactors[i] = scaleFactors[0]; 421 extOffsets[i] = offsets[0]; 422 } 423 } 424 425 if (skipAlpha) { 426 extScaleFactors[numBands - 1] = 1; 427 extOffsets[numBands - 1] = 0; 428 } 429 430 // Create a levels 431 for (int i = 0; i < numBands; i++) { 432 if (extScaleFactors[i] == 0) { 433 levels[i * 4] = 0; 434 levels[i * 4 + 1] = 0; 435 levels[i * 4 + 2] = maxValue + 1; 436 levels[i * 4 + 3] = maxValue + 1; 437 } 438 439 float minLevel = -extOffsets[i] / extScaleFactors[i]; 440 float maxLevel = (maxValue - extOffsets[i]) / extScaleFactors[i]; 441 442 if (minLevel < 0) { 443 minLevel = 0; 444 } else if (minLevel > maxValue) { 445 minLevel = maxValue; 446 } 447 448 if (maxLevel < 0) { 449 maxLevel = 0; 450 } else if (maxLevel > maxValue) { 451 maxLevel = maxValue; 452 } 453 454 levels[i * 4] = 0; 455 if (minLevel > maxLevel) { 456 levels[i * 4 + 1] = (int)maxLevel; 457 levels[i * 4 + 2] = (int)minLevel; 458 } else { 459 levels[i * 4 + 1] = (int)minLevel; 460 levels[i * 4 + 2] = (int)maxLevel; 461 } 462 levels[i * 4 + 3] = maxValue + 1; 463 464 // Fill values 465 for (int k = 0; k < 4; k++) { 466 int idx = i * 4 + k; 467 values[idx] = (int)(extScaleFactors[i] * levels[idx] + extOffsets[i]); 468 if (values[idx] < 0) { 469 values[idx] = 0; 470 } else if (values[idx] > maxValue) { 471 values[idx] = maxValue; 472 } 473 } 474 } 475 476 // Reorder data if channels are stored in different order 477 if (channelsOrder != null) { 478 int len = numBands * 4; 479 int savedLevels[] = new int[len]; 480 int savedValues[] = new int[len]; 481 System.arraycopy(levels, 0, savedLevels, 0, len); 482 System.arraycopy(values, 0, savedValues, 0, len); 483 for (int i = 0; i < channelsOrder.length; i++) { 484 System.arraycopy(savedLevels, i * 4, levels, channelsOrder[i] * 4, 4); 485 System.arraycopy(savedValues, i * 4, values, channelsOrder[i] * 4, 4); 486 } 487 } 488 } 489 490 // TODO remove when this method is used 491 /** 492 * Ipp filter. 493 * 494 * @param src 495 * the src. 496 * @param dst 497 * the dst. 498 * @param imageType 499 * the image type. 500 * @param skipAlpha 501 * the skip alpha. 502 * @return the int. 503 */ 504 @SuppressWarnings("unused") 505 private final int ippFilter(Raster src, WritableRaster dst, int imageType, boolean skipAlpha) { 506 int res; 507 508 int srcStride, dstStride; 509 int channels; 510 int offsets[] = null; 511 int channelsOrder[] = null; 512 513 switch (imageType) { 514 case BufferedImage.TYPE_INT_ARGB: 515 case BufferedImage.TYPE_INT_ARGB_PRE: 516 case BufferedImage.TYPE_INT_RGB: { 517 channels = 4; 518 srcStride = src.getWidth() * 4; 519 dstStride = dst.getWidth() * 4; 520 channelsOrder = new int[] { 521 2, 1, 0, 3 522 }; 523 break; 524 } 525 526 case BufferedImage.TYPE_4BYTE_ABGR: 527 case BufferedImage.TYPE_4BYTE_ABGR_PRE: 528 case BufferedImage.TYPE_INT_BGR: { 529 channels = 4; 530 srcStride = src.getWidth() * 4; 531 dstStride = dst.getWidth() * 4; 532 break; 533 } 534 535 case BufferedImage.TYPE_BYTE_GRAY: { 536 channels = 1; 537 srcStride = src.getWidth(); 538 dstStride = dst.getWidth(); 539 break; 540 } 541 542 case BufferedImage.TYPE_3BYTE_BGR: { 543 channels = 3; 544 srcStride = src.getWidth() * 3; 545 dstStride = dst.getWidth() * 3; 546 channelsOrder = new int[] { 547 2, 1, 0 548 }; 549 break; 550 } 551 552 case BufferedImage.TYPE_USHORT_GRAY: 553 case BufferedImage.TYPE_USHORT_565_RGB: 554 case BufferedImage.TYPE_USHORT_555_RGB: 555 case BufferedImage.TYPE_BYTE_BINARY: { 556 return slowFilter(src, dst, skipAlpha); 557 } 558 559 default: { 560 SampleModel srcSM = src.getSampleModel(); 561 SampleModel dstSM = dst.getSampleModel(); 562 563 if (srcSM instanceof PixelInterleavedSampleModel 564 && dstSM instanceof PixelInterleavedSampleModel) { 565 // Check PixelInterleavedSampleModel 566 if (srcSM.getDataType() != DataBuffer.TYPE_BYTE 567 || dstSM.getDataType() != DataBuffer.TYPE_BYTE) { 568 return slowFilter(src, dst, skipAlpha); 569 } 570 571 channels = srcSM.getNumBands(); // Have IPP functions for 1, 572 // 3 and 4 channels 573 if (!(channels == 1 || channels == 3 || channels == 4)) { 574 return slowFilter(src, dst, skipAlpha); 575 } 576 577 srcStride = ((ComponentSampleModel)srcSM).getScanlineStride(); 578 dstStride = ((ComponentSampleModel)dstSM).getScanlineStride(); 579 580 channelsOrder = ((ComponentSampleModel)srcSM).getBandOffsets(); 581 } else if (srcSM instanceof SinglePixelPackedSampleModel 582 && dstSM instanceof SinglePixelPackedSampleModel) { 583 // Check SinglePixelPackedSampleModel 584 SinglePixelPackedSampleModel sppsm1 = (SinglePixelPackedSampleModel)srcSM; 585 SinglePixelPackedSampleModel sppsm2 = (SinglePixelPackedSampleModel)dstSM; 586 587 channels = sppsm1.getNumBands(); 588 589 // TYPE_INT_RGB, TYPE_INT_ARGB... 590 if (sppsm1.getDataType() != DataBuffer.TYPE_INT 591 || sppsm2.getDataType() != DataBuffer.TYPE_INT 592 || !(channels == 3 || channels == 4)) { 593 return slowFilter(src, dst, skipAlpha); 594 } 595 596 // Check compatibility of sample models 597 if (!Arrays.equals(sppsm1.getBitOffsets(), sppsm2.getBitOffsets()) 598 || !Arrays.equals(sppsm1.getBitMasks(), sppsm2.getBitMasks())) { 599 return slowFilter(src, dst, skipAlpha); 600 } 601 602 for (int i = 0; i < channels; i++) { 603 if (sppsm1.getSampleSize(i) != 8) { 604 return slowFilter(src, dst, skipAlpha); 605 } 606 } 607 608 channelsOrder = new int[channels]; 609 int bitOffsets[] = sppsm1.getBitOffsets(); 610 for (int i = 0; i < channels; i++) { 611 channelsOrder[i] = bitOffsets[i] / 8; 612 } 613 614 if (channels == 3) { // Don't skip channel now, could be 615 // optimized 616 channels = 4; 617 } 618 619 srcStride = sppsm1.getScanlineStride() * 4; 620 dstStride = sppsm2.getScanlineStride() * 4; 621 } else { 622 return slowFilter(src, dst, skipAlpha); 623 } 624 625 // Fill offsets if there's a child raster 626 if (src.getParent() != null || dst.getParent() != null) { 627 if (src.getSampleModelTranslateX() != 0 || src.getSampleModelTranslateY() != 0 628 || dst.getSampleModelTranslateX() != 0 629 || dst.getSampleModelTranslateY() != 0) { 630 offsets = new int[4]; 631 offsets[0] = -src.getSampleModelTranslateX() + src.getMinX(); 632 offsets[1] = -src.getSampleModelTranslateY() + src.getMinY(); 633 offsets[2] = -dst.getSampleModelTranslateX() + dst.getMinX(); 634 offsets[3] = -dst.getSampleModelTranslateY() + dst.getMinY(); 635 } 636 } 637 } 638 } 639 640 int levels[] = new int[4 * channels]; 641 int values[] = new int[4 * channels]; 642 643 createLevels(src.getSampleModel(), channels, skipAlpha, levels, values, channelsOrder); 644 645 Object srcData, dstData; 646 AwtImageBackdoorAccessor dbAccess = AwtImageBackdoorAccessor.getInstance(); 647 try { 648 srcData = dbAccess.getData(src.getDataBuffer()); 649 dstData = dbAccess.getData(dst.getDataBuffer()); 650 } catch (IllegalArgumentException e) { 651 return -1; // Unknown data buffer type 652 } 653 654 res = LookupOp.ippLUT(srcData, src.getWidth(), src.getHeight(), srcStride, dstData, dst 655 .getWidth(), dst.getHeight(), dstStride, levels, values, channels, offsets, true); 656 657 return res; 658 } 659} 660