1 /* 2 * Copyright (c) 2001, 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 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 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 Region clip = sg.getCompClip(); 368 SurfaceData dstData = sg.surfaceData; 369 SurfaceData srcData = dstData.getSourceSurfaceData(img, 370 SunGraphics2D.TRANSFORM_GENERIC, 371 sg.imageComp, 372 bgColor); 373 374 if (srcData == null) { 375 img = getBufferedImage(img); 376 srcData = dstData.getSourceSurfaceData(img, 377 SunGraphics2D.TRANSFORM_GENERIC, 378 sg.imageComp, 379 bgColor); 380 if (srcData == null) { 381 // REMIND: Is this correct? Can this happen? 382 return; 383 } 384 } 385 386 if (isBgOperation(srcData, bgColor)) { 387 // We cannot perform bg operations during transform so make 388 // an opaque temp image with the appropriate background 389 // and work from there. 390 img = makeBufferedImage(img, bgColor, BufferedImage.TYPE_INT_RGB, 391 sx1, sy1, sx2, sy2); 392 // Temp image has appropriate subimage at 0,0 now. 393 sx2 -= sx1; 394 sy2 -= sy1; 395 sx1 = sy1 = 0; 396 397 srcData = dstData.getSourceSurfaceData(img, 398 SunGraphics2D.TRANSFORM_GENERIC, 399 sg.imageComp, 400 bgColor); 401 } 402 403 SurfaceType srcType = srcData.getSurfaceType(); 404 TransformHelper helper = TransformHelper.getFromCache(srcType); 405 406 if (helper == null) { 407 /* We have no helper for this source image type. 408 * But we know that we do have helpers for both RGB and ARGB, 409 * so convert to one of those types depending on transparency. 410 * ARGB_PRE might be a better choice if the source image has 411 * alpha, but it may cause some recursion here since we only 412 * tend to have converters that convert to ARGB. 413 */ 414 int type = ((srcData.getTransparency() == Transparency.OPAQUE) 415 ? BufferedImage.TYPE_INT_RGB 416 : BufferedImage.TYPE_INT_ARGB); 417 img = makeBufferedImage(img, null, type, sx1, sy1, sx2, sy2); 418 // Temp image has appropriate subimage at 0,0 now. 419 sx2 -= sx1; 420 sy2 -= sy1; 421 sx1 = sy1 = 0; 422 423 srcData = dstData.getSourceSurfaceData(img, 424 SunGraphics2D.TRANSFORM_GENERIC, 425 sg.imageComp, 426 null); 427 srcType = srcData.getSurfaceType(); 428 helper = TransformHelper.getFromCache(srcType); 429 // assert(helper != null); 430 } 431 432 AffineTransform itx; 433 try { 434 itx = tx.createInverse(); 435 } catch (NoninvertibleTransformException e) { 436 // Non-invertible transform means no output 437 return; 438 } 439 440 /* 441 * Find the maximum bounds on the destination that will be 442 * affected by the transformed source. First, transform all 443 * four corners of the source and then min and max the resulting 444 * destination coordinates of the transformed corners. 445 * Note that tx already has the offset to sx1,sy1 accounted 446 * for so we use the box (0, 0, sx2-sx1, sy2-sy1) as the 447 * source coordinates. 448 */ 449 double coords[] = new double[8]; 450 /* corner: UL UR LL LR */ 451 /* index: 0 1 2 3 4 5 6 7 */ 452 /* coord: (0, 0), (w, 0), (0, h), (w, h) */ 453 coords[2] = coords[6] = sx2 - sx1; 454 coords[5] = coords[7] = sy2 - sy1; 455 tx.transform(coords, 0, coords, 0, 4); 456 double ddx1, ddy1, ddx2, ddy2; 457 ddx1 = ddx2 = coords[0]; 458 ddy1 = ddy2 = coords[1]; 459 for (int i = 2; i < coords.length; i += 2) { 460 double d = coords[i]; 461 if (ddx1 > d) ddx1 = d; 462 else if (ddx2 < d) ddx2 = d; 463 d = coords[i+1]; 464 if (ddy1 > d) ddy1 = d; 465 else if (ddy2 < d) ddy2 = d; 466 } 467 int dx1 = (int) Math.floor(ddx1); 468 int dy1 = (int) Math.floor(ddy1); 469 int dx2 = (int) Math.ceil(ddx2); 470 int dy2 = (int) Math.ceil(ddy2); 471 472 SurfaceType dstType = dstData.getSurfaceType(); 473 MaskBlit maskblit; 474 Blit blit; 475 if (sg.compositeState <= SunGraphics2D.COMP_ALPHA) { 476 /* NOTE: We either have, or we can make, 477 * a MaskBlit for any alpha composite type 478 */ 479 maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre, 480 sg.imageComp, 481 dstType); 482 483 /* NOTE: We can only use the native TransformHelper 484 * func to go directly to the dest if both the helper 485 * and the MaskBlit are native. 486 * All helpers are native at this point, but some MaskBlit 487 * objects are implemented in Java, so we need to check. 488 */ 489 if (maskblit.getNativePrim() != 0) { 490 // We can render directly. 491 helper.Transform(maskblit, srcData, dstData, 492 sg.composite, clip, 493 itx, interpType, 494 sx1, sy1, sx2, sy2, 495 dx1, dy1, dx2, dy2, 496 null, 0, 0); 497 return; 498 } 499 blit = null; 500 } else { 501 /* NOTE: We either have, or we can make, 502 * a Blit for any composite type, even Custom 503 */ 504 maskblit = null; 505 blit = Blit.getFromCache(SurfaceType.IntArgbPre, 506 sg.imageComp, 507 dstType); 508 } 509 510 // We need to transform to a temp image and then copy 511 // just the pieces that are valid data to the dest. 512 BufferedImage tmpimg = new BufferedImage(dx2-dx1, dy2-dy1, 513 BufferedImage.TYPE_INT_ARGB); 514 SurfaceData tmpData = SurfaceData.getPrimarySurfaceData(tmpimg); 515 SurfaceType tmpType = tmpData.getSurfaceType(); 516 MaskBlit tmpmaskblit = 517 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 int edges[] = new int[(dy2-dy1)*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, dx2-dx1, dy2-dy1, 543 edges, dx1, dy1); 544 545 /* 546 * Now copy the results, scanline by scanline, into the dest. 547 * The edges array helps us minimize the work. 548 */ 549 int index = 2; 550 for (int y = edges[0]; y < edges[1]; y++) { 551 int relx1 = edges[index++]; 552 int relx2 = edges[index++]; 553 if (relx1 >= relx2) { 554 continue; 555 } 556 if (maskblit != null) { 557 maskblit.MaskBlit(tmpData, dstData, 558 sg.composite, clip, 559 relx1, y, 560 dx1+relx1, dy1+y, 561 relx2 - relx1, 1, 562 null, 0, 0); 563 } else { 564 blit.Blit(tmpData, dstData, 565 sg.composite, clip, 566 relx1, y, 567 dx1+relx1, dy1+y, 568 relx2 - relx1, 1); 569 } 570 } 571 } 572 573 // Render an image using only integer translation 574 // (no scale or transform or sub-pixel interpolated translations). 575 protected boolean renderImageCopy(SunGraphics2D sg, Image img, 576 Color bgColor, 577 int dx, int dy, 578 int sx, int sy, 579 int w, int h) 580 { 581 Region clip = sg.getCompClip(); 582 SurfaceData dstData = sg.surfaceData; 583 584 int attempts = 0; 585 // Loop up to twice through; this gives us a chance to 586 // revalidate the surfaceData objects in case of an exception 587 // and try it once more 588 while (true) { 589 SurfaceData srcData = 590 dstData.getSourceSurfaceData(img, 591 SunGraphics2D.TRANSFORM_ISIDENT, 592 sg.imageComp, 593 bgColor); 594 if (srcData == null) { 595 return false; 596 } 597 598 try { 599 SurfaceType srcType = srcData.getSurfaceType(); 600 SurfaceType dstType = dstData.getSurfaceType(); 601 blitSurfaceData(sg, clip, 602 srcData, dstData, srcType, dstType, 603 sx, sy, dx, dy, w, h, bgColor); 604 return true; 605 } catch (NullPointerException e) { 606 if (!(SurfaceData.isNull(dstData) || 607 SurfaceData.isNull(srcData))) 608 { 609 // Something else caused the exception, throw it... 610 throw e; 611 } 612 return false; 613 // NOP if we have been disposed 614 } catch (InvalidPipeException e) { 615 // Always catch the exception; try this a couple of times 616 // and fail silently if the system is not yet ready to 617 // revalidate the source or dest surfaceData objects. 618 ++attempts; 619 clip = sg.getCompClip(); // ensures sg.surfaceData is valid 620 dstData = sg.surfaceData; 621 if (SurfaceData.isNull(dstData) || 622 SurfaceData.isNull(srcData) || (attempts > 1)) 623 { 624 return false; 625 } 626 } 627 } 628 } 629 630 // Render an image using only integer scaling (no transform). 631 protected boolean renderImageScale(SunGraphics2D sg, Image img, 632 Color bgColor, int interpType, 633 int sx1, int sy1, 634 int sx2, int sy2, 635 double dx1, double dy1, 636 double dx2, double dy2) 637 { 638 // Currently only NEAREST_NEIGHBOR interpolation is implemented 639 // for ScaledBlit operations. 640 if (interpType != AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { 641 return false; 642 } 643 644 Region clip = sg.getCompClip(); 645 SurfaceData dstData = sg.surfaceData; 646 647 int attempts = 0; 648 // Loop up to twice through; this gives us a chance to 649 // revalidate the surfaceData objects in case of an exception 650 // and try it once more 651 while (true) { 652 SurfaceData srcData = 653 dstData.getSourceSurfaceData(img, 654 SunGraphics2D.TRANSFORM_TRANSLATESCALE, 655 sg.imageComp, 656 bgColor); 657 658 if (srcData == null || isBgOperation(srcData, bgColor)) { 659 return false; 660 } 661 662 try { 663 SurfaceType srcType = srcData.getSurfaceType(); 664 SurfaceType dstType = dstData.getSurfaceType(); 665 return scaleSurfaceData(sg, clip, 666 srcData, dstData, srcType, dstType, 667 sx1, sy1, sx2, sy2, 668 dx1, dy1, dx2, dy2); 669 } catch (NullPointerException e) { 670 if (!SurfaceData.isNull(dstData)) { 671 // Something else caused the exception, throw it... 672 throw e; 673 } 674 return false; 675 // NOP if we have been disposed 676 } catch (InvalidPipeException e) { 677 // Always catch the exception; try this a couple of times 678 // and fail silently if the system is not yet ready to 679 // revalidate the source or dest surfaceData objects. 680 ++attempts; 681 clip = sg.getCompClip(); // ensures sg.surfaceData is valid 682 dstData = sg.surfaceData; 683 if (SurfaceData.isNull(dstData) || 684 SurfaceData.isNull(srcData) || (attempts > 1)) 685 { 686 return false; 687 } 688 } 689 } 690 } 691 692 public boolean scaleImage(SunGraphics2D sg, Image img, 693 int dx1, int dy1, int dx2, int dy2, 694 int sx1, int sy1, int sx2, int sy2, 695 Color bgColor) 696 { 697 int srcW, srcH, dstW, dstH; 698 int srcX, srcY, dstX, dstY; 699 boolean srcWidthFlip = false; 700 boolean srcHeightFlip = false; 701 boolean dstWidthFlip = false; 702 boolean dstHeightFlip = false; 703 704 if (sx2 > sx1) { 705 srcW = sx2 - sx1; 706 srcX = sx1; 707 } else { 708 srcWidthFlip = true; 709 srcW = sx1 - sx2; 710 srcX = sx2; 711 } 712 if (sy2 > sy1) { 713 srcH = sy2-sy1; 714 srcY = sy1; 715 } else { 716 srcHeightFlip = true; 717 srcH = sy1-sy2; 718 srcY = sy2; 719 } 720 if (dx2 > dx1) { 721 dstW = dx2 - dx1; 722 dstX = dx1; 723 } else { 724 dstW = dx1 - dx2; 725 dstWidthFlip = true; 726 dstX = dx2; 727 } 728 if (dy2 > dy1) { 729 dstH = dy2 - dy1; 730 dstY = dy1; 731 } else { 732 dstH = dy1 - dy2; 733 dstHeightFlip = true; 734 dstY = dy2; 735 } 736 if (srcW <= 0 || srcH <= 0) { 737 return true; 738 } 739 // Only accelerate scale if it does not involve a flip or transform 740 if ((srcWidthFlip == dstWidthFlip) && 741 (srcHeightFlip == dstHeightFlip) && 742 isSimpleTranslate(sg)) 743 { 744 double ddx1 = dstX + sg.transX; 745 double ddy1 = dstY + sg.transY; 746 double ddx2 = ddx1 + dstW; 747 double ddy2 = ddy1 + dstH; 748 if (renderImageScale(sg, img, bgColor, sg.interpolationType, 749 srcX, srcY, srcX+srcW, srcY+srcH, 750 ddx1, ddy1, ddx2, ddy2)) 751 { 752 return true; 753 } 754 } 755 756 AffineTransform atfm = new AffineTransform(sg.transform); 757 atfm.translate(dx1, dy1); 758 double m00 = (double)(dx2-dx1)/(sx2-sx1); 759 double m11 = (double)(dy2-dy1)/(sy2-sy1); 760 atfm.scale(m00, m11); 761 atfm.translate(srcX-sx1, srcY-sy1); 762 763 final int scale = SurfaceManager.getImageScale(img); 764 final int imgW = img.getWidth(null) * scale; 765 final int imgH = img.getHeight(null) * scale; 766 srcW += srcX; 767 srcH += srcY; 768 // Make sure we are not out of bounds 769 if (srcW > imgW) { 770 srcW = imgW; 771 } 772 if (srcH > imgH) { 773 srcH = imgH; 774 } 775 if (srcX < 0) { 776 atfm.translate(-srcX, 0); 777 srcX = 0; 778 } 779 if (srcY < 0) { 780 atfm.translate(0, -srcY); 781 srcY = 0; 782 } 783 if (srcX >= srcW || srcY >= srcH) { 784 return true; 785 } 786 // Note: src[WH] are currently the right and bottom coordinates. 787 // The following two lines would adjust src[WH] back to being 788 // dimensions. 789 // srcW -= srcX; 790 // srcH -= srcY; 791 // Since transformImage needs right and bottom coords we will 792 // omit this adjustment. 793 794 transformImage(sg, img, atfm, sg.interpolationType, 795 srcX, srcY, srcW, srcH, bgColor); 796 return true; 797 } 798 799 /** 800 ** Utilities 801 ** The following methods are used by the public methods above 802 ** for performing various operations 803 **/ 804 805 /* 806 * This constant represents a tradeoff between the 807 * need to make sure that image transformations are 808 * "very close" to integer device coordinates before 809 * we decide to use an integer scale or copy operation 810 * as a substitute and the fact that roundoff errors 811 * in AffineTransforms are frequently introduced by 812 * performing multiple sequential operations on them. 813 * 814 * The evaluation of bug 4990624 details the potential 815 * for this error cutoff to result in display anomalies 816 * in different types of image operations and how this 817 * value represents a good compromise here. 818 */ 819 private static final double MAX_TX_ERROR = .0001; 820 821 public static boolean closeToInteger(int i, double d) { 822 return (Math.abs(d-i) < MAX_TX_ERROR); 823 } 824 825 public static boolean isSimpleTranslate(SunGraphics2D sg) { 826 int ts = sg.transformState; 827 if (ts <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) { 828 // Integer translates are always "simple" 829 return true; 830 } 831 if (ts >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) { 832 // Scales and beyond are always "not simple" 833 return false; 834 } 835 // non-integer translates are only simple when not interpolating 836 if (sg.interpolationType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR) { 837 return true; 838 } 839 return false; 840 } 841 842 protected static boolean isBgOperation(SurfaceData srcData, Color bgColor) { 843 // If we cannot get the srcData, then cannot assume anything about 844 // the image 845 return ((srcData == null) || 846 ((bgColor != null) && 847 (srcData.getTransparency() != Transparency.OPAQUE))); 848 } 849 850 protected BufferedImage getBufferedImage(Image img) { 851 if (img instanceof BufferedImage) { 852 return (BufferedImage)img; 853 } 854 // Must be VolatileImage; get BufferedImage representation 855 return ((VolatileImage)img).getSnapshot(); 856 } 857 858 /* 859 * Return the color model to be used with this BufferedImage and 860 * transform. 861 */ 862 private ColorModel getTransformColorModel(SunGraphics2D sg, 863 BufferedImage bImg, 864 AffineTransform tx) { 865 ColorModel cm = bImg.getColorModel(); 866 ColorModel dstCM = cm; 867 868 if (tx.isIdentity()) { 869 return dstCM; 870 } 871 int type = tx.getType(); 872 boolean needTrans = 873 ((type & (AffineTransform.TYPE_MASK_ROTATION | 874 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 875 if (! needTrans && 876 type != AffineTransform.TYPE_TRANSLATION && 877 type != AffineTransform.TYPE_IDENTITY) 878 { 879 double[] mtx = new double[4]; 880 tx.getMatrix(mtx); 881 // Check out the matrix. A non-integral scale will force ARGB 882 // since the edge conditions cannot be guaranteed. 883 needTrans = (mtx[0] != (int)mtx[0] || mtx[3] != (int)mtx[3]); 884 } 885 886 if (sg.renderHint != SunHints.INTVAL_RENDER_QUALITY) { 887 if (cm instanceof IndexColorModel) { 888 Raster raster = bImg.getRaster(); 889 IndexColorModel icm = (IndexColorModel) cm; 890 // Just need to make sure that we have a transparent pixel 891 if (needTrans && cm.getTransparency() == Transparency.OPAQUE) { 892 // Fix 4221407 893 if (raster instanceof sun.awt.image.BytePackedRaster) { 894 dstCM = ColorModel.getRGBdefault(); 895 } 896 else { 897 double[] matrix = new double[6]; 898 tx.getMatrix(matrix); 899 if (matrix[1] == 0. && matrix[2] ==0. 900 && matrix[4] == 0. && matrix[5] == 0.) { 901 // Only scaling so do not need to create 902 } 903 else { 904 int mapSize = icm.getMapSize(); 905 if (mapSize < 256) { 906 int[] cmap = new int[mapSize+1]; 907 icm.getRGBs(cmap); 908 cmap[mapSize] = 0x0000; 909 dstCM = new 910 IndexColorModel(icm.getPixelSize(), 911 mapSize+1, 912 cmap, 0, true, mapSize, 913 DataBuffer.TYPE_BYTE); 914 } 915 else { 916 dstCM = ColorModel.getRGBdefault(); 917 } 918 } /* if (matrix[0] < 1.f ...) */ 919 } /* raster instanceof sun.awt.image.BytePackedRaster */ 920 } /* if (cm.getTransparency() == cm.OPAQUE) */ 921 } /* if (cm instanceof IndexColorModel) */ 922 else if (needTrans && cm.getTransparency() == Transparency.OPAQUE) { 923 // Need a bitmask transparency 924 // REMIND: for now, use full transparency since no loops 925 // for bitmask 926 dstCM = ColorModel.getRGBdefault(); 927 } 928 } /* if (sg.renderHint == RENDER_QUALITY) */ 929 else { 930 931 if (cm instanceof IndexColorModel || 932 (needTrans && cm.getTransparency() == Transparency.OPAQUE)) 933 { 934 // Need a bitmask transparency 935 // REMIND: for now, use full transparency since no loops 936 // for bitmask 937 dstCM = ColorModel.getRGBdefault(); 938 } 939 } 940 941 return dstCM; 942 } 943 944 protected void blitSurfaceData(SunGraphics2D sg, 945 Region clipRegion, 946 SurfaceData srcData, 947 SurfaceData dstData, 948 SurfaceType srcType, 949 SurfaceType dstType, 950 int sx, int sy, int dx, int dy, 951 int w, int h, 952 Color bgColor) 953 { 954 if (w <= 0 || h <= 0) { 955 /* 956 * Fix for bugid 4783274 - BlitBg throws an exception for 957 * a particular set of anomalous parameters. 958 * REMIND: The native loops do proper clipping and would 959 * detect this situation themselves, but the Java loops 960 * all seem to trust their parameters a little too well 961 * to the point where they will try to process a negative 962 * area of pixels and throw exceptions. The real fix is 963 * to modify the Java loops to do proper clipping so that 964 * they can deal with negative dimensions as well as 965 * improperly large dimensions, but that fix is too risky 966 * to integrate for Mantis at this point. In the meantime 967 * eliminating the negative or zero dimensions here is 968 * "correct" and saves them from some nasty exceptional 969 * conditions, one of which is the test case of 4783274. 970 */ 971 return; 972 } 973 CompositeType comp = sg.imageComp; 974 if (CompositeType.SrcOverNoEa.equals(comp) && 975 (srcData.getTransparency() == Transparency.OPAQUE || 976 (bgColor != null && 977 bgColor.getTransparency() == Transparency.OPAQUE))) 978 { 979 comp = CompositeType.SrcNoEa; 980 } 981 if (!isBgOperation(srcData, bgColor)) { 982 Blit blit = Blit.getFromCache(srcType, comp, dstType); 983 blit.Blit(srcData, dstData, sg.composite, clipRegion, 984 sx, sy, dx, dy, w, h); 985 } else { 986 BlitBg blit = BlitBg.getFromCache(srcType, comp, dstType); 987 blit.BlitBg(srcData, dstData, sg.composite, clipRegion, 988 bgColor.getRGB(), sx, sy, dx, dy, w, h); 989 } 990 } 991 992 protected boolean scaleSurfaceData(SunGraphics2D sg, 993 Region clipRegion, 994 SurfaceData srcData, 995 SurfaceData dstData, 996 SurfaceType srcType, 997 SurfaceType dstType, 998 int sx1, int sy1, 999 int sx2, int sy2, 1000 double dx1, double dy1, 1001 double dx2, double dy2) 1002 { 1003 CompositeType comp = sg.imageComp; 1004 if (CompositeType.SrcOverNoEa.equals(comp) && 1005 (srcData.getTransparency() == Transparency.OPAQUE)) 1006 { 1007 comp = CompositeType.SrcNoEa; 1008 } 1009 1010 ScaledBlit blit = ScaledBlit.getFromCache(srcType, comp, dstType); 1011 if (blit != null) { 1012 blit.Scale(srcData, dstData, sg.composite, clipRegion, 1013 sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); 1014 return true; 1015 } 1016 return false; 1017 } 1018 1019 protected static boolean imageReady(ToolkitImage sunimg, 1020 ImageObserver observer) 1021 { 1022 if (sunimg.hasError()) { 1023 if (observer != null) { 1024 observer.imageUpdate(sunimg, 1025 ImageObserver.ERROR|ImageObserver.ABORT, 1026 -1, -1, -1, -1); 1027 } 1028 return false; 1029 } 1030 return true; 1031 } 1032 1033 public boolean copyImage(SunGraphics2D sg, Image img, 1034 int x, int y, 1035 Color bgColor, 1036 ImageObserver observer) { 1037 if (!(img instanceof ToolkitImage)) { 1038 return copyImage(sg, img, x, y, bgColor); 1039 } else { 1040 ToolkitImage sunimg = (ToolkitImage)img; 1041 if (!imageReady(sunimg, observer)) { 1042 return false; 1043 } 1044 ImageRepresentation ir = sunimg.getImageRep(); 1045 return ir.drawToBufImage(sg, sunimg, x, y, bgColor, observer); 1046 } 1047 } 1048 1049 public boolean copyImage(SunGraphics2D sg, Image img, 1050 int dx, int dy, int sx, int sy, int w, int h, 1051 Color bgColor, 1052 ImageObserver observer) { 1053 if (!(img instanceof ToolkitImage)) { 1054 return copyImage(sg, img, dx, dy, sx, sy, w, h, bgColor); 1055 } else { 1056 ToolkitImage sunimg = (ToolkitImage)img; 1057 if (!imageReady(sunimg, observer)) { 1058 return false; 1059 } 1060 ImageRepresentation ir = sunimg.getImageRep(); 1061 return ir.drawToBufImage(sg, sunimg, 1062 dx, dy, (dx + w), (dy + h), 1063 sx, sy, (sx + w), (sy + h), 1064 bgColor, observer); 1065 } 1066 } 1067 1068 public boolean scaleImage(SunGraphics2D sg, Image img, 1069 int x, int y, 1070 int width, int height, 1071 Color bgColor, 1072 ImageObserver observer) { 1073 if (!(img instanceof ToolkitImage)) { 1074 return scaleImage(sg, img, x, y, width, height, bgColor); 1075 } else { 1076 ToolkitImage sunimg = (ToolkitImage)img; 1077 if (!imageReady(sunimg, observer)) { 1078 return false; 1079 } 1080 ImageRepresentation ir = sunimg.getImageRep(); 1081 return ir.drawToBufImage(sg, sunimg, x, y, width, height, bgColor, 1082 observer); 1083 } 1084 } 1085 1086 public boolean scaleImage(SunGraphics2D sg, Image img, 1087 int dx1, int dy1, int dx2, int dy2, 1088 int sx1, int sy1, int sx2, int sy2, 1089 Color bgColor, 1090 ImageObserver observer) { 1091 if (!(img instanceof ToolkitImage)) { 1092 return scaleImage(sg, img, dx1, dy1, dx2, dy2, 1093 sx1, sy1, sx2, sy2, bgColor); 1094 } else { 1095 ToolkitImage sunimg = (ToolkitImage)img; 1096 if (!imageReady(sunimg, observer)) { 1097 return false; 1098 } 1099 ImageRepresentation ir = sunimg.getImageRep(); 1100 return ir.drawToBufImage(sg, sunimg, dx1, dy1, dx2, dy2, 1101 sx1, sy1, sx2, sy2, bgColor, observer); 1102 } 1103 } 1104 1105 public boolean transformImage(SunGraphics2D sg, Image img, 1106 AffineTransform atfm, 1107 ImageObserver observer) { 1108 if (!(img instanceof ToolkitImage)) { 1109 transformImage(sg, img, 0, 0, atfm, sg.interpolationType); 1110 return true; 1111 } else { 1112 ToolkitImage sunimg = (ToolkitImage)img; 1113 if (!imageReady(sunimg, observer)) { 1114 return false; 1115 } 1116 ImageRepresentation ir = sunimg.getImageRep(); 1117 return ir.drawToBufImage(sg, sunimg, atfm, observer); 1118 } 1119 } 1120 1121 public void transformImage(SunGraphics2D sg, BufferedImage img, 1122 BufferedImageOp op, int x, int y) 1123 { 1124 if (op != null) { 1125 if (op instanceof AffineTransformOp) { 1126 AffineTransformOp atop = (AffineTransformOp) op; 1127 transformImage(sg, img, x, y, 1128 atop.getTransform(), 1129 atop.getInterpolationType()); 1130 return; 1131 } else { 1132 img = op.filter(img, null); 1133 } 1134 } 1135 copyImage(sg, img, x, y, null); 1136 } 1137 }