I know that Java 9 fixed issues with screen scaling and resolution that were present with Swing in Java 8. However, I have a situation where the exact same code renders clearly if being run on Java 8 but blurrily if using any version of Java 9 and after (including the most recent LTS version Java 21 and Java 22). The catch is that this only happens on Windows if Windows display settings are set to 125% scaling.
These seem to be the conditions that need to all be the case simultaneously for this to happen:
- Windows is set to use 125% scaling
- Java 9 or newer JRE is being used to run the code
- The graphics are rendered via the
drawImage()
method specifically.
If I simply draw my lines and text on the Graphics2D
object directly (the one given to me by the JComponent
‘s paint(Graphics g)
function), then everything works fine even on Java 9+ with Windows 125% scaling. However, if I render the same exact graphics but use the drawImage()
method, then it is blurry.
So, if I run my code on Java 8 under any of the above conditions (Windows set at 100% or 125% scaling, using the Graphics
object directly or using drawImage()
, doesn’t matter), everything renders clearly (i.e., without any type of aliasing or dithering or whatever is going on).
If I run my code on any version of Java 9+ with Windows not at 125% scaling, everything renders clearly (same as on Java 8). If I run my code on any version of Java 9+ with Windows at 125% scaling but using the Graphics
object directly (and not drawImage()
), then everything renders clearly. It is only if I use the drawImage()
method that things go south.
I have a minimum reproducible example:
Here is how it renders on Java 22 with Windows at 125% scaling not using the drawImage()
method:
Here is how it renders on Java 22 with Windows at 125% scaling using the drawImage()
method:
However, if I use Java 8, then there is no difference at all between using drawImage()
vs. not using drawImage()
.
Here is the code:
public class MainWindow extends JFrame implements ActionListener {
DrawPanel dp;
public static void main(String[] args) {
new MainWindow();
}
public MainWindow() {
super("Graphics Test");
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("Redraw");
button.addActionListener(this);
add(button, BorderLayout.PAGE_START);
JPanel center = new JPanel(new FlowLayout());
dp = new DrawPanel();
center.add(dp);
add(center, BorderLayout.CENTER);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
dp.drawOnImage = (e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK;
dp.repaint();
}
}
And the custom component. Notice that holding CTRL will trigger this to render using drawImage() whereas without CTRL it will use the Graphics object directly:
public class DrawPanel extends JComponent {
Graphics2D gSave;
BufferedImage bi;
boolean drawOnImage = false;
public DrawPanel() {
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
public void paint(Graphics gr) {
Graphics2D g = (Graphics2D) gr;
gSave = g;
if (drawOnImage) {
bi = new BufferedImage(getSize().width, getSize().height, BufferedImage.TYPE_INT_ARGB);
g = bi.createGraphics();
}
Font font = new Font("SansSerif", Font.BOLD, 36);
g.setFont(font);
g.setColor(Color.BLACK);
g.drawString("Test String", 5, 40);
g.setStroke(new BasicStroke(6.0001f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0f, null, 0f));
g.drawRect(100, 70, 100, 100);
font = new Font("SansSerif", Font.PLAIN, 16);
g.setFont(font);
g.drawString("More Text to test the resolution", 5, 200);
Rectangle rect2 = new Rectangle(300, 40, 100, 100);
g.rotate(Math.toRadians(45));
g.draw(rect2);
if (drawOnImage) {
g.dispose();
gSave.drawImage(bi, null, 0, 0);
}
}
}
So the question is this: Why did an older version of Java have no problem with this but newer versions of Java seem to have degraded performance? Is there something that drawImage()
does differently that I can mitigate to not have this result?
(And for the inevitable question about why I’m even doing this and why is this necessary (e.g. If it works, why don’t you just directly draw on the Graphics
object rather than using the drawImage()
method?), I will note that the actual code I’m working on tries to draw translucent overlapping shapes on mouse dragging, which is why this BufferedImage
approach using drawImage()
was used. Perhaps there is a different way of doing that which doesn’t require using drawImage()
but I have not yet found it.)