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