I'm developing a Java application where several JPanel
s (not JFrame
s) have complex animations that necessitate drawing to an off-screen buffer before blitting to the display surface. A problem I'm having is that Swing is performing UI scaling for high-DPI screens, and the off-screen buffer (a raster) isn't "aware" of the scaling. Consequently, when text or graphics are rendered to the buffer, and the buffer is blitted to the JPanel
, Swing scales the graphic as a raster and the result looks like garbage.
A simple example is:
import java.awt.*;
import java.awt.geom.Line2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame jf = new JFrame("Demo");
Container cp = jf.getContentPane();
MyCanvas tl = new MyCanvas();
cp.add(tl);
jf.setSize(500, 250);
jf.setVisible(true);
jf.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}
}
class MyCanvas extends JComponent {
@Override
public void paintComponent(Graphics g) {
if( g instanceof Graphics2D g2 ) {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setFont( Font.decode( "Times New Roman-26" ) );
g2.drawString("The poorly-scaled cake is a lie.",70,40);
g2.setStroke( new BasicStroke( 2.3f ) );
g2.draw( new Line2D.Double( 420, 10, 425, 70 ) );
Image I = createImage( 500, 150 );
Graphics2D g2_ = (Graphics2D)I.getGraphics();
g2_.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2_.setColor( Color.BLACK );
g2_.setFont( Font.decode( "Times New Roman-26" ) );
g2_.drawString( "The poorly-scaled cake is a lie.",70,40 );
g2_.setStroke( new BasicStroke( 2.3f ) );
g2_.draw( new Line2D.Double( 420, 10, 425, 70 ) );
g2_.dispose();
g2.drawImage( I, 0, 130, null );
}
}
}
From this, compiling with JDK 20 on my Windows 11 machine, I get:
On the top is text and graphics rendered directly to the JPanel
. On the bottom is the same content rendered via an intermediary image.
Ideally, I'm looking for a method, e.g., Image createScalingAwareBuffer( JPanel jp, int width, int height )
that returns an image I
, in the same vein as JPanel.createImage( ... )
but where the returned Image
is vector scaling aware, such that jp.drawImage( I )
or equivalent displays the lower graphic content identically to the upper content.
I suspect that rendering to the back buffer in a double-buffered Swing component has this kind of "awareness", but this isn't an option in my case since I need to precisely control when buffer flips occur on a panel-by-panel basis, which (insofar as I know) is impossible in Swing.
Is there any solution for this without a radical rewrite (i.e., migrating away from Swing, etc.)?
I should also note that I don't want to disable the UI scaling (e.g., using -Dsun.java2d.uiScale=1
in VM options), hence "just disable UI scaling" isn't really a solution.