8059942: Default implementation of DrawImage.renderImageXform() should be improved for d3d/ogl
Reviewed-by: flar, prr
diff --git a/src/share/classes/sun/java2d/pipe/DrawImage.java b/src/share/classes/sun/java2d/pipe/DrawImage.java
index 6b10d24..cfa130b 100644
--- a/src/share/classes/sun/java2d/pipe/DrawImage.java
+++ b/src/share/classes/sun/java2d/pipe/DrawImage.java
@@ -364,8 +364,53 @@
                                     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();
-        SurfaceData dstData = sg.surfaceData;
+        final int dx1 = Math.max((int) Math.floor(ddx1), clip.lox);
+        final int dy1 = Math.max((int) Math.floor(ddy1), clip.loy);
+        final int dx2 = Math.min((int) Math.ceil(ddx2), clip.hix);
+        final int dy2 = Math.min((int) Math.ceil(ddy2), clip.hiy);
+        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,
@@ -429,56 +474,13 @@
             // assert(helper != null);
         }
 
-        AffineTransform itx;
-        try {
-            itx = tx.createInverse();
-        } catch (NoninvertibleTransformException e) {
-            // 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.
-         */
-        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;
-        }
-        int dx1 = (int) Math.floor(ddx1);
-        int dy1 = (int) Math.floor(ddy1);
-        int dx2 = (int) Math.ceil(ddx2);
-        int dy2 = (int) Math.ceil(ddy2);
-
         SurfaceType dstType = dstData.getSurfaceType();
-        MaskBlit maskblit;
-        Blit blit;
         if (sg.compositeState <= SunGraphics2D.COMP_ALPHA) {
             /* NOTE: We either have, or we can make,
              * a MaskBlit for any alpha composite type
              */
-            maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre,
-                                             sg.imageComp,
-                                             dstType);
+            MaskBlit maskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre,
+                                                      sg.imageComp, dstType);
 
             /* NOTE: We can only use the native TransformHelper
              * func to go directly to the dest if both the helper
@@ -496,27 +498,19 @@
                                  null, 0, 0);
                 return;
             }
-            blit = null;
-        } else {
-            /* NOTE: We either have, or we can make,
-             * a Blit for any composite type, even Custom
-             */
-            maskblit = null;
-            blit = Blit.getFromCache(SurfaceType.IntArgbPre,
-                                     sg.imageComp,
-                                     dstType);
         }
 
         // We need to transform to a temp image and then copy
         // just the pieces that are valid data to the dest.
-        BufferedImage tmpimg = new BufferedImage(dx2-dx1, dy2-dy1,
+        final int w = dx2 - dx1;
+        final int h = dy2 - dy1;
+        BufferedImage tmpimg = new BufferedImage(w, h,
                                                  BufferedImage.TYPE_INT_ARGB_PRE);
         SurfaceData tmpData = SurfaceData.getPrimarySurfaceData(tmpimg);
         SurfaceType tmpType = tmpData.getSurfaceType();
-        MaskBlit tmpmaskblit =
-            MaskBlit.getFromCache(SurfaceType.IntArgbPre,
-                                  CompositeType.SrcNoEa,
-                                  tmpType);
+        MaskBlit tmpmaskblit = MaskBlit.getFromCache(SurfaceType.IntArgbPre,
+                                                     CompositeType.SrcNoEa,
+                                                     tmpType);
         /*
          * The helper function fills a temporary edges buffer
          * for us with the bounding coordinates of each scanline
@@ -531,7 +525,7 @@
          *
          * edges thus has to be h*2+2 in length
          */
-        int edges[] = new int[(dy2-dy1)*2+2];
+        final int[] edges = new int[h * 2 + 2];
         // It is important that edges[0]=edges[1]=0 when we call
         // Transform in case it must return early and we would
         // not want to render anything on an error condition.
@@ -539,35 +533,17 @@
                          AlphaComposite.Src, null,
                          itx, interpType,
                          sx1, sy1, sx2, sy2,
-                         0, 0, dx2-dx1, dy2-dy1,
+                         0, 0, w, h,
                          edges, dx1, dy1);
 
-        /*
-         * Now copy the results, scanline by scanline, into the dest.
-         * The edges array helps us minimize the work.
+        final Region region = Region.getInstance(dx1, dy1, dx2, dy2, edges);
+        clip = clip.getIntersection(region);
+
+        /* NOTE: We either have, or we can make,
+         * a Blit for any composite type, even Custom
          */
-        int index = 2;
-        for (int y = edges[0]; y < edges[1]; y++) {
-            int relx1 = edges[index++];
-            int relx2 = edges[index++];
-            if (relx1 >= relx2) {
-                continue;
-            }
-            if (maskblit != null) {
-                maskblit.MaskBlit(tmpData, dstData,
-                                  sg.composite, clip,
-                                  relx1, y,
-                                  dx1+relx1, dy1+y,
-                                  relx2 - relx1, 1,
-                                  null, 0, 0);
-            } else {
-                blit.Blit(tmpData, dstData,
-                          sg.composite, clip,
-                          relx1, y,
-                          dx1+relx1, dy1+y,
-                          relx2 - relx1, 1);
-            }
-        }
+        final Blit blit = Blit.getFromCache(tmpType, sg.imageComp, dstType);
+        blit.Blit(tmpData, dstData, sg.composite, clip, 0, 0, dx1, dy1, w, h);
     }
 
     // Render an image using only integer translation
diff --git a/src/share/classes/sun/java2d/pipe/Region.java b/src/share/classes/sun/java2d/pipe/Region.java
index d398126..dc0460b 100644
--- a/src/share/classes/sun/java2d/pipe/Region.java
+++ b/src/share/classes/sun/java2d/pipe/Region.java
@@ -30,6 +30,8 @@
 import java.awt.geom.AffineTransform;
 import java.awt.geom.RectangularShape;
 
+import sun.java2d.loops.TransformHelper;
+
 /**
  * This class encapsulates a definition of a two dimensional region which
  * consists of a number of Y ranges each containing multiple X bands.
@@ -160,6 +162,15 @@
         this.hiy = hiy;
     }
 
+    private Region(int lox, int loy, int hix, int hiy, int[] bands, int end) {
+        this.lox = lox;
+        this.loy = loy;
+        this.hix = hix;
+        this.hiy = hiy;
+        this.bands = bands;
+        this.endIndex = end;
+    }
+
     /**
      * Returns a Region object covering the pixels which would be
      * touched by a fill or clip operation on a Graphics implementation
@@ -256,6 +267,44 @@
     }
 
     /**
+     * Returns a Region object with a rectangle of interest specified by the
+     * indicated rectangular area in lox, loy, hix, hiy and edges array, which
+     * is located relative to the rectangular area. Edges array - 0,1 are y
+     * range, 2N,2N+1 are x ranges, 1 per y range.
+     *
+     * @see TransformHelper
+     */
+    static Region getInstance(final int lox, final int loy, final int hix,
+                              final int hiy, final int[] edges) {
+        final int y1 = edges[0];
+        final int y2 = edges[1];
+        if (hiy <= loy || hix <= lox || y2 <= y1) {
+            return EMPTY_REGION;
+        }
+        // rowsNum * (3 + 1 * 2)
+        final int[] bands = new int[(y2 - y1) * 5];
+        int end = 0;
+        int index = 2;
+        for (int y = y1; y < y2; ++y) {
+            final int spanlox = Math.max(clipAdd(lox, edges[index++]), lox);
+            final int spanhix = Math.min(clipAdd(lox, edges[index++]), hix);
+            if (spanlox < spanhix) {
+                final int spanloy = Math.max(clipAdd(loy, y), loy);
+                final int spanhiy = Math.min(clipAdd(spanloy, 1), hiy);
+                if (spanloy < spanhiy) {
+                    bands[end++] = spanloy;
+                    bands[end++] = spanhiy;
+                    bands[end++] = 1; // 1 span per row
+                    bands[end++] = spanlox;
+                    bands[end++] = spanhix;
+                }
+            }
+        }
+        return end != 0 ? new Region(lox, loy, hix, hiy, bands, end)
+                        : EMPTY_REGION;
+    }
+
+    /**
      * Returns a Region object with a rectangle of interest specified
      * by the indicated Rectangle object.
      * <p>
diff --git a/test/java/awt/image/DrawImage/IncorrectUnmanagedImageRotatedClip.java b/test/java/awt/image/DrawImage/IncorrectUnmanagedImageRotatedClip.java
new file mode 100644
index 0000000..faaf767
--- /dev/null
+++ b/test/java/awt/image/DrawImage/IncorrectUnmanagedImageRotatedClip.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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.
+ */
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferShort;
+import java.awt.image.VolatileImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import static java.awt.Transparency.TRANSLUCENT;
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+
+/**
+ * @test
+ * @bug 8059942
+ * @summary Tests rotated clip when unmanaged image is drawn to VI.
+ *          Results of the blit to compatibleImage are used for comparison.
+ * @author Sergey Bylokhov
+ */
+public final class IncorrectUnmanagedImageRotatedClip {
+
+    public static void main(final String[] args) throws IOException {
+        BufferedImage bi = makeUnmanagedBI();
+        fill(bi);
+        test(bi);
+    }
+
+    private static void test(final BufferedImage bi) throws IOException {
+        GraphicsEnvironment ge = GraphicsEnvironment
+                .getLocalGraphicsEnvironment();
+        GraphicsConfiguration gc = ge.getDefaultScreenDevice()
+                                     .getDefaultConfiguration();
+        VolatileImage vi = gc.createCompatibleVolatileImage(500, 200,
+                                                            TRANSLUCENT);
+        BufferedImage gold = gc.createCompatibleImage(500, 200, TRANSLUCENT);
+        // draw to compatible Image
+        draw(bi, gold);
+        // draw to volatile image
+        int attempt = 0;
+        BufferedImage snapshot;
+        while (true) {
+            if (++attempt > 10) {
+                throw new RuntimeException("Too many attempts: " + attempt);
+            }
+            vi.validate(gc);
+            if (vi.validate(gc) != VolatileImage.IMAGE_OK) {
+                continue;
+            }
+            draw(bi, vi);
+            snapshot = vi.getSnapshot();
+            if (vi.contentsLost()) {
+                continue;
+            }
+            break;
+        }
+        // validate images
+        for (int x = 0; x < gold.getWidth(); ++x) {
+            for (int y = 0; y < gold.getHeight(); ++y) {
+                if (gold.getRGB(x, y) != snapshot.getRGB(x, y)) {
+                    ImageIO.write(gold, "png", new File("gold.png"));
+                    ImageIO.write(snapshot, "png", new File("bi.png"));
+                    throw new RuntimeException("Test failed.");
+                }
+            }
+        }
+    }
+
+    private static void draw(final BufferedImage from,final Image to) {
+        final Graphics2D g2d = (Graphics2D) to.getGraphics();
+        g2d.setComposite(AlphaComposite.Src);
+        g2d.setColor(Color.ORANGE);
+        g2d.fillRect(0, 0, to.getWidth(null), to.getHeight(null));
+        g2d.rotate(Math.toRadians(45));
+        g2d.clip(new Rectangle(41, 42, 43, 44));
+        g2d.drawImage(from, 50, 50, Color.blue, null);
+        g2d.dispose();
+    }
+
+    private static BufferedImage makeUnmanagedBI() {
+        final BufferedImage bi = new BufferedImage(500, 200, TYPE_INT_ARGB);
+        final DataBuffer db = bi.getRaster().getDataBuffer();
+        if (db instanceof DataBufferInt) {
+            ((DataBufferInt) db).getData();
+        } else if (db instanceof DataBufferShort) {
+            ((DataBufferShort) db).getData();
+        } else if (db instanceof DataBufferByte) {
+            ((DataBufferByte) db).getData();
+        } else {
+            try {
+                bi.setAccelerationPriority(0.0f);
+            } catch (final Throwable ignored) {
+            }
+        }
+        return bi;
+    }
+
+    private static void fill(final Image image) {
+        final Graphics2D graphics = (Graphics2D) image.getGraphics();
+        graphics.setComposite(AlphaComposite.Src);
+        for (int i = 0; i < image.getHeight(null); ++i) {
+            graphics.setColor(new Color(i, 0, 0));
+            graphics.fillRect(0, i, image.getWidth(null), 1);
+        }
+        graphics.dispose();
+    }
+}