1 /* 2 * Copyright (c) 2001, 2016, 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 sun.java2d.pipe; 27 28 import java.awt.AlphaComposite; 29 import java.awt.Color; 30 import java.awt.Image; 31 import java.awt.Transparency; 32 import java.awt.geom.AffineTransform; 33 import java.awt.geom.NoninvertibleTransformException; 34 import java.awt.image.AffineTransformOp; 35 import java.awt.image.BufferedImage; 36 import java.awt.image.BufferedImageOp; 37 import java.awt.image.ColorModel; 38 import java.awt.image.DataBuffer; 39 import java.awt.image.ImageObserver; 40 import java.awt.image.IndexColorModel; 41 import java.awt.image.Raster; 42 import java.awt.image.VolatileImage; 43 import sun.awt.SunHints; 44 import sun.awt.image.ImageRepresentation; 45 import sun.awt.image.SurfaceManager; 46 import sun.awt.image.ToolkitImage; 47 import sun.java2d.InvalidPipeException; 48 import sun.java2d.SunGraphics2D; 49 import sun.java2d.SurfaceData; 50 import sun.java2d.loops.Blit; 51 import sun.java2d.loops.BlitBg; 52 import sun.java2d.loops.TransformHelper; 53 import sun.java2d.loops.MaskBlit; 54 import sun.java2d.loops.CompositeType; 55 import sun.java2d.loops.ScaledBlit; 56 import sun.java2d.loops.SurfaceType; 57 58 public class DrawImage implements DrawImagePipe 59 { 60 public boolean copyImage(SunGraphics2D sg, Image img, 61 int x, int y, 62 Color bgColor) 63 { 64 int imgw = img.getWidth(null); 65 int imgh = img.getHeight(null); 66 if (isSimpleTranslate(sg)) { 67 return renderImageCopy(sg, img, bgColor, 68 x + sg.transX, y + sg.transY, 69 0, 0, imgw, imgh); 70 } 71 AffineTransform atfm = sg.transform; 72 if ((x | y) != 0) { 73 atfm = new AffineTransform(atfm); 74 atfm.translate(x, y); 75 } 76 transformImage(sg, img, atfm, sg.interpolationType, 77 0, 0, imgw, imgh, bgColor); 78 return true; 79 } 80 81 public boolean copyImage(SunGraphics2D sg, Image img, 82 int dx, int dy, int sx, int sy, int w, int h, 83 Color bgColor) 84 { 85 if (isSimpleTranslate(sg)) { 86 return renderImageCopy(sg, img, bgColor, 87 dx + sg.transX, dy + sg.transY, 88 sx, sy, w, h); 89 } 90 scaleImage(sg, img, dx, dy, (dx + w), (dy + h), 91 sx, sy, (sx + w), (sy + h), bgColor); 92 return true; 93 } 94 95 public boolean scaleImage(SunGraphics2D sg, Image img, int x, int y, 96 int width, int height, 97 Color bgColor) 98 { 99 int imgw = img.getWidth(null); 100 int imgh = img.getHeight(null); 101 // Only accelerate scale if: 102 // - w/h positive values 103 // - sg transform integer translate/identity only 104 // - no bgColor in operation 105 if ((width > 0) && (height > 0) && isSimpleTranslate(sg)) { 106 double dx1 = x + sg.transX; 107 double dy1 = y + sg.transY; 108 double dx2 = dx1 + width; 109 double dy2 = dy1 + height; 110 if (renderImageScale(sg, img, bgColor, sg.interpolationType, 111 0, 0, imgw, imgh, 112 dx1, dy1, dx2, dy2)) 113 { 114 return true; 115 } 116 } 117 118 AffineTransform atfm = sg.transform; 119 if ((x | y) != 0 || width != imgw || height != imgh) { 120 atfm = new AffineTransform(atfm); 121 atfm.translate(x, y); 122 atfm.scale(((double)width)/imgw, ((double)height)/imgh); 123 } 124 transformImage(sg, img, atfm, sg.interpolationType, 125 0, 0, imgw, imgh, bgColor); 126 return true; 127 } 128 129 /* 130 * This method is only called in those circumstances where the 131 * operation has a non-null secondary transform specified. Its 132 * role is to check for various optimizations based on the types 133 * of both the secondary and SG2D transforms and to do some 134 * quick calculations to avoid having to combine the transforms 135 * and/or to call a more generalized method. 136 */ 137 protected void transformImage(SunGraphics2D sg, Image img, int x, int y, 138 AffineTransform extraAT, int interpType) 139 { 140 int txtype = extraAT.getType(); 141 int imgw = img.getWidth(null); 142 int imgh = img.getHeight(null); 143 boolean checkfinalxform; 144 145 if (sg.transformState <= SunGraphics2D.TRANSFORM_ANY_TRANSLATE && 146 (txtype == AffineTransform.TYPE_IDENTITY || 147 txtype == AffineTransform.TYPE_TRANSLATION)) 148 { 149 // First optimization - both are some kind of translate 150 151 // Combine the translations and check if interpolation is necessary. 152 double tx = extraAT.getTranslateX(); 153 double ty = extraAT.getTranslateY(); 154 tx += sg.transform.getTranslateX(); 155 ty += sg.transform.getTranslateY(); 156 int itx = (int) Math.floor(tx + 0.5); 157 int ity = (int) Math.floor(ty + 0.5); 158 if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR || 159 (closeToInteger(itx, tx) && closeToInteger(ity, ty))) 160 { 161 renderImageCopy(sg, img, null, x+itx, y+ity, 0, 0, imgw, imgh); 162 return; 163 } 164 checkfinalxform = false; 165 } else if (sg.transformState <= SunGraphics2D.TRANSFORM_TRANSLATESCALE && 166 ((txtype & (AffineTransform.TYPE_FLIP | 167 AffineTransform.TYPE_MASK_ROTATION | 168 AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)) 169 { 170 // Second optimization - both are some kind of translate or scale 171 172 // Combine the scales and check if interpolation is necessary. 173 174 // Transform source bounds by extraAT, 175 // then translate the bounds again by x, y 176 // then transform the bounds again by sg.transform 177 double coords[] = new double[] { 178 0, 0, imgw, imgh, 179 }; 180 extraAT.transform(coords, 0, coords, 0, 2); 181 coords[0] += x; 182 coords[1] += y; 183 coords[2] += x; 184 coords[3] += y; 185 sg.transform.transform(coords, 0, coords, 0, 2); 186 187 if (tryCopyOrScale(sg, img, 0, 0, imgw, imgh, 188 null, interpType, coords)) 189 { 190 return; 191 } 192 checkfinalxform = false; 193 } else { 194 checkfinalxform = true; 195 } 196 197 // Begin Transform 198 AffineTransform tx = new AffineTransform(sg.transform); 199 tx.translate(x, y); 200 tx.concatenate(extraAT); 201 202 // Do not try any more optimizations if either of the cases 203 // above was tried as we have already verified that the 204 // resulting transform will not simplify. 205 if (checkfinalxform) { 206 // In this case neither of the above simple transform 207 // pairs was found so we will do some final tests on 208 // the final rendering transform which may be the 209 // simple product of two complex transforms. 210 transformImage(sg, img, tx, interpType, 0, 0, imgw, imgh, null); 211 } else { 212 renderImageXform(sg, img, tx, interpType, 0, 0, imgw, imgh, null); 213 } 214 } 215 216 /* 217 * This method is called with a final rendering transform that 218 * has combined all of the information about the Graphics2D 219 * transform attribute with the transformations specified by 220 * the arguments to the drawImage call. 221 * Its role is to see if the combined transform ends up being 222 * acceleratable by either a renderImageCopy or renderImageScale 223 * once all of the math is done. 224 * 225 * Note: The transform supplied here has an origin that is 226 * already adjusted to point to the device location where 227 * the (sx1, sy1) location of the source image should be placed. 228 */ 229 protected void transformImage(SunGraphics2D sg, Image img, 230 AffineTransform tx, int interpType, 231 int sx1, int sy1, int sx2, int sy2, 232 Color bgColor) 233 { 234 // Transform 3 source corners by tx and analyze them 235 // for simplified operations (Copy or Scale). Using 236 // 3 points lets us analyze any kind of transform, 237 // even transforms that involve very tiny amounts of 238 // rotation or skew to see if they degenerate to a 239 // simple scale or copy operation within the allowable 240 // error bounds. 241 // Note that we use (0,0,w,h) instead of (sx1,sy1,sx2,sy2) 242 // because the transform is already translated such that 243 // the origin is where sx1, sy1 should go. 244 double coords[] = new double[6]; 245 /* index: 0 1 2 3 4 5 */ 246 /* coord: (0, 0), (w, h), (0, h) */ 247 coords[2] = sx2 - sx1; 248 coords[3] = coords[5] = sy2 - sy1; 249 tx.transform(coords, 0, coords, 0, 3); 250 // First test if the X coords of the transformed UL 251 // and LL points match and that the Y coords of the 252 // transformed LR and LL points also match. 253 // If they do then it is a "rectilinear" transform and 254 // tryCopyOrScale will make sure it is upright and 255 // integer-based. 256 if (Math.abs(coords[0] - coords[4]) < MAX_TX_ERROR && 257 Math.abs(coords[3] - coords[5]) < MAX_TX_ERROR && 258 tryCopyOrScale(sg, img, sx1, sy1, sx2, sy2, 259 bgColor, interpType, coords)) 260 { 261 return; 262 } 263 264 renderImageXform(sg, img, tx, interpType, sx1, sy1, sx2, sy2, bgColor); 265 } 266 267 /* 268 * Check the bounding coordinates of the transformed source 269 * image to see if they fall on integer coordinates such 270 * that they will cause no interpolation anomalies if we 271 * use our simplified Blit or ScaledBlit operations instead 272 * of a full transform operation. 273 */ 274 protected boolean tryCopyOrScale(SunGraphics2D sg, 275 Image img, 276 int sx1, int sy1, 277 int sx2, int sy2, 278 Color bgColor, int interpType, 279 double coords[]) 280 { 281 double dx1 = coords[0]; 282 double dy1 = coords[1]; 283 double dx2 = coords[2]; 284 double dy2 = coords[3]; 285 double dw = dx2 - dx1; 286 double dh = dy2 - dy1; 287 288 /* If any of the destination coordinates exceed the integer range, 289 * then the calculations performed in calls made here cannot be 290 * guaranteed to be correct, or to converge (terminate). 291 * So return out of here, deferring to code that can handle this. 292 */ 293 if (dx1 < Integer.MIN_VALUE || dx1 > Integer.MAX_VALUE || 294 dy1 < Integer.MIN_VALUE || dy1 > Integer.MAX_VALUE || 295 dx2 < Integer.MIN_VALUE || dx2 > Integer.MAX_VALUE || 296 dy2 < Integer.MIN_VALUE || dy2 > Integer.MAX_VALUE) 297 { 298 return false; 299 } 300 301 // First check if width and height are very close to img w&h. 302 if (closeToInteger(sx2-sx1, dw) && closeToInteger(sy2-sy1, dh)) { 303 // Round location to nearest pixel and then test 304 // if it will cause interpolation anomalies. 305 int idx = (int) Math.floor(dx1 + 0.5); 306 int idy = (int) Math.floor(dy1 + 0.5); 307 if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR || 308 (closeToInteger(idx, dx1) && closeToInteger(idy, dy1))) 309 { 310 renderImageCopy(sg, img, bgColor, 311 idx, idy, 312 sx1, sy1, sx2-sx1, sy2-sy1); 313 return true; 314 } 315 } 316 // (For now) We can only use our ScaledBlits if the image 317 // is upright (i.e. dw & dh both > 0) 318 if (dw > 0 && dh > 0) { 319 if (renderImageScale(sg, img, bgColor, interpType, 320 sx1, sy1, sx2, sy2, 321 dx1, dy1, dx2, dy2)) 322 { 323 return true; 324 } 325 } 326 return false; 327 } 328 329 /** 330 * Return a non-accelerated BufferedImage of the requested type with the 331 * indicated subimage of the original image located at 0,0 in the new image. 332 * If a bgColor is supplied, composite the original image over that color 333 * with a SrcOver operation, otherwise make a SrcNoEa copy. 334 * <p> 335 * Returned BufferedImage is not accelerated for two reasons: 336 * <ul> 337 * <li> Types of the image and surface are predefined, because these types 338 * correspond to the TransformHelpers, which we know we have. And 339 * acceleration can change the type of the surface 340 * <li> Image will be used only once and acceleration caching wouldn't help 341 * </ul> 342 */ 343 private BufferedImage makeBufferedImage(Image img, Color bgColor, int type, 344 int sx1, int sy1, int sx2, int sy2) 345 { 346 final int width = sx2 - sx1; 347 final int height = sy2 - sy1; 348 final BufferedImage bimg = new BufferedImage(width, height, type); 349 final SunGraphics2D g2d = (SunGraphics2D) bimg.createGraphics(); 350 g2d.setComposite(AlphaComposite.Src); 351 bimg.setAccelerationPriority(0); 352 if (bgColor != null) { 353 g2d.setColor(bgColor); 354 g2d.fillRect(0, 0, width, height); 355 g2d.setComposite(AlphaComposite.SrcOver); 356 } 357 g2d.copyImage(img, 0, 0, sx1, sy1, width, height, null, null); 358 g2d.dispose(); 359 return bimg; 360 } 361 362 protected void renderImageXform(SunGraphics2D sg, Image img, 363 AffineTransform tx, int interpType, 364 int sx1, int sy1, int sx2, int sy2, 365 Color bgColor) 366 { 367 final AffineTransform itx; 368 try { 369 itx = tx.createInverse(); 370 } catch (final NoninvertibleTransformException ignored) { 371 // Non-invertible transform means no output 372 return; 373 } 374 375 /* 376 * Find the maximum bounds on the destination that will be 377 * affected by the transformed source. First, transform all 378 * four corners of the source and then min and max the resulting 379 * destination coordinates of the transformed corners. 380 * Note that tx already has the offset to sx1,sy1 accounted 381 * for so we use the box (0, 0, sx2-sx1, sy2-sy1) as the 382 * source coordinates. 383 */ 384 final double[] coords = new double[8]; 385 /* corner: UL UR LL LR */ 386 /* index: 0 1 2 3 4 5 6 7 */ 387 /* coord: (0, 0), (w, 0), (0, h), (w, h) */ 388 coords[2] = coords[6] = sx2 - sx1; 389 coords[5] = coords[7] = sy2 - sy1; 390 tx.transform(coords, 0, coords, 0, 4); 391 double ddx1, ddy1, ddx2, ddy2; 392 ddx1 = ddx2 = coords[0]; 393 ddy1 = ddy2 = coords[1]; 394 for (int i = 2; i < coords.length; i += 2) { 395 double d = coords[i]; 396 if (ddx1 > d) ddx1 = d; 397 else if (ddx2 < d) ddx2 = d; 398 d = coords[i+1]; 399 if (ddy1 > d) ddy1 = d; 400 else if (ddy2 < d) ddy2 = d; 401 } 402 403 Region clip = sg.getCompClip(); 404 final int dx1 = Math.max((int) Math.floor(ddx1), clip.getLoX()); 405 final int dy1 = Math.max((int) Math.floor(ddy1), clip.getLoY()); 406 final int dx2 = Math.min((int) Math.ceil(ddx2), clip.getHiX()); 407 final int dy2 = Math.min((int) Math.ceil(ddy2), clip.getHiY()); 408 if (dx2 <= dx1 || dy2 <= dy1) { 409 // empty destination means no output 410 return; 411 } 412 413 final SurfaceData dstData = sg.surfaceData; 414 SurfaceData srcData = dstData.getSourceSurfaceData(img, 415 SunGraphics2D.TRANSFORM_GENERIC, 416 sg.imageComp, 417 bgColor); 418 419 if (srcData == null) { 420 img = getBufferedImage(img); 421 srcData = dstData.getSourceSurfaceData(img, 422 SunGraphics2D.TRANSFORM_GENERIC, 423 sg.imageComp, 424 bgColor); 425 if (srcData == null) { 426 // REMIND: Is this correct? Can this happen? 427 return; 428 } 429 } 430 431 if (isBgOperation(srcData, bgColor)) { 432 // We cannot perform bg operations during transform so make 433 // a temp image with the appropriate background based on 434 // background alpha value and work from there. If background 435 // alpha is opaque use INT_RGB else use INT_ARGB so that we 436 // will not lose translucency of background. 437 438 int bgAlpha = bgColor.getAlpha(); 439 int type = ((bgAlpha == 255) 440 ? BufferedImage.TYPE_INT_RGB 441 : BufferedImage.TYPE_INT_ARGB); 442 img = makeBufferedImage(img, bgColor, type, sx1, sy1, sx2, sy2); 443 // Temp image has appropriate subimage at 0,0 now. 444 sx2 -= sx1; 445 sy2 -= sy1; 446 sx1 = sy1 = 0; 447 448 srcData = dstData.getSourceSurfaceData(img, 449 SunGraphics2D.TRANSFORM_GENERIC, 450 sg.imageComp, 451 bgColor); 452 } 453 454 SurfaceType srcType = srcData.getSurfaceType(); 455 TransformHelper helper = TransformHelper.getFromCache(srcType); 456 457 if (helper == null) { 458 /* We have no helper for this source image type. 459 * But we know that we do have helpers for both RGB and ARGB, 460 * so convert to one of those types depending on transparency. 461 * ARGB_PRE might be a better choice if the source image has 462 * alpha, but it may cause some recursion here since we only 463 * tend to have converters that convert to ARGB. 464 */ 465 int type = ((srcData.getTransparency() == Transparency.OPAQUE) 466 ? BufferedImage.TYPE_INT_RGB 467 : BufferedImage.TYPE_INT_ARGB); 468 img = makeBufferedImage(img, null, type, sx1, sy1, sx2, sy2); 469 // Temp image has appropriate subimage at 0,0 now. 470 sx2 -= sx1; 471 sy2 -= sy1; 472 sx1 = sy1 = 0; 473 474 srcData = dstData.getSourceSurfaceData(img, 475 SunGraphics2D.TRANSFORM_GENERIC, 476 sg.imageComp, 477 null); 478 srcType = srcData.getSurfaceType(); 479 helper = TransformHelper.getFromCache(srcType); 480 // assert(helper != null); 481 } 482 483 SurfaceType dstType = dstData.getSurfaceType(); 484 if (sg.compositeState <= SunGraphics2D.COMP_ALPHA) { 485 /* NOTE: We either have, or we can make, 486 * a MaskBlit for any alpha composite type 487 */ 488 MaskBlit maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre, 489 sg.imageComp, dstType); 490 491 /* NOTE: We can only use the native TransformHelper 492 * func to go directly to the dest if both the helper 493 * and the MaskBlit are native. 494 * All helpers are native at this point, but some MaskBlit 495 * objects are implemented in Java, so we need to check. 496 */ 497 if (maskblit.getNativePrim() != 0) { 498 // We can render directly. 499 helper.Transform(maskblit, srcData, dstData, 500 sg.composite, clip, 501 itx, interpType, 502 sx1, sy1, sx2, sy2, 503 dx1, dy1, dx2, dy2, 504 null, 0, 0); 505 return; 506 } 507 } 508 509 // We need to transform to a temp image and then copy 510 // just the pieces that are valid data to the dest. 511 final int w = dx2 - dx1; 512 final int h = dy2 - dy1; 513 BufferedImage tmpimg = new BufferedImage(w, h, 514 BufferedImage.TYPE_INT_ARGB_PRE); 515 SurfaceData tmpData = SurfaceData.getPrimarySurfaceData(tmpimg); 516 SurfaceType tmpType = tmpData.getSurfaceType(); 517 MaskBlit tmpmaskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre, 518 CompositeType.SrcNoEa, 519 tmpType); 520 /* 521 * The helper function fills a temporary edges buffer 522 * for us with the bounding coordinates of each scanline 523 * in the following format: 524 * 525 * edges[0, 1] = [top y, bottom y) 526 * edges[2, 3] = [left x, right x) of top row 527 * ... 528 * edges[h*2, h*2+1] = [left x, right x) of bottom row 529 * 530 * all coordinates in the edges array will be relative to dx1, dy1 531 * 532 * edges thus has to be h*2+2 in length 533 */ 534 final int[] edges = new int[h * 2 + 2]; 535 // It is important that edges[0]=edges[1]=0 when we call 536 // Transform in case it must return early and we would 537 // not want to render anything on an error condition. 538 helper.Transform(tmpmaskblit, srcData, tmpData, 539 AlphaComposite.Src, null, 540 itx, interpType, 541 sx1, sy1, sx2, sy2, 542 0, 0, w, h, 543 edges, dx1, dy1); 544 545 final Region region = Region.getInstance(dx1, dy1, dx2, dy2, edges); 546 clip = clip.getIntersection(region); 547 548 /* NOTE: We either have, or we can make, 549 * a Blit for any composite type, even Custom 550 */ 551 final Blit blit = Blit.getFromCache(tmpType, sg.imageComp, dstType); 552 blit.Blit(tmpData, dstData, sg.composite, clip, 0, 0, dx1, dy1, w, h); 553 } 554 555 // Render an image using only integer translation 556 // (no scale or transform or sub-pixel interpolated translations). 557 protected boolean renderImageCopy(SunGraphics2D sg, Image img, 558 Color bgColor, 559 int dx, int dy, 560 int sx, int sy, 561 int w, int h) 562 { 563 Region clip = sg.getCompClip(); 564 SurfaceData dstData = sg.surfaceData; 565 566 int attempts = 0; 567 // Loop up to twice through; this gives us a chance to 568 // revalidate the surfaceData objects in case of an exception 569 // and try it once more 570 while (true) { 571 SurfaceData srcData = 572 dstData.getSourceSurfaceData(img, 573 SunGraphics2D.TRANSFORM_ISIDENT, 574 sg.imageComp, 575 bgColor); 576 if (srcData == null) { 577 return false; 578 } 579 580 try { 581 SurfaceType srcType = srcData.getSurfaceType(); 582 SurfaceType dstType = dstData.getSurfaceType(); 583 blitSurfaceData(sg, clip, 584 srcData, dstData, srcType, dstType, 585 sx, sy, dx, dy, w, h, bgColor); 586 return true; 587 } catch (NullPointerException e) { 588 if (!(SurfaceData.isNull(dstData) || 589 SurfaceData.isNull(srcData))) 590 { 591 // Something else caused the exception, throw it... 592 throw e; 593 } 594 return false; 595 // NOP if we have been disposed 596 } catch (InvalidPipeException e) { 597 // Always catch the exception; try this a couple of times 598 // and fail silently if the system is not yet ready to 599 // revalidate the source or dest surfaceData objects. 600 ++attempts; 601 clip = sg.getCompClip(); // ensures sg.surfaceData is valid 602 dstData = sg.surfaceData; 603 if (SurfaceData.isNull(dstData) || 604 SurfaceData.isNull(srcData) || (attempts > 1)) 605 { 606 return false; 607 } 608 } 609 } 610 } 611 612 // Render an image using only integer scaling (no transform). 613 protected boolean renderImageScale(SunGraphics2D sg, Image img, 614 Color bgColor, int interpType, 615 int sx1, int sy1, 616 int sx2, int sy2, 617 double dx1, double dy1, 618 double dx2, double dy2) 619 { 620 // Currently only NEAREST_NEIGHBOR interpolation is implemented 621 // for ScaledBlit operations. 622 if (interpType != AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { 623 return false; 624 } 625 626 Region clip = sg.getCompClip(); 627 SurfaceData dstData = sg.surfaceData; 628 629 int attempts = 0; 630 // Loop up to twice through; this gives us a chance to 631 // revalidate the surfaceData objects in case of an exception 632 // and try it once more 633 while (true) { 634 SurfaceData srcData = 635 dstData.getSourceSurfaceData(img, 636 SunGraphics2D.TRANSFORM_TRANSLATESCALE, 637 sg.imageComp, 638 bgColor); 639 640 if (srcData == null || isBgOperation(srcData, bgColor)) { 641 return false; 642 } 643 644 try { 645 SurfaceType srcType = srcData.getSurfaceType(); 646 SurfaceType dstType = dstData.getSurfaceType(); 647 return scaleSurfaceData(sg, clip, 648 srcData, dstData, srcType, dstType, 649 sx1, sy1, sx2, sy2, 650 dx1, dy1, dx2, dy2); 651 } catch (NullPointerException e) { 652 if (!SurfaceData.isNull(dstData)) { 653 // Something else caused the exception, throw it... 654 throw e; 655 } 656 return false; 657 // NOP if we have been disposed 658 } catch (InvalidPipeException e) { 659 // Always catch the exception; try this a couple of times 660 // and fail silently if the system is not yet ready to 661 // revalidate the source or dest surfaceData objects. 662 ++attempts; 663 clip = sg.getCompClip(); // ensures sg.surfaceData is valid 664 dstData = sg.surfaceData; 665 if (SurfaceData.isNull(dstData) || 666 SurfaceData.isNull(srcData) || (attempts > 1)) 667 { 668 return false; 669 } 670 } 671 } 672 } 673 674 public boolean scaleImage(SunGraphics2D sg, Image img, 675 int dx1, int dy1, int dx2, int dy2, 676 int sx1, int sy1, int sx2, int sy2, 677 Color bgColor) 678 { 679 int srcW, srcH, dstW, dstH; 680 int srcX, srcY, dstX, dstY; 681 boolean srcWidthFlip = false; 682 boolean srcHeightFlip = false; 683 boolean dstWidthFlip = false; 684 boolean dstHeightFlip = false; 685 686 if (sx2 > sx1) { 687 srcW = sx2 - sx1; 688 srcX = sx1; 689 } else { 690 srcWidthFlip = true; 691 srcW = sx1 - sx2; 692 srcX = sx2; 693 } 694 if (sy2 > sy1) { 695 srcH = sy2-sy1; 696 srcY = sy1; 697 } else { 698 srcHeightFlip = true; 699 srcH = sy1-sy2; 700 srcY = sy2; 701 } 702 if (dx2 > dx1) { 703 dstW = dx2 - dx1; 704 dstX = dx1; 705 } else { 706 dstW = dx1 - dx2; 707 dstWidthFlip = true; 708 dstX = dx2; 709 } 710 if (dy2 > dy1) { 711 dstH = dy2 - dy1; 712 dstY = dy1; 713 } else { 714 dstH = dy1 - dy2; 715 dstHeightFlip = true; 716 dstY = dy2; 717 } 718 if (srcW <= 0 || srcH <= 0) { 719 return true; 720 } 721 // Only accelerate scale if it does not involve a flip or transform 722 if ((srcWidthFlip == dstWidthFlip) && 723 (srcHeightFlip == dstHeightFlip) && 724 isSimpleTranslate(sg)) 725 { 726 double ddx1 = dstX + sg.transX; 727 double ddy1 = dstY + sg.transY; 728 double ddx2 = ddx1 + dstW; 729 double ddy2 = ddy1 + dstH; 730 if (renderImageScale(sg, img, bgColor, sg.interpolationType, 731 srcX, srcY, srcX+srcW, srcY+srcH, 732 ddx1, ddy1, ddx2, ddy2)) 733 { 734 return true; 735 } 736 } 737 738 AffineTransform atfm = new AffineTransform(sg.transform); 739 atfm.translate(dx1, dy1); 740 double m00 = (double)(dx2-dx1)/(sx2-sx1); 741 double m11 = (double)(dy2-dy1)/(sy2-sy1); 742 atfm.scale(m00, m11); 743 atfm.translate(srcX-sx1, srcY-sy1); 744 745 final double scaleX = SurfaceManager.getImageScaleX(img); 746 final double scaleY = SurfaceManager.getImageScaleY(img); 747 final int imgW = (int) Math.ceil(img.getWidth(null) * scaleX); 748 final int imgH = (int) Math.ceil(img.getHeight(null) * scaleY); 749 srcW += srcX; 750 srcH += srcY; 751 // Make sure we are not out of bounds 752 if (srcW > imgW) { 753 srcW = imgW; 754 } 755 if (srcH > imgH) { 756 srcH = imgH; 757 } 758 if (srcX < 0) { 759 atfm.translate(-srcX, 0); 760 srcX = 0; 761 } 762 if (srcY < 0) { 763 atfm.translate(0, -srcY); 764 srcY = 0; 765 } 766 if (srcX >= srcW || srcY >= srcH) { 767 return true; 768 } 769 // Note: src[WH] are currently the right and bottom coordinates. 770 // The following two lines would adjust src[WH] back to being 771 // dimensions. 772 // srcW -= srcX; 773 // srcH -= srcY; 774 // Since transformImage needs right and bottom coords we will 775 // omit this adjustment. 776 777 transformImage(sg, img, atfm, sg.interpolationType, 778 srcX, srcY, srcW, srcH, bgColor); 779 return true; 780 } 781 782 /** 783 ** Utilities 784 ** The following methods are used by the public methods above 785 ** for performing various operations 786 **/ 787 788 /* 789 * This constant represents a tradeoff between the 790 * need to make sure that image transformations are 791 * "very close" to integer device coordinates before 792 * we decide to use an integer scale or copy operation 793 * as a substitute and the fact that roundoff errors 794 * in AffineTransforms are frequently introduced by 795 * performing multiple sequential operations on them. 796 * 797 * The evaluation of bug 4990624 details the potential 798 * for this error cutoff to result in display anomalies 799 * in different types of image operations and how this 800 * value represents a good compromise here. 801 */ 802 private static final double MAX_TX_ERROR = .0001; 803 804 public static boolean closeToInteger(int i, double d) { 805 return (Math.abs(d-i) < MAX_TX_ERROR); 806 } 807 808 public static boolean isSimpleTranslate(SunGraphics2D sg) { 809 int ts = sg.transformState; 810 if (ts <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) { 811 // Integer translates are always "simple" 812 return true; 813 } 814 if (ts >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) { 815 // Scales and beyond are always "not simple" 816 return false; 817 } 818 // non-integer translates are only simple when not interpolating 819 if (sg.interpolationType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { 820 return true; 821 } 822 return false; 823 } 824 825 protected static boolean isBgOperation(SurfaceData srcData, Color bgColor) { 826 // If we cannot get the srcData, then cannot assume anything about 827 // the image 828 return ((srcData == null) || 829 ((bgColor != null) && 830 (srcData.getTransparency() != Transparency.OPAQUE))); 831 } 832 833 protected BufferedImage getBufferedImage(Image img) { 834 if (img instanceof BufferedImage) { 835 return (BufferedImage)img; 836 } 837 // Must be VolatileImage; get BufferedImage representation 838 return ((VolatileImage)img).getSnapshot(); 839 } 840 841 /* 842 * Return the color model to be used with this BufferedImage and 843 * transform. 844 */ 845 private ColorModel getTransformColorModel(SunGraphics2D sg, 846 BufferedImage bImg, 847 AffineTransform tx) { 848 ColorModel cm = bImg.getColorModel(); 849 ColorModel dstCM = cm; 850 851 if (tx.isIdentity()) { 852 return dstCM; 853 } 854 int type = tx.getType(); 855 boolean needTrans = 856 ((type & (AffineTransform.TYPE_MASK_ROTATION | 857 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 858 if (! needTrans && 859 type != AffineTransform.TYPE_TRANSLATION && 860 type != AffineTransform.TYPE_IDENTITY) 861 { 862 double[] mtx = new double[4]; 863 tx.getMatrix(mtx); 864 // Check out the matrix. A non-integral scale will force ARGB 865 // since the edge conditions cannot be guaranteed. 866 needTrans = (mtx[0] != (int)mtx[0] || mtx[3] != (int)mtx[3]); 867 } 868 869 if (sg.renderHint != SunHints.INTVAL_RENDER_QUALITY) { 870 if (cm instanceof IndexColorModel) { 871 Raster raster = bImg.getRaster(); 872 IndexColorModel icm = (IndexColorModel) cm; 873 // Just need to make sure that we have a transparent pixel 874 if (needTrans && cm.getTransparency() == Transparency.OPAQUE) { 875 // Fix 4221407 876 if (raster instanceof sun.awt.image.BytePackedRaster) { 877 dstCM = ColorModel.getRGBdefault(); 878 } 879 else { 880 double[] matrix = new double[6]; 881 tx.getMatrix(matrix); 882 if (matrix[1] == 0. && matrix[2] ==0. 883 && matrix[4] == 0. && matrix[5] == 0.) { 884 // Only scaling so do not need to create 885 } 886 else { 887 int mapSize = icm.getMapSize(); 888 if (mapSize < 256) { 889 int[] cmap = new int[mapSize+1]; 890 icm.getRGBs(cmap); 891 cmap[mapSize] = 0x0000; 892 dstCM = new 893 IndexColorModel(icm.getPixelSize(), 894 mapSize+1, 895 cmap, 0, true, mapSize, 896 DataBuffer.TYPE_BYTE); 897 } 898 else { 899 dstCM = ColorModel.getRGBdefault(); 900 } 901 } /* if (matrix[0] < 1.f ...) */ 902 } /* raster instanceof sun.awt.image.BytePackedRaster */ 903 } /* if (cm.getTransparency() == cm.OPAQUE) */ 904 } /* if (cm instanceof IndexColorModel) */ 905 else if (needTrans && cm.getTransparency() == Transparency.OPAQUE) { 906 // Need a bitmask transparency 907 // REMIND: for now, use full transparency since no loops 908 // for bitmask 909 dstCM = ColorModel.getRGBdefault(); 910 } 911 } /* if (sg.renderHint == RENDER_QUALITY) */ 912 else { 913 914 if (cm instanceof IndexColorModel || 915 (needTrans && cm.getTransparency() == Transparency.OPAQUE)) 916 { 917 // Need a bitmask transparency 918 // REMIND: for now, use full transparency since no loops 919 // for bitmask 920 dstCM = ColorModel.getRGBdefault(); 921 } 922 } 923 924 return dstCM; 925 } 926 927 protected void blitSurfaceData(SunGraphics2D sg, 928 Region clipRegion, 929 SurfaceData srcData, 930 SurfaceData dstData, 931 SurfaceType srcType, 932 SurfaceType dstType, 933 int sx, int sy, int dx, int dy, 934 int w, int h, 935 Color bgColor) 936 { 937 if (w <= 0 || h <= 0) { 938 /* 939 * Fix for bugid 4783274 - BlitBg throws an exception for 940 * a particular set of anomalous parameters. 941 * REMIND: The native loops do proper clipping and would 942 * detect this situation themselves, but the Java loops 943 * all seem to trust their parameters a little too well 944 * to the point where they will try to process a negative 945 * area of pixels and throw exceptions. The real fix is 946 * to modify the Java loops to do proper clipping so that 947 * they can deal with negative dimensions as well as 948 * improperly large dimensions, but that fix is too risky 949 * to integrate for Mantis at this point. In the meantime 950 * eliminating the negative or zero dimensions here is 951 * "correct" and saves them from some nasty exceptional 952 * conditions, one of which is the test case of 4783274. 953 */ 954 return; 955 } 956 CompositeType comp = sg.imageComp; 957 if (CompositeType.SrcOverNoEa.equals(comp) && 958 (srcData.getTransparency() == Transparency.OPAQUE || 959 (bgColor != null && 960 bgColor.getTransparency() == Transparency.OPAQUE))) 961 { 962 comp = CompositeType.SrcNoEa; 963 } 964 if (srcData == dstData && sx == dx && sy == dy 965 && CompositeType.SrcNoEa.equals(comp)) { 966 // Performance optimization. We skip the Blit/BlitBG if we know that 967 // it will be noop. 968 return; 969 } 970 if (!isBgOperation(srcData, bgColor)) { 971 Blit blit = Blit.getFromCache(srcType, comp, dstType); 972 blit.Blit(srcData, dstData, sg.composite, clipRegion, 973 sx, sy, dx, dy, w, h); 974 } else { 975 BlitBg blit = BlitBg.getFromCache(srcType, comp, dstType); 976 blit.BlitBg(srcData, dstData, sg.composite, clipRegion, 977 bgColor.getRGB(), sx, sy, dx, dy, w, h); 978 } 979 } 980 981 protected boolean scaleSurfaceData(SunGraphics2D sg, 982 Region clipRegion, 983 SurfaceData srcData, 984 SurfaceData dstData, 985 SurfaceType srcType, 986 SurfaceType dstType, 987 int sx1, int sy1, 988 int sx2, int sy2, 989 double dx1, double dy1, 990 double dx2, double dy2) 991 { 992 CompositeType comp = sg.imageComp; 993 if (CompositeType.SrcOverNoEa.equals(comp) && 994 (srcData.getTransparency() == Transparency.OPAQUE)) 995 { 996 comp = CompositeType.SrcNoEa; 997 } 998 999 ScaledBlit blit = ScaledBlit.getFromCache(srcType, comp, dstType); 1000 if (blit != null) { 1001 blit.Scale(srcData, dstData, sg.composite, clipRegion, 1002 sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); 1003 return true; 1004 } 1005 return false; 1006 } 1007 1008 protected static boolean imageReady(ToolkitImage sunimg, 1009 ImageObserver observer) 1010 { 1011 if (sunimg.hasError()) { 1012 if (observer != null) { 1013 observer.imageUpdate(sunimg, 1014 ImageObserver.ERROR|ImageObserver.ABORT, 1015 -1, -1, -1, -1); 1016 } 1017 return false; 1018 } 1019 return true; 1020 } 1021 1022 public boolean copyImage(SunGraphics2D sg, Image img, 1023 int x, int y, 1024 Color bgColor, 1025 ImageObserver observer) { 1026 if (!(img instanceof ToolkitImage)) { 1027 return copyImage(sg, img, x, y, bgColor); 1028 } else { 1029 ToolkitImage sunimg = (ToolkitImage)img; 1030 if (!imageReady(sunimg, observer)) { 1031 return false; 1032 } 1033 ImageRepresentation ir = sunimg.getImageRep(); 1034 return ir.drawToBufImage(sg, sunimg, x, y, bgColor, observer); 1035 } 1036 } 1037 1038 public boolean copyImage(SunGraphics2D sg, Image img, 1039 int dx, int dy, int sx, int sy, int w, int h, 1040 Color bgColor, 1041 ImageObserver observer) { 1042 if (!(img instanceof ToolkitImage)) { 1043 return copyImage(sg, img, dx, dy, sx, sy, w, h, bgColor); 1044 } else { 1045 ToolkitImage sunimg = (ToolkitImage)img; 1046 if (!imageReady(sunimg, observer)) { 1047 return false; 1048 } 1049 ImageRepresentation ir = sunimg.getImageRep(); 1050 return ir.drawToBufImage(sg, sunimg, 1051 dx, dy, (dx + w), (dy + h), 1052 sx, sy, (sx + w), (sy + h), 1053 bgColor, observer); 1054 } 1055 } 1056 1057 public boolean scaleImage(SunGraphics2D sg, Image img, 1058 int x, int y, 1059 int width, int height, 1060 Color bgColor, 1061 ImageObserver observer) { 1062 if (!(img instanceof ToolkitImage)) { 1063 return scaleImage(sg, img, x, y, width, height, bgColor); 1064 } else { 1065 ToolkitImage sunimg = (ToolkitImage)img; 1066 if (!imageReady(sunimg, observer)) { 1067 return false; 1068 } 1069 ImageRepresentation ir = sunimg.getImageRep(); 1070 return ir.drawToBufImage(sg, sunimg, x, y, width, height, bgColor, 1071 observer); 1072 } 1073 } 1074 1075 public boolean scaleImage(SunGraphics2D sg, Image img, 1076 int dx1, int dy1, int dx2, int dy2, 1077 int sx1, int sy1, int sx2, int sy2, 1078 Color bgColor, 1079 ImageObserver observer) { 1080 if (!(img instanceof ToolkitImage)) { 1081 return scaleImage(sg, img, dx1, dy1, dx2, dy2, 1082 sx1, sy1, sx2, sy2, bgColor); 1083 } else { 1084 ToolkitImage sunimg = (ToolkitImage)img; 1085 if (!imageReady(sunimg, observer)) { 1086 return false; 1087 } 1088 ImageRepresentation ir = sunimg.getImageRep(); 1089 return ir.drawToBufImage(sg, sunimg, dx1, dy1, dx2, dy2, 1090 sx1, sy1, sx2, sy2, bgColor, observer); 1091 } 1092 } 1093 1094 public boolean transformImage(SunGraphics2D sg, Image img, 1095 AffineTransform atfm, 1096 ImageObserver observer) { 1097 if (!(img instanceof ToolkitImage)) { 1098 transformImage(sg, img, 0, 0, atfm, sg.interpolationType); 1099 return true; 1100 } else { 1101 ToolkitImage sunimg = (ToolkitImage)img; 1102 if (!imageReady(sunimg, observer)) { 1103 return false; 1104 } 1105 ImageRepresentation ir = sunimg.getImageRep(); 1106 return ir.drawToBufImage(sg, sunimg, atfm, observer); 1107 } 1108 } 1109 1110 public void transformImage(SunGraphics2D sg, BufferedImage img, 1111 BufferedImageOp op, int x, int y) 1112 { 1113 if (op != null) { 1114 if (op instanceof AffineTransformOp) { 1115 AffineTransformOp atop = (AffineTransformOp) op; 1116 transformImage(sg, img, x, y, 1117 atop.getTransform(), 1118 atop.getInterpolationType()); 1119 return; 1120 } else { 1121 img = op.filter(img, null); 1122 } 1123 } 1124 copyImage(sg, img, x, y, null); 1125 } 1126 }