-
Bug
-
Resolution: Not an Issue
-
P3
-
9, 11.0.1, 12
ADDITIONAL SYSTEM INFORMATION :
Windows 10 Home, Windows 10 Pro
OpenJDK 10.0.2, OpenJDK 11.0.1
A DESCRIPTION OF THE PROBLEM :
The java.awt.Robot.createScreenCapture(Rectangle) method captures the screen pixels. It is expected that the resulting image will be equal to what the user sees on the screen. There is a number of automation and automated testing tools which rely on it.
This functionality doesn't work well on recent Java versions when the Windows desktop scaling is on. The image is very raw (pixelized). It looks like some kind of scaling is in progress under the hood. The impact is significant because many new laptops featuring high resolution (Full HD+) with small screen sizes come with the scaling on by default.
As the method works well on Java 8 I presume this is a bug introduced by the HiDPI support in Java 9/10/11. I personally reproduced it on OpenJDK 10.0.2 and OpenJDK 11.0.1 on two different machines (Windows 10 Home, Windows 10 Pro).
REGRESSION : Last worked in version 8u191
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Have a Windows 10 machine
2. Open the Windows' Settings window and select System->Display
3. Select any "Change the size of text, apps and other items" value other than 100%, for example 150%
4. Execute the java.awt.Robot.createScreenCapture(Rectangle) method and check the output.
You may take advantage of the attached code example featuring a simple JFrame showing the output of the createScreenCapture() method (on Java 8) or all images produced through the createScreenCapture() and createMultiResolutionScreenCapture() methods (Java 9+).
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The method must produce an image equivalent to what the user sees on the screen.
ACTUAL -
The image is of low quality (distorted/pixelized).
---------- BEGIN SOURCE ----------
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
public class RobotCapture extends WindowAdapter implements Runnable {
public static void main(String args[]) throws InterruptedException, InvocationTargetException {
SwingUtilities.invokeAndWait(new RobotCapture());
}
@Override
public void run() {
try {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice dev = ge.getDefaultScreenDevice();
Rectangle bounds = dev.getDefaultConfiguration().getBounds();
DisplayMode mode = dev.getDisplayMode();
StringBuilder title = new StringBuilder();
title.append(bounds.width).append('x').append(bounds.height);
if (bounds.width == mode.getWidth()) {
title.append(" (no scaling)");
} else {
title.append(" scaled to ").append(mode.getWidth()).append('x').append(mode.getHeight());
title.append(" (").append(100 * mode.getWidth() / bounds.width).append("%)");
}
Rectangle r = new Rectangle(0, 0, mode.getWidth(), mode.getHeight());
LinkedHashMap<String, Image> images = getImages(r, bounds);
JTabbedPane tabs = new JTabbedPane();
for (String tab : images.keySet()) {
JLabel l = new JLabel();
l.setIcon(new ImageIcon(images.get(tab)));
tabs.add(tab, new JScrollPane(l));
}
JFrame f = new JFrame();
f.setTitle(title.toString());
f.addWindowListener(this);
f.add(tabs);
f.pack();
Dimension d = f.getSize();
d.width -= 100;
d.height -= 80;
f.setSize(d);
f.setLocationRelativeTo(null);
f.setVisible(true);
} catch (AWTException ex) {
Logger.getLogger(RobotCapture.class.getName()).log(Level.SEVERE, null, ex);
}
}
private LinkedHashMap<String, Image> getImages(Rectangle nativeBounds, Rectangle scaledBounds) throws AWTException {
LinkedHashMap<String, Image> map = new LinkedHashMap<>();
Robot robot = new Robot();
map.put("createScreenCapture()", robot.createScreenCapture(nativeBounds));
// Call createMultiResolutionScreenCapture() introduced in Java 9
// through the Reflection API to keep backward compatibility with Java 8
try {
Object o;
Method m = robot.getClass().getMethod("createMultiResolutionScreenCapture", Rectangle.class);
o = m.invoke(robot, scaledBounds);
m = o.getClass().getMethod("getResolutionVariants");
List<Image> l = (List<Image>) m.invoke(o);
for (Image img : l) {
map.put("createMultiResolutionScreenCapture(" + img.getWidth(null) + "x" + img.getHeight(null) + ")", img);
}
} catch (Throwable e) {
System.err.println("MultiResolutionImage is not supported: " + e.getClass().getName() + ": " + e.getMessage());
}
return map;
}
public void windowClosing(WindowEvent we) {
System.exit(0);
}
}
---------- END SOURCE ----------
FREQUENCY : always
Windows 10 Home, Windows 10 Pro
OpenJDK 10.0.2, OpenJDK 11.0.1
A DESCRIPTION OF THE PROBLEM :
The java.awt.Robot.createScreenCapture(Rectangle) method captures the screen pixels. It is expected that the resulting image will be equal to what the user sees on the screen. There is a number of automation and automated testing tools which rely on it.
This functionality doesn't work well on recent Java versions when the Windows desktop scaling is on. The image is very raw (pixelized). It looks like some kind of scaling is in progress under the hood. The impact is significant because many new laptops featuring high resolution (Full HD+) with small screen sizes come with the scaling on by default.
As the method works well on Java 8 I presume this is a bug introduced by the HiDPI support in Java 9/10/11. I personally reproduced it on OpenJDK 10.0.2 and OpenJDK 11.0.1 on two different machines (Windows 10 Home, Windows 10 Pro).
REGRESSION : Last worked in version 8u191
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Have a Windows 10 machine
2. Open the Windows' Settings window and select System->Display
3. Select any "Change the size of text, apps and other items" value other than 100%, for example 150%
4. Execute the java.awt.Robot.createScreenCapture(Rectangle) method and check the output.
You may take advantage of the attached code example featuring a simple JFrame showing the output of the createScreenCapture() method (on Java 8) or all images produced through the createScreenCapture() and createMultiResolutionScreenCapture() methods (Java 9+).
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The method must produce an image equivalent to what the user sees on the screen.
ACTUAL -
The image is of low quality (distorted/pixelized).
---------- BEGIN SOURCE ----------
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
public class RobotCapture extends WindowAdapter implements Runnable {
public static void main(String args[]) throws InterruptedException, InvocationTargetException {
SwingUtilities.invokeAndWait(new RobotCapture());
}
@Override
public void run() {
try {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice dev = ge.getDefaultScreenDevice();
Rectangle bounds = dev.getDefaultConfiguration().getBounds();
DisplayMode mode = dev.getDisplayMode();
StringBuilder title = new StringBuilder();
title.append(bounds.width).append('x').append(bounds.height);
if (bounds.width == mode.getWidth()) {
title.append(" (no scaling)");
} else {
title.append(" scaled to ").append(mode.getWidth()).append('x').append(mode.getHeight());
title.append(" (").append(100 * mode.getWidth() / bounds.width).append("%)");
}
Rectangle r = new Rectangle(0, 0, mode.getWidth(), mode.getHeight());
LinkedHashMap<String, Image> images = getImages(r, bounds);
JTabbedPane tabs = new JTabbedPane();
for (String tab : images.keySet()) {
JLabel l = new JLabel();
l.setIcon(new ImageIcon(images.get(tab)));
tabs.add(tab, new JScrollPane(l));
}
JFrame f = new JFrame();
f.setTitle(title.toString());
f.addWindowListener(this);
f.add(tabs);
f.pack();
Dimension d = f.getSize();
d.width -= 100;
d.height -= 80;
f.setSize(d);
f.setLocationRelativeTo(null);
f.setVisible(true);
} catch (AWTException ex) {
Logger.getLogger(RobotCapture.class.getName()).log(Level.SEVERE, null, ex);
}
}
private LinkedHashMap<String, Image> getImages(Rectangle nativeBounds, Rectangle scaledBounds) throws AWTException {
LinkedHashMap<String, Image> map = new LinkedHashMap<>();
Robot robot = new Robot();
map.put("createScreenCapture()", robot.createScreenCapture(nativeBounds));
// Call createMultiResolutionScreenCapture() introduced in Java 9
// through the Reflection API to keep backward compatibility with Java 8
try {
Object o;
Method m = robot.getClass().getMethod("createMultiResolutionScreenCapture", Rectangle.class);
o = m.invoke(robot, scaledBounds);
m = o.getClass().getMethod("getResolutionVariants");
List<Image> l = (List<Image>) m.invoke(o);
for (Image img : l) {
map.put("createMultiResolutionScreenCapture(" + img.getWidth(null) + "x" + img.getHeight(null) + ")", img);
}
} catch (Throwable e) {
System.err.println("MultiResolutionImage is not supported: " + e.getClass().getName() + ": " + e.getMessage());
}
return map;
}
public void windowClosing(WindowEvent we) {
System.exit(0);
}
}
---------- END SOURCE ----------
FREQUENCY : always