Search code examples
javagraphics2dsubpixel

Sub-pixel rendering


I'm attempting, and failing, to render 2d shapes in Java using sub-pixel positioning. A failed attempt is below (ignore the scale and the AffineTransform for the moment) :

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;

public class TestFrame extends Frame {
    private static final int D = 64;

    public static void main(String args[]) {
        new TestFrame(D, D);
    }

    private final Insets ins;
    private final double scale = 1;

    TestFrame(int w, int h) {
        ins = getInsets();
        final Dimension dim = new Dimension(w + ins.left + ins.right, h + ins.top + ins.bottom);

        pack();

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent we) {
                dispose();
            }
        });

        setSize(dim);
        setLocationRelativeTo(null);
        setBackground(Color.BLACK);
        setVisible(true);
        repaint();
    }

    @Override
    public void paint(Graphics gfx) {
        super.paint(gfx);
        final Graphics2D g2d = (Graphics2D)gfx;
        g2d.setTransform(new AffineTransform(1.0/scale, 0.0, 0.0, 1.0/scale, ins.left, ins.top));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(new BasicStroke((float)scale));
        g2d.setColor(Color.WHITE);

        for (double deg = 0; deg < 90.0; deg += 2.0) {
            final double rad = deg / 180.0 * Math.PI;
            final double x1 = Math.cos(rad) * D * scale;
            final double y1 = Math.sin(rad) * D * scale;
            final double x2 = Math.cos(rad) * (D - 4.0) * scale;
            final double y2 = Math.sin(rad) * (D - 4.0) * scale;
            g2d.draw(new Line2D.Double(x1, y1, x2, y2));
        }
    }
}

This gives rise to the following output:

Rendering

The lines should be evenly spaced, but, as you can see, they are not. My guess is that the start and end points are being rounded to the nearest pixel.

Suggested solutions on SO mention using Line2D.Double (which I'm already doing and apparently doesn't work), or using an AffineTransform to scale down. If you change the scale variable in the code sample to say 100, then this plots the lines at a much higher scale, and then uses the transform to scale it back down as per the suggestions. This too has no effect on the resultant image.

What am I doing wrong?


Solution

  • The solution is set one of the rendering hints:

    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    

    Quoting the Javadocs:

    Stroke normalization control hint value -- geometry should be left unmodified and rendered with sub-pixel accuracy.