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