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