From 246a6da52eaf479bfcb061cfd62271c765433479 Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Fri, 28 Nov 2014 10:42:03 +0000 Subject: [PATCH] 8059942: Default implementation of DrawImage.renderImageXform() should be improved for d3d/ogl Reviewed-by: flar, prr --- .../classes/sun/java2d/pipe/DrawImage.java | 150 ++++++++---------- .../share/classes/sun/java2d/pipe/Region.java | 49 ++++++ .../IncorrectUnmanagedImageRotatedClip.java | 138 ++++++++++++++++ 3 files changed, 250 insertions(+), 87 deletions(-) create mode 100644 jdk/test/java/awt/image/DrawImage/IncorrectUnmanagedImageRotatedClip.java diff --git a/jdk/src/java.desktop/share/classes/sun/java2d/pipe/DrawImage.java b/jdk/src/java.desktop/share/classes/sun/java2d/pipe/DrawImage.java index 6b10d247e43..cfa130b8199 100644 --- a/jdk/src/java.desktop/share/classes/sun/java2d/pipe/DrawImage.java +++ b/jdk/src/java.desktop/share/classes/sun/java2d/pipe/DrawImage.java @@ -364,8 +364,53 @@ public class DrawImage implements DrawImagePipe 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 @@ public class DrawImage implements DrawImagePipe // 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 @@ public class DrawImage implements DrawImagePipe 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 @@ public class DrawImage implements DrawImagePipe * * 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 @@ public class DrawImage implements DrawImagePipe 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/jdk/src/java.desktop/share/classes/sun/java2d/pipe/Region.java b/jdk/src/java.desktop/share/classes/sun/java2d/pipe/Region.java index 55bb0a43cc1..723fdaaf97e 100644 --- a/jdk/src/java.desktop/share/classes/sun/java2d/pipe/Region.java +++ b/jdk/src/java.desktop/share/classes/sun/java2d/pipe/Region.java @@ -30,6 +30,8 @@ import java.awt.Shape; 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 @@ public class Region { 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 @@ -255,6 +266,44 @@ public class Region { } } + /** + * 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. diff --git a/jdk/test/java/awt/image/DrawImage/IncorrectUnmanagedImageRotatedClip.java b/jdk/test/java/awt/image/DrawImage/IncorrectUnmanagedImageRotatedClip.java new file mode 100644 index 00000000000..faaf767d15c --- /dev/null +++ b/jdk/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(); + } +}