I'm developing a Java application where several JPanels (not JFrames) 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.
