1/*
2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.awt.BasicStroke;
25import java.awt.BorderLayout;
26import java.awt.Color;
27import java.awt.Dimension;
28import java.awt.Graphics;
29import java.awt.Graphics2D;
30import java.awt.GraphicsConfiguration;
31import java.awt.GraphicsDevice;
32import java.awt.GraphicsEnvironment;
33import java.awt.GridBagConstraints;
34import java.awt.GridBagLayout;
35import java.awt.Image;
36import java.awt.Rectangle;
37import java.awt.Robot;
38import java.awt.event.ActionEvent;
39import java.awt.event.ActionListener;
40import java.awt.event.WindowAdapter;
41import java.awt.event.WindowEvent;
42import java.awt.geom.AffineTransform;
43import java.awt.image.BufferedImage;
44import java.awt.image.MultiResolutionImage;
45import java.util.List;
46import java.util.concurrent.CountDownLatch;
47import java.util.concurrent.TimeUnit;
48import javax.swing.JButton;
49import javax.swing.JFrame;
50import javax.swing.JPanel;
51import javax.swing.JTextArea;
52import javax.swing.SwingUtilities;
53
54/* @test
55 * @bug 8173972
56 * @summary createScreenCapture not working as expected on multimonitor setup
57 *          with different DPI scales.
58 * @run main/manual/othervm RobotMultiDPIScreenTest
59 */
60public class RobotMultiDPIScreenTest {
61
62    private static volatile boolean testResult = false;
63    private static volatile CountDownLatch countDownLatch;
64    private static JFrame mainFrame;
65    private static Rectangle maxBounds;
66    private static Rectangle[] screenBounds;
67    private static double[][] scales;
68
69    private static final String INSTRUCTIONS = "INSTRUCTIONS:\n"
70            + "Verify that screenshots are properly taken from monitors"
71            + " with different DPI.\n"
72            + "\n"
73            + "The test is applicable for a multi-monitor system where displays"
74            + " are configured to have different DPI\n"
75            + "\n"
76            + "1. Press Take Screenshots button\n"
77            + "Check that screenshots shown on the panel are properly taken.\n";
78
79    public static void main(String args[]) throws Exception {
80
81        countDownLatch = new CountDownLatch(1);
82        SwingUtilities.invokeLater(RobotMultiDPIScreenTest::createUI);
83        countDownLatch.await(15, TimeUnit.MINUTES);
84        if (!testResult) {
85            throw new RuntimeException("Test fails!");
86        }
87    }
88
89    private static void createUI() {
90
91        initScreenBounds();
92
93        mainFrame = new JFrame("DPI change test");
94        GridBagLayout layout = new GridBagLayout();
95        JPanel mainControlPanel = new JPanel(layout);
96        JPanel resultButtonPanel = new JPanel(layout);
97
98        GridBagConstraints gbc = new GridBagConstraints();
99
100        JPanel testPanel = new JPanel(new BorderLayout());
101
102        final BufferedImage screensImage = getScreenImages();
103        final JPanel screensPanel = new JPanel() {
104
105            @Override
106            public void paint(Graphics g) {
107                super.paint(g);
108                g.drawImage(screensImage, 0, 0, getWidth(), getHeight(), this);
109            }
110        };
111
112        screensPanel.setPreferredSize(new Dimension(400, 200));
113
114        JButton frameButton = new JButton("Take Screenshots");
115        frameButton.addActionListener((e) -> {
116
117            try {
118                Robot robot = new Robot();
119                Graphics2D g = screensImage.createGraphics();
120                g.translate(-maxBounds.x, -maxBounds.y);
121
122                for (Rectangle rect : screenBounds) {
123                    MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(rect);
124
125                    List<Image> resolutionVariants = mrImage.getResolutionVariants();
126                    Image rvImage = resolutionVariants.get(resolutionVariants.size() - 1);
127                    g.drawImage(rvImage, rect.x, rect.y, rect.width, rect.height, null);
128                }
129
130                g.dispose();
131                screensPanel.repaint();
132            } catch (Exception ex) {
133                throw new RuntimeException(ex);
134            }
135        });
136
137        testPanel.add(screensPanel, BorderLayout.CENTER);
138        testPanel.add(frameButton, BorderLayout.SOUTH);
139
140        gbc.gridx = 0;
141        gbc.gridy = 0;
142        gbc.fill = GridBagConstraints.HORIZONTAL;
143        mainControlPanel.add(testPanel, gbc);
144
145        JTextArea instructionTextArea = new JTextArea();
146        instructionTextArea.setText(INSTRUCTIONS);
147        instructionTextArea.setEditable(false);
148        instructionTextArea.setBackground(Color.white);
149
150        gbc.gridx = 0;
151        gbc.gridy = 1;
152        gbc.fill = GridBagConstraints.HORIZONTAL;
153        mainControlPanel.add(instructionTextArea, gbc);
154
155        JButton passButton = new JButton("Pass");
156        passButton.setActionCommand("Pass");
157        passButton.addActionListener((ActionEvent e) -> {
158            testResult = true;
159            disposeFrames();
160            countDownLatch.countDown();
161
162        });
163
164        JButton failButton = new JButton("Fail");
165        failButton.setActionCommand("Fail");
166        failButton.addActionListener(new ActionListener() {
167            @Override
168            public void actionPerformed(ActionEvent e) {
169                disposeFrames();
170                countDownLatch.countDown();
171            }
172        });
173
174        gbc.gridx = 0;
175        gbc.gridy = 0;
176        resultButtonPanel.add(passButton, gbc);
177
178        gbc.gridx = 1;
179        gbc.gridy = 0;
180        resultButtonPanel.add(failButton, gbc);
181
182        gbc.gridx = 0;
183        gbc.gridy = 2;
184        mainControlPanel.add(resultButtonPanel, gbc);
185
186        mainFrame.add(mainControlPanel);
187        mainFrame.pack();
188
189        mainFrame.addWindowListener(new WindowAdapter() {
190
191            @Override
192            public void windowClosing(WindowEvent e) {
193                disposeFrames();
194                countDownLatch.countDown();
195            }
196        });
197        mainFrame.setVisible(true);
198    }
199
200    private static void disposeFrames() {
201        if (mainFrame != null && mainFrame.isVisible()) {
202            mainFrame.dispose();
203        }
204    }
205
206    static void initScreenBounds() {
207
208        GraphicsDevice[] devices = GraphicsEnvironment
209                .getLocalGraphicsEnvironment()
210                .getScreenDevices();
211
212        screenBounds = new Rectangle[devices.length];
213        scales = new double[devices.length][2];
214        for (int i = 0; i < devices.length; i++) {
215            GraphicsConfiguration gc = devices[i].getDefaultConfiguration();
216            screenBounds[i] = gc.getBounds();
217            AffineTransform tx = gc.getDefaultTransform();
218            scales[i][0] = tx.getScaleX();
219            scales[i][1] = tx.getScaleY();
220        }
221
222        maxBounds = screenBounds[0];
223        for (int i = 0; i < screenBounds.length; i++) {
224            maxBounds = maxBounds.union(screenBounds[i]);
225        }
226    }
227
228    private static Rectangle getCenterRect(Rectangle rect) {
229        int w = rect.width / 2;
230        int h = rect.height / 2;
231        int x = rect.x + w / 2;
232        int y = rect.y + h / 2;
233
234        return new Rectangle(x, y, w, h);
235    }
236
237    static BufferedImage getScreenImages() {
238
239        final BufferedImage img = new BufferedImage(maxBounds.width, maxBounds.height, BufferedImage.TYPE_INT_RGB);
240        Graphics2D g = img.createGraphics();
241        g.setColor(Color.WHITE);
242        g.fillRect(0, 0, maxBounds.width, maxBounds.height);
243        g.translate(-maxBounds.x, -maxBounds.y);
244
245        g.setStroke(new BasicStroke(8f));
246        for (int i = 0; i < screenBounds.length; i++) {
247            Rectangle r = screenBounds[i];
248            g.setColor(Color.BLACK);
249            g.drawRect(r.x, r.y, r.width, r.height);
250
251            g.setColor(Color.ORANGE);
252            Rectangle cr = getCenterRect(r);
253            g.fillRect(cr.x, cr.y, cr.width, cr.height);
254
255            double scaleX = scales[i][0];
256            double scaleY = scales[i][1];
257            float fontSize = maxBounds.height / 7;
258            g.setFont(g.getFont().deriveFont(fontSize));
259            g.setColor(Color.BLUE);
260            g.drawString(String.format("Scale: [%2.1f, %2.1f]", scaleX, scaleY),
261                    r.x + r.width / 8, r.y + r.height / 2);
262
263        }
264
265        g.dispose();
266
267        return img;
268    }
269}
270