gt:             sx1 = sy1 = 0;
context: /*
   * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package sun.java2d.pipe;
  
  import java.awt.AlphaComposite;
  import java.awt.Color;
  import java.awt.Image;
  import java.awt.Transparency;
  import java.awt.geom.AffineTransform;
  import java.awt.geom.NoninvertibleTransformException;
  import java.awt.image.AffineTransformOp;
  import java.awt.image.BufferedImage;
  import java.awt.image.BufferedImageOp;
  import java.awt.image.ColorModel;
  import java.awt.image.DataBuffer;
  import java.awt.image.ImageObserver;
  import java.awt.image.IndexColorModel;
  import java.awt.image.Raster;
  import java.awt.image.VolatileImage;
  import sun.awt.SunHints;
  import sun.awt.image.ImageRepresentation;
  import sun.awt.image.SurfaceManager;
  import sun.awt.image.ToolkitImage;
  import sun.java2d.InvalidPipeException;
  import sun.java2d.SunGraphics2D;
  import sun.java2d.SurfaceData;
  import sun.java2d.loops.Blit;
  import sun.java2d.loops.BlitBg;
  import sun.java2d.loops.TransformHelper;
  import sun.java2d.loops.MaskBlit;
  import sun.java2d.loops.CompositeType;
  import sun.java2d.loops.ScaledBlit;
  import sun.java2d.loops.SurfaceType;
  
  public class DrawImage implements DrawImagePipe
  {
      public boolean copyImage(SunGraphics2D sg, Image img,
                               int x, int y,
                               Color bgColor)
      {
          int imgw = img.getWidth(null);
          int imgh = img.getHeight(null);
          if (isSimpleTranslate(sg)) {
              return renderImageCopy(sg, img, bgColor,
                                     x + sg.transX, y + sg.transY,
                                     0, 0, imgw, imgh);
          }
          AffineTransform atfm = sg.transform;
          if ((x | y) != 0) {
              atfm = new AffineTransform(atfm);
              atfm.translate(x, y);
          }
          transformImage(sg, img, atfm, sg.interpolationType,
                         0, 0, imgw, imgh, bgColor);
          return true;
      }
  
      public boolean copyImage(SunGraphics2D sg, Image img,
                               int dx, int dy, int sx, int sy, int w, int h,
                               Color bgColor)
      {
          if (isSimpleTranslate(sg)) {
              return renderImageCopy(sg, img, bgColor,
                                     dx + sg.transX, dy + sg.transY,
                                     sx, sy, w, h);
          }
          scaleImage(sg, img, dx, dy, (dx + w), (dy + h),
                     sx, sy, (sx + w), (sy + h), bgColor);
          return true;
      }
  
      public boolean scaleImage(SunGraphics2D sg, Image img, int x, int y,
                                int width, int height,
                                Color bgColor)
      {
          int imgw = img.getWidth(null);
          int imgh = img.getHeight(null);
          // Only accelerate scale if:
          //          - w/h positive values
          //          - sg transform integer translate/identity only
          //          - no bgColor in operation
          if ((width > 0) && (height > 0) && isSimpleTranslate(sg)) {
              double dx1 = x + sg.transX;
              double dy1 = y + sg.transY;
              double dx2 = dx1 + width;
              double dy2 = dy1 + height;
              if (renderImageScale(sg, img, bgColor, sg.interpolationType,
                                   0, 0, imgw, imgh,
                                   dx1, dy1, dx2, dy2))
              {
                  return true;
              }
          }
  
          AffineTransform atfm = sg.transform;
          if ((x | y) != 0 || width != imgw || height != imgh) {
              atfm = new AffineTransform(atfm);
              atfm.translate(x, y);
              atfm.scale(((double)width)/imgw, ((double)height)/imgh);
          }
          transformImage(sg, img, atfm, sg.interpolationType,
                         0, 0, imgw, imgh, bgColor);
          return true;
      }
  
      /*
       * This method is only called in those circumstances where the
       * operation has a non-null secondary transform specified.  Its
       * role is to check for various optimizations based on the types
       * of both the secondary and SG2D transforms and to do some
       * quick calculations to avoid having to combine the transforms
       * and/or to call a more generalized method.
       */
      protected void transformImage(SunGraphics2D sg, Image img, int x, int y,
                                    AffineTransform extraAT, int interpType)
      {
          int txtype = extraAT.getType();
          int imgw = img.getWidth(null);
          int imgh = img.getHeight(null);
          boolean checkfinalxform;
  
          if (sg.transformState <= SunGraphics2D.TRANSFORM_ANY_TRANSLATE &&
              (txtype == AffineTransform.TYPE_IDENTITY ||
               txtype == AffineTransform.TYPE_TRANSLATION))
          {
              // First optimization - both are some kind of translate
  
              // Combine the translations and check if interpolation is necessary.
              double tx = extraAT.getTranslateX();
              double ty = extraAT.getTranslateY();
              tx += sg.transform.getTranslateX();
              ty += sg.transform.getTranslateY();
              int itx = (int) Math.floor(tx + 0.5);
              int ity = (int) Math.floor(ty + 0.5);
              if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR ||
                  (closeToInteger(itx, tx) && closeToInteger(ity, ty)))
              {
                  renderImageCopy(sg, img, null, x+itx, y+ity, 0, 0, imgw, imgh);
                  return;
              }
              checkfinalxform = false;
          } else if (sg.transformState <= SunGraphics2D.TRANSFORM_TRANSLATESCALE &&
                     ((txtype & (AffineTransform.TYPE_FLIP |
                                 AffineTransform.TYPE_MASK_ROTATION |
                                 AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0))
          {
              // Second optimization - both are some kind of translate or scale
  
              // Combine the scales and check if interpolation is necessary.
  
              // Transform source bounds by extraAT,
              // then translate the bounds again by x, y
              // then transform the bounds again by sg.transform
              double coords[] = new double[] {
                  0, 0, imgw, imgh,
              };
              extraAT.transform(coords, 0, coords, 0, 2);
              coords[0] += x;
              coords[1] += y;
              coords[2] += x;
              coords[3] += y;
              sg.transform.transform(coords, 0, coords, 0, 2);
  
              if (tryCopyOrScale(sg, img, 0, 0, imgw, imgh,
                                 null, interpType, coords))
              {
                  return;
              }
              checkfinalxform = false;
          } else {
              checkfinalxform = true;
          }
  
          // Begin Transform
          AffineTransform tx = new AffineTransform(sg.transform);
          tx.translate(x, y);
          tx.concatenate(extraAT);
  
          // Do not try any more optimizations if either of the cases
          // above was tried as we have already verified that the
          // resulting transform will not simplify.
          if (checkfinalxform) {
              // In this case neither of the above simple transform
              // pairs was found so we will do some final tests on
              // the final rendering transform which may be the
              // simple product of two complex transforms.
              transformImage(sg, img, tx, interpType, 0, 0, imgw, imgh, null);
          } else {
              renderImageXform(sg, img, tx, interpType, 0, 0, imgw, imgh, null);
          }
      }
  
      /*
       * This method is called with a final rendering transform that
       * has combined all of the information about the Graphics2D
       * transform attribute with the transformations specified by
       * the arguments to the drawImage call.
       * Its role is to see if the combined transform ends up being
       * acceleratable by either a renderImageCopy or renderImageScale
       * once all of the math is done.
       *
       * Note: The transform supplied here has an origin that is
       * already adjusted to point to the device location where
       * the (sx1, sy1) location of the source image should be placed.
       */
      protected void transformImage(SunGraphics2D sg, Image img,
                                    AffineTransform tx, int interpType,
                                    int sx1, int sy1, int sx2, int sy2,
                                    Color bgColor)
      {
          // Transform 3 source corners by tx and analyze them
          // for simplified operations (Copy or Scale).  Using
          // 3 points lets us analyze any kind of transform,
          // even transforms that involve very tiny amounts of
          // rotation or skew to see if they degenerate to a
          // simple scale or copy operation within the allowable
          // error bounds.
          // Note that we use (0,0,w,h) instead of (sx1,sy1,sx2,sy2)
          // because the transform is already translated such that
          // the origin is where sx1, sy1 should go.
          double coords[] = new double[6];
          /* index:  0  1    2  3    4  5  */
          /* coord: (0, 0), (w, h), (0, h) */
          coords[2] = sx2 - sx1;
          coords[3] = coords[5] = sy2 - sy1;
          tx.transform(coords, 0, coords, 0, 3);
          // First test if the X coords of the transformed UL
          // and LL points match and that the Y coords of the
          // transformed LR and LL points also match.
          // If they do then it is a "rectilinear" transform and
          // tryCopyOrScale will make sure it is upright and
          // integer-based.
          if (Math.abs(coords[0] - coords[4]) < MAX_TX_ERROR &&
              Math.abs(coords[3] - coords[5]) < MAX_TX_ERROR &&
              tryCopyOrScale(sg, img, sx1, sy1, sx2, sy2,
                             bgColor, interpType, coords))
          {
              return;
          }
  
          renderImageXform(sg, img, tx, interpType, sx1, sy1, sx2, sy2, bgColor);
      }
  
      /*
       * Check the bounding coordinates of the transformed source
       * image to see if they fall on integer coordinates such
       * that they will cause no interpolation anomalies if we
       * use our simplified Blit or ScaledBlit operations instead
       * of a full transform operation.
       */
      protected boolean tryCopyOrScale(SunGraphics2D sg,
                                       Image img,
                                       int sx1, int sy1,
                                       int sx2, int sy2,
                                       Color bgColor, int interpType,
                                       double coords[])
      {
          double dx1 = coords[0];
          double dy1 = coords[1];
          double dx2 = coords[2];
          double dy2 = coords[3];
          double dw = dx2 - dx1;
          double dh = dy2 - dy1;
  
          /* If any of the destination coordinates exceed the integer range,
           * then the calculations performed in calls made here cannot be
           * guaranteed to be correct, or to converge (terminate).
           * So return out of here, deferring to code that can handle this.
           */
          if (dx1 < Integer.MIN_VALUE || dx1 > Integer.MAX_VALUE ||
              dy1 < Integer.MIN_VALUE || dy1 > Integer.MAX_VALUE ||
              dx2 < Integer.MIN_VALUE || dx2 > Integer.MAX_VALUE ||
              dy2 < Integer.MIN_VALUE || dy2 > Integer.MAX_VALUE)
          {
              return false;
          }
  
          // First check if width and height are very close to img w&h.
          if (closeToInteger(sx2-sx1, dw) && closeToInteger(sy2-sy1, dh)) {
              // Round location to nearest pixel and then test
              // if it will cause interpolation anomalies.
              int idx = (int) Math.floor(dx1 + 0.5);
              int idy = (int) Math.floor(dy1 + 0.5);
              if (interpType == AffineTransformOp.TYPE_NEAREST_NEIGHBOR ||
                  (closeToInteger(idx, dx1) && closeToInteger(idy, dy1)))
              {
                  renderImageCopy(sg, img, bgColor,
                                  idx, idy,
                                  sx1, sy1, sx2-sx1, sy2-sy1);
                  return true;
              }
          }
          // (For now) We can only use our ScaledBlits if the image
          // is upright (i.e. dw & dh both > 0)
          if (dw > 0 && dh > 0) {
              if (renderImageScale(sg, img, bgColor, interpType,
                                   sx1, sy1, sx2, sy2,
                                   dx1, dy1, dx2, dy2))
              {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Return a non-accelerated BufferedImage of the requested type with the
       * indicated subimage of the original image located at 0,0 in the new image.
       * If a bgColor is supplied, composite the original image over that color
       * with a SrcOver operation, otherwise make a SrcNoEa copy.
       * <p>
       * Returned BufferedImage is not accelerated for two reasons:
       * <ul>
       * <li> Types of the image and surface are predefined, because these types
       *      correspond to the TransformHelpers, which we know we have. And
       *      acceleration can change the type of the surface
       * <li> Image will be used only once and acceleration caching wouldn't help
       * </ul>
       */
      private BufferedImage makeBufferedImage(Image img, Color bgColor, int type,
                                              int sx1, int sy1, int sx2, int sy2)
      {
          final int width = sx2 - sx1;
          final int height = sy2 - sy1;
          final BufferedImage bimg = new BufferedImage(width, height, type);
          final SunGraphics2D g2d = (SunGraphics2D) bimg.createGraphics();
          g2d.setComposite(AlphaComposite.Src);
          bimg.setAccelerationPriority(0);
          if (bgColor != null) {
              g2d.setColor(bgColor);
              g2d.fillRect(0, 0, width, height);
              g2d.setComposite(AlphaComposite.SrcOver);
          }
          g2d.copyImage(img, 0, 0, sx1, sy1, width, height, null, null);
          g2d.dispose();
          return bimg;
      }
  
      protected void renderImageXform(SunGraphics2D sg, Image img,
                                      AffineTransform tx, int interpType,
                                      int sx1, int sy1, int sx2, int sy2,
                                      Color bgColor)
      {
          final AffineTransform itx;
          try {
              itx = tx.createInverse();
          } catch (final NoninvertibleTransformException ignored) {
              // Non-invertible transform means no output
              return;
          }
  
          /*
           * Find the maximum bounds on the destination that will be
           * affected by the transformed source.  First, transform all
           * four corners of the source and then min and max the resulting
           * destination coordinates of the transformed corners.
           * Note that tx already has the offset to sx1,sy1 accounted
           * for so we use the box (0, 0, sx2-sx1, sy2-sy1) as the
           * source coordinates.
           */
          final double[] coords = new double[8];
          /* corner:  UL      UR      LL      LR   */
          /* index:  0  1    2  3    4  5    6  7  */
          /* coord: (0, 0), (w, 0), (0, h), (w, h) */
          coords[2] = coords[6] = sx2 - sx1;
          coords[5] = coords[7] = sy2 - sy1;
          tx.transform(coords, 0, coords, 0, 4);
          double ddx1, ddy1, ddx2, ddy2;
          ddx1 = ddx2 = coords[0];
          ddy1 = ddy2 = coords[1];
          for (int i = 2; i < coords.length; i += 2) {
              double d = coords[i];
              if (ddx1 > d) ddx1 = d;
              else if (ddx2 < d) ddx2 = d;
              d = coords[i+1];
              if (ddy1 > d) ddy1 = d;
              else if (ddy2 < d) ddy2 = d;
          }
  
          Region clip = sg.getCompClip();
          final int dx1 = Math.max((int) Math.floor(ddx1), clip.getLoX());
          final int dy1 = Math.max((int) Math.floor(ddy1), clip.getLoY());
          final int dx2 = Math.min((int) Math.ceil(ddx2), clip.getHiX());
          final int dy2 = Math.min((int) Math.ceil(ddy2), clip.getHiY());
          if (dx2 <= dx1 || dy2 <= dy1) {
              // empty destination means no output
              return;
          }
  
          final SurfaceData dstData = sg.surfaceData;
          SurfaceData srcData = dstData.getSourceSurfaceData(img,
                                                             SunGraphics2D.TRANSFORM_GENERIC,
                                                             sg.imageComp,
                                                             bgColor);
  
          if (srcData == null) {
              img = getBufferedImage(img);
              srcData = dstData.getSourceSurfaceData(img,
                                                     SunGraphics2D.TRANSFORM_GENERIC,
                                                     sg.imageComp,
                                                     bgColor);
              if (srcData == null) {
                  // REMIND: Is this correct?  Can this happen?
                  return;
              }
          }
  
          if (isBgOperation(srcData, bgColor)) {
              // We cannot perform bg operations during transform so make
              // a temp image with the appropriate background based on
              // background alpha value and work from there. If background
              // alpha is opaque use INT_RGB else use INT_ARGB so that we
              // will not lose translucency of background.
  
              int bgAlpha = bgColor.getAlpha();
              int type = ((bgAlpha == 255)
                          ? BufferedImage.TYPE_INT_RGB
                          : BufferedImage.TYPE_INT_ARGB);
              img = makeBufferedImage(img, bgColor, type, sx1, sy1, sx2, sy2);
              // Temp image has appropriate subimage at 0,0 now.
              sx2 -= sx1;
              sy2 -= sy1;
              sx1 = sy1 = 0;
  
              srcData = dstData.getSourceSurfaceData(img,
                                                     SunGraphics2D.TRANSFORM_GENERIC,
                                                     sg.imageComp,
                                                     bgColor);
          }
  
          SurfaceType srcType = srcData.getSurfaceType();
          TransformHelper helper = TransformHelper.getFromCache(srcType);
  
          if (helper == null) {
              /* We have no helper for this source image type.
               * But we know that we do have helpers for both RGB and ARGB,
               * so convert to one of those types depending on transparency.
               * ARGB_PRE might be a better choice if the source image has
               * alpha, but it may cause some recursion here since we only
               * tend to have converters that convert to ARGB.
               */
              int type = ((srcData.getTransparency() == Transparency.OPAQUE)
                          ? BufferedImage.TYPE_INT_RGB
                          : BufferedImage.TYPE_INT_ARGB);
              img = makeBufferedImage(img, null, type, sx1, sy1, sx2, sy2);
              // Temp image has appropriate subimage at 0,0 now.
              sx2 -= sx1;
              sy2 -= sy1;
  