8176097: Window set location to a display with different DPI does not properly work
8173972: createScreenCapture not working as expected on multimonitor setup with different DPI scales
Reviewed-by: serb, azvegint
--- a/jdk/src/java.desktop/share/classes/java/awt/Robot.java Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/src/java.desktop/share/classes/java/awt/Robot.java Mon Mar 06 08:06:54 2017 +0300
@@ -41,6 +41,7 @@
import sun.awt.ComponentFactory;
import sun.awt.SunToolkit;
import sun.awt.image.SunWritableRaster;
+import sun.swing.SwingUtilities2;
/**
* This class is used to generate native system input events
@@ -499,9 +500,15 @@
// need to sync the toolkit prior to grabbing the pixels since in some
// cases rendering to the screen may be delayed
Toolkit.getDefaultToolkit().sync();
- AffineTransform tx = GraphicsEnvironment.
- getLocalGraphicsEnvironment().getDefaultScreenDevice().
- getDefaultConfiguration().getDefaultTransform();
+
+ GraphicsConfiguration gc = GraphicsEnvironment
+ .getLocalGraphicsEnvironment()
+ .getDefaultScreenDevice().
+ getDefaultConfiguration();
+ gc = SwingUtilities2.getGraphicsConfigurationAtPoint(
+ gc, screenRect.getCenterX(), screenRect.getCenterY());
+
+ AffineTransform tx = gc.getDefaultTransform();
double uiScaleX = tx.getScaleX();
double uiScaleY = tx.getScaleY();
int pixels[];
--- a/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java Mon Mar 06 08:06:54 2017 +0300
@@ -2197,6 +2197,35 @@
}
/**
+ *
+ * Returns the graphics configuration which bounds contain the given
+ * point
+ *
+ * @param current the default configuration which is checked in the first place
+ * @param x the x coordinate of the given point
+ * @param y the y coordinate of the given point
+ * @return the graphics configuration
+ */
+ public static GraphicsConfiguration getGraphicsConfigurationAtPoint(GraphicsConfiguration current, double x, double y) {
+
+ if (current.getBounds().contains(x, y)) {
+ return current;
+ }
+
+ GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ GraphicsDevice[] devices = env.getScreenDevices();
+
+ for (GraphicsDevice device : devices) {
+ GraphicsConfiguration config = device.getDefaultConfiguration();
+ if (config.getBounds().contains(x, y)) {
+ return config;
+ }
+ }
+
+ return current;
+ }
+
+ /**
* Used to listen to "blit" repaints in RepaintManager.
*/
public interface RepaintListener {
--- a/jdk/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java Mon Mar 06 08:06:54 2017 +0300
@@ -38,6 +38,7 @@
import sun.awt.*;
import sun.java2d.pipe.Region;
+import sun.swing.SwingUtilities2;
public class WWindowPeer extends WPanelPeer implements WindowPeer,
DisplayChangedListener
@@ -630,9 +631,42 @@
sysW = width;
sysH = height;
+ int cx = x + width / 2;
+ int cy = y + height / 2;
+ GraphicsConfiguration current = getGraphicsConfiguration();
+ GraphicsConfiguration other = SwingUtilities2.getGraphicsConfigurationAtPoint(current, cx, cy);
+ if (!current.equals(other)) {
+ AffineTransform tx = other.getDefaultTransform();
+ double otherScaleX = tx.getScaleX();
+ double otherScaleY = tx.getScaleY();
+ initScales();
+ if (scaleX != otherScaleX || scaleY != otherScaleY) {
+ x = (int) Math.floor(x * otherScaleX / scaleX);
+ y = (int) Math.floor(y * otherScaleY / scaleY);
+ }
+ }
+
super.setBounds(x, y, width, height, op);
}
+ private final void initScales() {
+
+ if (scaleX >= 1 && scaleY >= 1) {
+ return;
+ }
+
+ GraphicsConfiguration gc = getGraphicsConfiguration();
+ if (gc instanceof Win32GraphicsConfig) {
+ Win32GraphicsDevice gd = ((Win32GraphicsConfig) gc).getDevice();
+ scaleX = gd.getDefaultScaleX();
+ scaleY = gd.getDefaultScaleY();
+ } else {
+ AffineTransform tx = gc.getDefaultTransform();
+ scaleX = (float) tx.getScaleX();
+ scaleY = (float) tx.getScaleY();
+ }
+ }
+
@Override
public void print(Graphics g) {
// We assume we print the whole frame,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/Robot/HiDPIScreenCapture/RobotMultiDPIScreenTest.java Mon Mar 06 08:06:54 2017 +0300
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2017, 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.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.MultiResolutionImage;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+
+/* @test
+ * @bug 8173972
+ * @summary createScreenCapture not working as expected on multimonitor setup
+ * with different DPI scales.
+ * @run main/manual/othervm RobotMultiDPIScreenTest
+ */
+public class RobotMultiDPIScreenTest {
+
+ private static volatile boolean testResult = false;
+ private static volatile CountDownLatch countDownLatch;
+ private static JFrame mainFrame;
+ private static Rectangle maxBounds;
+ private static Rectangle[] screenBounds;
+ private static double[][] scales;
+
+ private static final String INSTRUCTIONS = "INSTRUCTIONS:\n"
+ + "Verify that screenshots are properly taken from monitors"
+ + " with different DPI.\n"
+ + "\n"
+ + "The test is applicable for a multi-monitor system where displays"
+ + " are configured to have different DPI\n"
+ + "\n"
+ + "1. Press Take Screenshots button\n"
+ + "Check that screenshots shown on the panel are properly taken.\n";
+
+ public static void main(String args[]) throws Exception {
+
+ countDownLatch = new CountDownLatch(1);
+ SwingUtilities.invokeLater(RobotMultiDPIScreenTest::createUI);
+ countDownLatch.await(15, TimeUnit.MINUTES);
+ if (!testResult) {
+ throw new RuntimeException("Test fails!");
+ }
+ }
+
+ private static void createUI() {
+
+ initScreenBounds();
+
+ mainFrame = new JFrame("DPI change test");
+ GridBagLayout layout = new GridBagLayout();
+ JPanel mainControlPanel = new JPanel(layout);
+ JPanel resultButtonPanel = new JPanel(layout);
+
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ JPanel testPanel = new JPanel(new BorderLayout());
+
+ final BufferedImage screensImage = getScreenImages();
+ final JPanel screensPanel = new JPanel() {
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ g.drawImage(screensImage, 0, 0, getWidth(), getHeight(), this);
+ }
+ };
+
+ screensPanel.setPreferredSize(new Dimension(400, 200));
+
+ JButton frameButton = new JButton("Take Screenshots");
+ frameButton.addActionListener((e) -> {
+
+ try {
+ Robot robot = new Robot();
+ Graphics2D g = screensImage.createGraphics();
+ g.translate(-maxBounds.x, -maxBounds.y);
+
+ for (Rectangle rect : screenBounds) {
+ MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(rect);
+
+ List<Image> resolutionVariants = mrImage.getResolutionVariants();
+ Image rvImage = resolutionVariants.get(resolutionVariants.size() - 1);
+ g.drawImage(rvImage, rect.x, rect.y, rect.width, rect.height, null);
+ }
+
+ g.dispose();
+ screensPanel.repaint();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ });
+
+ testPanel.add(screensPanel, BorderLayout.CENTER);
+ testPanel.add(frameButton, BorderLayout.SOUTH);
+
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ mainControlPanel.add(testPanel, gbc);
+
+ JTextArea instructionTextArea = new JTextArea();
+ instructionTextArea.setText(INSTRUCTIONS);
+ instructionTextArea.setEditable(false);
+ instructionTextArea.setBackground(Color.white);
+
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ mainControlPanel.add(instructionTextArea, gbc);
+
+ JButton passButton = new JButton("Pass");
+ passButton.setActionCommand("Pass");
+ passButton.addActionListener((ActionEvent e) -> {
+ testResult = true;
+ disposeFrames();
+ countDownLatch.countDown();
+
+ });
+
+ JButton failButton = new JButton("Fail");
+ failButton.setActionCommand("Fail");
+ failButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ disposeFrames();
+ countDownLatch.countDown();
+ }
+ });
+
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ resultButtonPanel.add(passButton, gbc);
+
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ resultButtonPanel.add(failButton, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy = 2;
+ mainControlPanel.add(resultButtonPanel, gbc);
+
+ mainFrame.add(mainControlPanel);
+ mainFrame.pack();
+
+ mainFrame.addWindowListener(new WindowAdapter() {
+
+ @Override
+ public void windowClosing(WindowEvent e) {
+ disposeFrames();
+ countDownLatch.countDown();
+ }
+ });
+ mainFrame.setVisible(true);
+ }
+
+ private static void disposeFrames() {
+ if (mainFrame != null && mainFrame.isVisible()) {
+ mainFrame.dispose();
+ }
+ }
+
+ static void initScreenBounds() {
+
+ GraphicsDevice[] devices = GraphicsEnvironment
+ .getLocalGraphicsEnvironment()
+ .getScreenDevices();
+
+ screenBounds = new Rectangle[devices.length];
+ scales = new double[devices.length][2];
+ for (int i = 0; i < devices.length; i++) {
+ GraphicsConfiguration gc = devices[i].getDefaultConfiguration();
+ screenBounds[i] = gc.getBounds();
+ AffineTransform tx = gc.getDefaultTransform();
+ scales[i][0] = tx.getScaleX();
+ scales[i][1] = tx.getScaleY();
+ }
+
+ maxBounds = screenBounds[0];
+ for (int i = 0; i < screenBounds.length; i++) {
+ maxBounds = maxBounds.union(screenBounds[i]);
+ }
+ }
+
+ private static Rectangle getCenterRect(Rectangle rect) {
+ int w = rect.width / 2;
+ int h = rect.height / 2;
+ int x = rect.x + w / 2;
+ int y = rect.y + h / 2;
+
+ return new Rectangle(x, y, w, h);
+ }
+
+ static BufferedImage getScreenImages() {
+
+ final BufferedImage img = new BufferedImage(maxBounds.width, maxBounds.height, BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = img.createGraphics();
+ g.setColor(Color.WHITE);
+ g.fillRect(0, 0, maxBounds.width, maxBounds.height);
+ g.translate(-maxBounds.x, -maxBounds.y);
+
+ g.setStroke(new BasicStroke(8f));
+ for (int i = 0; i < screenBounds.length; i++) {
+ Rectangle r = screenBounds[i];
+ g.setColor(Color.BLACK);
+ g.drawRect(r.x, r.y, r.width, r.height);
+
+ g.setColor(Color.ORANGE);
+ Rectangle cr = getCenterRect(r);
+ g.fillRect(cr.x, cr.y, cr.width, cr.height);
+
+ double scaleX = scales[i][0];
+ double scaleY = scales[i][1];
+ float fontSize = maxBounds.height / 7;
+ g.setFont(g.getFont().deriveFont(fontSize));
+ g.setColor(Color.BLUE);
+ g.drawString(String.format("Scale: [%2.1f, %2.1f]", scaleX, scaleY),
+ r.x + r.width / 8, r.y + r.height / 2);
+
+ }
+
+ g.dispose();
+
+ return img;
+ }
+}
--- a/jdk/test/java/awt/Window/WindowResizingOnDPIChanging/WindowResizingOnSetLocationTest.java Sun Mar 05 23:02:04 2017 +0300
+++ b/jdk/test/java/awt/Window/WindowResizingOnDPIChanging/WindowResizingOnSetLocationTest.java Mon Mar 06 08:06:54 2017 +0300
@@ -21,7 +21,10 @@
* questions.
*/
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
import java.awt.Color;
+import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
@@ -49,15 +52,15 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
+import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
/* @test
- * @bug 8175293
- * @summary HiDPI (Windows): Swing components have incorrect sizes after
- * changing display resolution
+ * @bug 8175293 8176097
+ * @summary Window set location to a display with different DPI does not properly work
* @run main/manual/othervm WindowResizingOnSetLocationTest
*/
public class WindowResizingOnSetLocationTest {
@@ -66,6 +69,10 @@
private static volatile CountDownLatch countDownLatch;
private static TestFrame frame;
private static JFrame mainFrame;
+ private static Rectangle[] screenBounds;
+ private static double[][] scales;
+ private static int screen1 = -1;
+ private static int screen2 = -1;
private static final String INSTRUCTIONS = "INSTRUCTIONS:\n"
+ "Verify that a window is properly resized after setting the location"
@@ -75,7 +82,10 @@
+ " are configured to have different DPI\n"
+ "\n"
+ "1. Press Show Frame button\n"
- + "The frame appear.\n"
+ + " (note that the button is disabled in case there are no two monitors"
+ + " with different DPI)\n"
+ + "The frame should appear in the center of the display (either"
+ + " on the first or on the second).\n"
+ "2. Check that the string \"scales [ScaleX, ScaleY]\" is painted on the window"
+ " where ScaleX and ScaleY are the scales for current display.\n"
+ "The scales are calculated as DPI / 96 and are 1 for the DPI value 96"
@@ -86,7 +96,6 @@
+ " to show the right display scales.\n"
+ "6. Check that the window is properly resized.\n"
+ "7. Check that the window is properly repainted and does not contain drawing artifacts\n"
- + "Try different display positions (left, right, top, bottom).\n"
+ "If all tests are passed, press PASS, else press FAIL.\n";
public static void main(String args[]) throws Exception {
@@ -101,6 +110,8 @@
private static void createUI() {
+ initScreenBounds();
+
mainFrame = new JFrame("DPI change test");
GridBagLayout layout = new GridBagLayout();
JPanel mainControlPanel = new JPanel(layout);
@@ -108,20 +119,22 @@
GridBagConstraints gbc = new GridBagConstraints();
- JPanel testPanel = new JPanel(new FlowLayout());
+ JPanel testPanel = new JPanel(new BorderLayout());
JButton frameButton = new JButton("Show Frame");
frameButton.addActionListener((e) -> {
- int x = 20;
- int y = 10;
- int w = 400;
- int h = 300;
+ GraphicsConfiguration gc = GraphicsEnvironment
+ .getLocalGraphicsEnvironment()
+ .getScreenDevices()[screen1]
+ .getDefaultConfiguration();
- frame = new TestFrame(w, h);
- frame.setLocation(x, y);
+ Rectangle rect = getCenterRect(screenBounds[screen2]);
+ frame = new TestFrame(gc, rect);
frame.setVisible(true);
});
- testPanel.add(frameButton);
+ frameButton.setEnabled(screen1 != -1 && screen2 != -1);
+ testPanel.add(getDisplaysComponent(), BorderLayout.CENTER);
+ testPanel.add(frameButton, BorderLayout.SOUTH);
gbc.gridx = 0;
gbc.gridy = 0;
@@ -193,14 +206,99 @@
}
}
+ static void initScreenBounds() {
+
+ GraphicsDevice[] devices = GraphicsEnvironment
+ .getLocalGraphicsEnvironment()
+ .getScreenDevices();
+
+ screenBounds = new Rectangle[devices.length];
+ scales = new double[devices.length][2];
+ for (int i = 0; i < devices.length; i++) {
+ GraphicsConfiguration gc = devices[i].getDefaultConfiguration();
+ screenBounds[i] = gc.getBounds();
+ AffineTransform tx = gc.getDefaultTransform();
+ scales[i][0] = tx.getScaleX();
+ scales[i][1] = tx.getScaleY();
+ }
+
+ for (int i = 0; i < devices.length; i++) {
+ for (int j = i + 1; j < devices.length; j++) {
+ if (scales[i][0] != scales[j][0] || scales[i][1] != scales[j][1]) {
+ screen1 = i;
+ screen2 = j;
+ }
+ }
+ }
+ }
+
+ private static Rectangle getCenterRect(Rectangle rect) {
+ int w = rect.width / 2;
+ int h = rect.height / 2;
+ int x = rect.x + w / 2;
+ int y = rect.y + h / 2;
+
+ return new Rectangle(x, y, w, h);
+ }
+
+ static JComponent getDisplaysComponent() {
+
+ Rectangle rect = screenBounds[0];
+ for (int i = 0; i < screenBounds.length; i++) {
+ rect = rect.union(screenBounds[i]);
+ }
+
+ final BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
+ Graphics2D g = img.createGraphics();
+ g.setColor(Color.WHITE);
+ g.fillRect(0, 0, rect.width, rect.height);
+ g.translate(-rect.x, -rect.y);
+
+ g.setStroke(new BasicStroke(8f));
+ for (int i = 0; i < screenBounds.length; i++) {
+ Rectangle r = screenBounds[i];
+ g.setColor(Color.BLACK);
+ g.drawRect(r.x, r.y, r.width, r.height);
+
+ g.setColor(Color.ORANGE);
+ Rectangle cr = getCenterRect(r);
+ g.fillRect(cr.x, cr.y, cr.width, cr.height);
+
+ double scaleX = scales[i][0];
+ double scaleY = scales[i][1];
+ float fontSize = rect.height / 7;
+ g.setFont(g.getFont().deriveFont(fontSize));
+ g.setColor(Color.BLUE);
+ g.drawString(String.format("Scale: [%2.1f, %2.1f]", scaleX, scaleY),
+ r.x + r.width / 8, r.y + r.height / 2);
+
+ }
+
+ g.dispose();
+
+ JPanel panel = new JPanel() {
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
+
+ }
+ };
+
+ panel.setPreferredSize(new Dimension(400, 200));
+
+ return panel;
+ }
+
static class TestFrame extends JFrame {
private final TestMultiResolutionImage mrImage;
- public TestFrame(int width, int height) throws HeadlessException {
- super("Test Frame");
- setSize(width, height);
- mrImage = new TestMultiResolutionImage(width, height);
+ public TestFrame(GraphicsConfiguration gc, Rectangle rect) throws HeadlessException {
+ super(gc);
+ setBounds(rect);
+ mrImage = new TestMultiResolutionImage(rect.width, rect.height);
JPanel panel = new JPanel(new FlowLayout()) {
@Override
@@ -217,29 +315,17 @@
JButton button = new JButton("Move to another display");
button.addActionListener((e) -> {
GraphicsConfiguration config = getGraphicsConfiguration();
- GraphicsDevice device = config.getDevice();
GraphicsDevice[] devices = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getScreenDevices();
- boolean found = false;
- for (GraphicsDevice dev : devices) {
- if (!dev.equals(device)) {
- found = true;
- Rectangle bounds = dev.getDefaultConfiguration().getBounds();
- AffineTransform tx = config.getDefaultTransform();
- int x = (int) Math.round(bounds.x / tx.getScaleX()) + 15;
- int y = (int) Math.round(bounds.y / tx.getScaleY()) + 15;
- frame.setLocation(x, y);
- break;
- }
- }
+ int index = devices[screen1].getDefaultConfiguration().equals(config)
+ ? screen2 : screen1;
- if (!found) {
- System.out.println("Another display not found!");
- }
+ Rectangle r = getCenterRect(screenBounds[index]);
+ frame.setBounds(r);
});
panel.add(button);