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