I'm trying to figure out what is the right way to draw paths such rectangles or rounded rectangles, such that horizontal and vertical parts always fall on a full pixel and are displayed at hairline stroke width.
A long time ago there was an option to specify stroke width of 0 for that, but then it was quickly reverted. My understanding is that I need to query scale x and scale y of the device's config's transform, and use that to compute the stroke width. Here's my code: import javax.swing.*; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; abstract class Base extends JComponent { protected float strokeWidth; public Base() { this.setOpaque(false); this.setPreferredSize(new Dimension(80, 24)); this.strokeWidth = 1.0f / (float) getScaleFactor(); } private static double getScaleFactor(GraphicsDevice device) { GraphicsConfiguration graphicsConfig = device.getDefaultConfiguration(); AffineTransform tx = graphicsConfig.getDefaultTransform(); double scaleX = tx.getScaleX(); double scaleY = tx.getScaleY(); return Math.max(scaleX, scaleY); } private static double getScaleFactor() { double result = 1.0; GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] devices = e.getScreenDevices(); // now get the configurations for each device for (GraphicsDevice device : devices) { result = Math.max(result, getScaleFactor(device)); } return result; } } class Version1 extends Base { @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g2d.setStroke(new BasicStroke(this.strokeWidth, BasicStroke.JOIN_ROUND, BasicStroke.CAP_BUTT)); g2d.setColor(Color.BLACK); g2d.draw(new Rectangle2D.Float(this.strokeWidth, this.strokeWidth, this.getWidth() - 2 * this.strokeWidth, this.getHeight() - 2 * this.strokeWidth)); for (int i = 1; i < 5; i++) { float inset = 2 * i; g2d.draw(new Rectangle2D.Float(this.strokeWidth + inset, this.strokeWidth + inset, this.getWidth() - 2 * this.strokeWidth - 2 * inset, this.getHeight() - 2 * this.strokeWidth - 2 * inset)); } g2d.dispose(); } } public class Hairlines { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame frame = new JFrame("Hairlines"); frame.setLayout(new FlowLayout()); JComponent version1 = new Version1(); frame.add(version1); frame.setVisible(true); frame.pack(); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); }); } } Now, running this on a Windows 10 laptop with recommended / default 250% scale factor gets the correct value of 2.5 as scale. But the visuals switch between hairline and "smudged" as I horizontally resize the frame one pixel at a time and the component shifts horizontally: [image: image.png] (and the horizontal lines are also not hairline) How do I make this work in Java2D across all display scale factors so that I get consistent hairlines? I see the same behavior on Java 9 and Java 17 Thanks Kirill