8029455: [JLightweightFrame] support scaled painting
authorserb
Thu, 12 Jun 2014 00:32:33 +0400
changeset 25195 9de77f5b0df2
parent 25194 2972c76c5a15
child 25196 48d9cfe3f138
8029455: [JLightweightFrame] support scaled painting Reviewed-by: anthony, ant
jdk/src/macosx/classes/sun/lwawt/macosx/CPlatformLWWindow.java
jdk/src/share/classes/sun/awt/LightweightFrame.java
jdk/src/share/classes/sun/java2d/SunGraphics2D.java
jdk/src/share/classes/sun/swing/JLightweightFrame.java
jdk/src/share/classes/sun/swing/LightweightContent.java
jdk/test/java/awt/Graphics2D/ScaledCopyArea/ScaledCopyArea.java
jdk/test/javax/swing/JFrame/HangNonVolatileBuffer/HangNonVolatileBuffer.java
--- a/jdk/src/macosx/classes/sun/lwawt/macosx/CPlatformLWWindow.java	Thu Jun 12 00:19:00 2014 +0400
+++ b/jdk/src/macosx/classes/sun/lwawt/macosx/CPlatformLWWindow.java	Thu Jun 12 00:32:33 2014 +0400
@@ -29,12 +29,18 @@
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
 import java.awt.Insets;
 import java.awt.MenuBar;
 import java.awt.Point;
+import java.awt.Rectangle;
 import java.awt.Window;
+import sun.awt.CGraphicsDevice;
+import sun.awt.CGraphicsEnvironment;
 import sun.awt.CausedFocusEvent;
+import sun.awt.LightweightFrame;
 import sun.java2d.SurfaceData;
+import sun.lwawt.LWLightweightFramePeer;
 import sun.lwawt.LWWindowPeer;
 import sun.lwawt.PlatformWindow;
 
@@ -73,11 +79,6 @@
     }
 
     @Override
-    public GraphicsDevice getGraphicsDevice() {
-        return null;
-    }
-
-    @Override
     public SurfaceData getScreenSurface() {
         return null;
     }
@@ -199,4 +200,24 @@
     public long getLayerPtr() {
         return 0;
     }
+
+    @Override
+    public GraphicsDevice getGraphicsDevice() {
+        CGraphicsEnvironment ge = (CGraphicsEnvironment)GraphicsEnvironment.
+                                  getLocalGraphicsEnvironment();
+
+        LWLightweightFramePeer peer = (LWLightweightFramePeer)getPeer();
+        int scale = ((LightweightFrame)peer.getTarget()).getScaleFactor();
+
+        Rectangle bounds = ((LightweightFrame)peer.getTarget()).getHostBounds();
+        for (GraphicsDevice d : ge.getScreenDevices()) {
+            if (d.getDefaultConfiguration().getBounds().intersects(bounds) &&
+                ((CGraphicsDevice)d).getScaleFactor() == scale)
+            {
+                return d;
+            }
+        }
+        // We shouldn't be here...
+        return ge.getDefaultScreenDevice();
+    }
 }
--- a/jdk/src/share/classes/sun/awt/LightweightFrame.java	Thu Jun 12 00:19:00 2014 +0400
+++ b/jdk/src/share/classes/sun/awt/LightweightFrame.java	Thu Jun 12 00:32:33 2014 +0400
@@ -31,6 +31,7 @@
 import java.awt.Image;
 import java.awt.MenuBar;
 import java.awt.MenuComponent;
+import java.awt.Rectangle;
 import java.awt.Toolkit;
 import java.awt.peer.FramePeer;
 
@@ -124,4 +125,48 @@
      * @see SunToolkit#ungrab(java.awt.Window)
      */
     public abstract void ungrabFocus();
+
+    /**
+     * Returns the scale factor of this frame. The default value is 1.
+     *
+     * @return the scale factor
+     * @see #notifyDisplayChanged(int)
+     */
+    public abstract int getScaleFactor();
+
+    /**
+     * Called when display of the hosted frame is changed.
+     *
+     * @param scaleFactor the scale factor
+     */
+    public abstract void notifyDisplayChanged(int scaleFactor);
+
+    /**
+     * Host window absolute bounds.
+     */
+    private int hostX, hostY, hostW, hostH;
+
+    /**
+     * Returns the absolute bounds of the host (embedding) window.
+     *
+     * @return the host window bounds
+     */
+    public Rectangle getHostBounds() {
+        if (hostX == 0 && hostY == 0 && hostW == 0 && hostH == 0) {
+            // The client app is probably unaware of the setHostBounds.
+            // A safe fall-back:
+            return getBounds();
+        }
+        return new Rectangle(hostX, hostY, hostW, hostH);
+    }
+
+    /**
+     * Sets the absolute bounds of the host (embedding) window.
+     */
+    public void setHostBounds(int x, int y, int w, int h) {
+        hostX = x;
+        hostY = y;
+        hostW = w;
+        hostH = h;
+    }
 }
--- a/jdk/src/share/classes/sun/java2d/SunGraphics2D.java	Thu Jun 12 00:19:00 2014 +0400
+++ b/jdk/src/share/classes/sun/java2d/SunGraphics2D.java	Thu Jun 12 00:32:33 2014 +0400
@@ -2108,7 +2108,7 @@
         if (theData.copyArea(this, x, y, w, h, dx, dy)) {
             return;
         }
-        if (transformState >= TRANSFORM_TRANSLATESCALE) {
+        if (transformState > TRANSFORM_TRANSLATESCALE) {
             throw new InternalError("transformed copyArea not implemented yet");
         }
         // REMIND: This method does not deal with missing data from the
@@ -2129,8 +2129,25 @@
             lastCAcomp = comp;
         }
 
-        x += transX;
-        y += transY;
+        double[] coords = {x, y, x + w, y + h, x + dx, y + dy};
+        transform.transform(coords, 0, coords, 0, 3);
+
+        x = (int)Math.ceil(coords[0] - 0.5);
+        y = (int)Math.ceil(coords[1] - 0.5);
+        w = ((int)Math.ceil(coords[2] - 0.5)) - x;
+        h = ((int)Math.ceil(coords[3] - 0.5)) - y;
+        dx = ((int)Math.ceil(coords[4] - 0.5)) - x;
+        dy = ((int)Math.ceil(coords[5] - 0.5)) - y;
+
+        // In case of negative scale transform, reflect the rect coords.
+        if (w < 0) {
+            w *= -1;
+            x -= w;
+        }
+        if (h < 0) {
+            h *= -1;
+            y -= h;
+        }
 
         Blit ob = lastCAblit;
         if (dy == 0 && dx > 0 && dx < w) {
--- a/jdk/src/share/classes/sun/swing/JLightweightFrame.java	Thu Jun 12 00:19:00 2014 +0400
+++ b/jdk/src/share/classes/sun/swing/JLightweightFrame.java	Thu Jun 12 00:32:33 2014 +0400
@@ -54,6 +54,7 @@
 import javax.swing.RootPaneContainer;
 import javax.swing.SwingUtilities;
 
+import sun.awt.DisplayChangedListener;
 import sun.awt.LightweightFrame;
 import sun.security.action.GetPropertyAction;
 import sun.swing.SwingUtilities2.RepaintListener;
@@ -80,6 +81,8 @@
 
     private BufferedImage bbImage;
 
+    private volatile int scaleFactor = 1;
+
     /**
      * {@code copyBufferEnabled}, true by default, defines the following strategy.
      * A duplicating (copy) buffer is created for the original pixel buffer.
@@ -90,7 +93,7 @@
      * by the lock (managed with the {@link LightweightContent#paintLock()},
      * {@link LightweightContent#paintUnlock()} methods).
      */
-    private boolean copyBufferEnabled;
+    private static boolean copyBufferEnabled;
     private int[] copyBuffer;
 
     private PropertyChangeListener layoutSizeListener;
@@ -103,6 +106,8 @@
                 frame.updateClientCursor();
             }
         });
+        copyBufferEnabled = "true".equals(AccessController.
+            doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true")));
     }
 
     /**
@@ -144,7 +149,8 @@
             }
             Point p = SwingUtilities.convertPoint(c, x, y, jlf);
             Rectangle r = new Rectangle(p.x, p.y, w, h).intersection(
-                    new Rectangle(0, 0, bbImage.getWidth(), bbImage.getHeight()));
+                    new Rectangle(0, 0, bbImage.getWidth() / scaleFactor,
+                                  bbImage.getHeight() / scaleFactor));
 
             if (!r.isEmpty()) {
                 notifyImageUpdated(r.x, r.y, r.width, r.height);
@@ -198,6 +204,7 @@
         g.setBackground(getBackground());
         g.setColor(getForeground());
         g.setFont(getFont());
+        g.scale(scaleFactor, scaleFactor);
         return g;
     }
 
@@ -221,7 +228,39 @@
         if (content != null) content.focusUngrabbed();
     }
 
-    private void syncCopyBuffer(boolean reset, int x, int y, int w, int h) {
+    @Override
+    public int getScaleFactor() {
+        return scaleFactor;
+    }
+
+    @Override
+    public void notifyDisplayChanged(final int scaleFactor) {
+        if (scaleFactor != this.scaleFactor) {
+            if (!copyBufferEnabled) content.paintLock();
+            try {
+                if (bbImage != null) {
+                    resizeBuffer(getWidth(), getHeight(), scaleFactor);
+                }
+            } finally {
+                if (!copyBufferEnabled) content.paintUnlock();
+            }
+            this.scaleFactor = scaleFactor;
+        }
+        if (getPeer() instanceof DisplayChangedListener) {
+            ((DisplayChangedListener)getPeer()).displayChanged();
+        }
+        repaint();
+    }
+
+    @Override
+    public void addNotify() {
+        super.addNotify();
+        if (getPeer() instanceof DisplayChangedListener) {
+            ((DisplayChangedListener)getPeer()).displayChanged();
+        }
+    }
+
+    private void syncCopyBuffer(boolean reset, int x, int y, int w, int h, int scale) {
         content.paintLock();
         try {
             int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
@@ -230,6 +269,11 @@
             }
             int linestride = bbImage.getWidth();
 
+            x *= scale;
+            y *= scale;
+            w *= scale;
+            h *= scale;
+
             for (int i=0; i<h; i++) {
                 int from = (y + i) * linestride + x;
                 System.arraycopy(srcBuffer, from, copyBuffer, from, w);
@@ -241,7 +285,7 @@
 
     private void notifyImageUpdated(int x, int y, int width, int height) {
         if (copyBufferEnabled) {
-            syncCopyBuffer(false, x, y, width, height);
+            syncCopyBuffer(false, x, y, width, height, scaleFactor);
         }
         content.imageUpdated(x, y, width, height);
     }
@@ -269,7 +313,8 @@
                     EventQueue.invokeLater(new Runnable() {
                         @Override
                         public void run() {
-                            notifyImageUpdated(clip.x, clip.y, clip.width, clip.height);
+                            Rectangle c = contentPane.getBounds().intersection(clip);
+                            notifyImageUpdated(c.x, c.y, c.width, c.height);
                         }
                     });
                 } finally {
@@ -323,48 +368,37 @@
             content.paintLock();
         }
         try {
-            if ((bbImage == null) || (width != bbImage.getWidth()) || (height != bbImage.getHeight())) {
-                boolean createBB = true;
-                int newW = width;
-                int newH = height;
-                if (bbImage != null) {
-                    int oldW = bbImage.getWidth();
-                    int oldH = bbImage.getHeight();
-                    if ((oldW >= newW) && (oldH >= newH)) {
-                        createBB = false;
-                    } else {
-                        if (oldW >= newW) {
-                            newW = oldW;
+            boolean createBB = (bbImage == null);
+            int newW = width;
+            int newH = height;
+            if (bbImage != null) {
+                int imgWidth = bbImage.getWidth() / scaleFactor;
+                int imgHeight = bbImage.getHeight() / scaleFactor;
+                if (width != imgWidth || height != imgHeight) {
+                    createBB = true;
+                    if (bbImage != null) {
+                        int oldW = imgWidth;
+                        int oldH = imgHeight;
+                        if ((oldW >= newW) && (oldH >= newH)) {
+                            createBB = false;
                         } else {
-                            newW = Math.max((int)(oldW * 1.2), width);
-                        }
-                        if (oldH >= newH) {
-                            newH = oldH;
-                        } else {
-                            newH = Math.max((int)(oldH * 1.2), height);
+                            if (oldW >= newW) {
+                                newW = oldW;
+                            } else {
+                                newW = Math.max((int)(oldW * 1.2), width);
+                            }
+                            if (oldH >= newH) {
+                                newH = oldH;
+                            } else {
+                                newH = Math.max((int)(oldH * 1.2), height);
+                            }
                         }
                     }
                 }
-                if (createBB) {
-                    BufferedImage oldBB = bbImage;
-                    bbImage = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB_PRE);
-                    if (oldBB != null) {
-                        Graphics g = bbImage.getGraphics();
-                        try {
-                            g.drawImage(oldBB, 0, 0, newW, newH, null);
-                        } finally {
-                            g.dispose();
-                            oldBB.flush();
-                        }
-                    }
-                    int[] pixels = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
-                    if (copyBufferEnabled) {
-                        syncCopyBuffer(true, 0, 0, width, height);
-                        pixels = copyBuffer;
-                    }
-                    content.imageBufferReset(pixels, 0, 0, width, height, bbImage.getWidth());
-                    return;
-                }
+            }
+            if (createBB) {
+                resizeBuffer(newW, newH, scaleFactor);
+                return;
             }
             content.imageReshaped(0, 0, width, height);
 
@@ -375,6 +409,18 @@
         }
     }
 
+    private void resizeBuffer(int width, int height, int newScaleFactor) {
+            bbImage = new BufferedImage(width*newScaleFactor,height*newScaleFactor,
+                                        BufferedImage.TYPE_INT_ARGB_PRE);
+        int[] pixels= ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
+        if (copyBufferEnabled) {
+            syncCopyBuffer(true, 0, 0, width, height, newScaleFactor);
+            pixels = copyBuffer;
+        }
+        content.imageBufferReset(pixels, 0, 0, width, height,
+                                 width * newScaleFactor, newScaleFactor);
+    }
+
     @Override
     public JRootPane getRootPane() {
         return rootPane;
--- a/jdk/src/share/classes/sun/swing/LightweightContent.java	Thu Jun 12 00:19:00 2014 +0400
+++ b/jdk/src/share/classes/sun/swing/LightweightContent.java	Thu Jun 12 00:32:33 2014 +0400
@@ -85,31 +85,53 @@
      * {@code JLightweightFrame} calls this method to notify the client
      * application that a new data buffer has been set as a content pixel
      * buffer. Typically this occurs when a buffer of a larger size is
-     * created in response to a content resize event. The method reports
-     * a reference to the pixel data buffer, the content image bounds
-     * within the buffer and the line stride of the buffer. These values
-     * have the following correlation.
+     * created in response to a content resize event.
      * <p>
-     * The {@code width} and {@code height} matches the size of the content
+     * The method reports a reference to the pixel data buffer, the content
+     * image bounds within the buffer and the line stride of the buffer.
+     * These values have the following correlation.
+     * The {@code width} and {@code height} matches the layout size of the content
      * (the component returned from the {@link #getComponent} method). The
      * {@code x} and {@code y} is the origin of the content, {@code (0, 0)}
-     * in the coordinate space of the content, appearing at
-     * {@code data[y * linestride + x]} in the buffer. All indices
-     * {@code data[(y + j) * linestride + (x + i)]} where
-     * {@code (0 <= i < width)} and {@code (0 <= j < height)} will represent
-     * valid pixel data, {@code (i, j)} in the coordinate space of the content.
+     * in the layout coordinate space of the content, appearing at
+     * {@code data[y * scale * linestride + x * scale]} in the buffer.
+     * A pixel with indices {@code (i, j)}, where {@code (0 <= i < width)} and
+     * {@code (0 <= j < height)}, in the layout coordinate space of the content
+     * is represented by a {@code scale^2} square of pixels in the physical
+     * coordinate space of the buffer. The top-left corner of the square has the
+     * following physical coordinate in the buffer:
+     * {@code data[(y + j) * scale * linestride + (x + i) * scale]}.
      *
      * @param data the content pixel data buffer of INT_ARGB_PRE type
-     * @param x the x coordinate of the image
-     * @param y the y coordinate of the image
-     * @param width the width of the image
-     * @param height the height of the image
+     * @param x the logical x coordinate of the image
+     * @param y the logical y coordinate of the image
+     * @param width the logical width of the image
+     * @param height the logical height of the image
      * @param linestride the line stride of the pixel buffer
+     * @param scale the scale factor of the pixel buffer
      */
-    public void imageBufferReset(int[] data,
+    default public void imageBufferReset(int[] data,
                                  int x, int y,
                                  int width, int height,
-                                 int linestride);
+                                 int linestride,
+                                 int scale)
+    {
+        imageBufferReset(data, x, y, width, height, linestride);
+    }
+
+    /**
+     * The default implementation for #imageBufferReset uses a hard-coded value
+     * of 1 for the scale factor. Both the old and the new methods provide
+     * default implementations in order to allow a client application to run
+     * with any JDK version without breaking backward compatibility.
+     */
+    default public void imageBufferReset(int[] data,
+                                 int x, int y,
+                                 int width, int height,
+                                 int linestride)
+    {
+        imageBufferReset(data, x, y, width, height, linestride, 1);
+    }
 
     /**
      * {@code JLightweightFrame} calls this method to notify the client
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Graphics2D/ScaledCopyArea/ScaledCopyArea.java	Thu Jun 12 00:32:33 2014 +0400
@@ -0,0 +1,60 @@
+/*
+ * 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.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * @test
+ * @bug 8029455
+ * @summary Tests that copyarea on offscreen images works as expected when
+ *          scaled transform is set
+ * @run main ScaledCopyArea
+ */
+public final class ScaledCopyArea {
+
+    public static void main(final String[] args) {
+        final BufferedImage bi = new BufferedImage(100, 300,
+                                                   BufferedImage.TYPE_INT_RGB);
+        final Graphics2D g = bi.createGraphics();
+        g.scale(2, 2);
+        g.setColor(Color.RED);
+        g.fillRect(0, 0, 100, 300);
+        g.setColor(Color.GREEN);
+        g.fillRect(0, 100, 100, 100);
+        g.copyArea(0, 100, 100, 100, 0, -100);
+        g.dispose();
+        for (int x = 0; x < 100; ++x) {
+            for (int y = 0; y < 100; ++y) {
+                final int actual = bi.getRGB(x, y);
+                final int exp = Color.GREEN.getRGB();
+                if (actual != exp) {
+                    System.err.println("Expected:" + Integer.toHexString(exp));
+                    System.err.println("Actual:" + Integer.toHexString(actual));
+                    throw new RuntimeException("Test " + "failed");
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/swing/JFrame/HangNonVolatileBuffer/HangNonVolatileBuffer.java	Thu Jun 12 00:32:33 2014 +0400
@@ -0,0 +1,53 @@
+/*
+ * 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.lang.reflect.InvocationTargetException;
+
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+/**
+ * @test
+ * @bug 8029455
+ * @summary Swing should not hang if non-volatile image is used as a backbuffer.
+ * @run main/othervm -Dswing.volatileImageBufferEnabled=false HangNonVolatileBuffer
+ */
+public final class HangNonVolatileBuffer {
+
+    private static JFrame f;
+
+    public static void main(final String[] args)
+            throws InvocationTargetException, InterruptedException {
+        SwingUtilities.invokeAndWait(() -> {
+            f = new JFrame("JFrame");
+            f.setSize(300, 300);
+            f.setLocationRelativeTo(null);
+            f.setVisible(true);
+        });
+        SwingUtilities.invokeAndWait(() -> {
+            // flush the EDT
+        });
+        Thread.sleep(1000);
+        SwingUtilities.invokeAndWait(f::dispose);
+    }
+}