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