Antialias height map edge using Java 2D

2019-09-15 23:24发布

问题:

I'm using Java 2D to render a terrain map. Terrain Map looks like this:

You can see how the edges are rather jagged. I want to render the edge of the terrain smoothly, but turning on antialiasing with RenderingHints doesn't work because I render the terrain map one column at a time.

Here's my code to render the terrain:

// terrainImageG2 renders to a BufferedImage, obtained via BufferedImage.createGraphics()
terrainImageG2.setBackground(Color.WHITE);
terrainImageG2.clearRect(0, 0, NUM_WIDE, NUM_HIGH);
terrainImageG2.setStroke(new BasicStroke(1.0F));
terrainImageG2.setColor(Color.BLACK);
for (int x = 0; x < NUM_WIDE; x++) {
    terrainImageG2.drawLine(x, NUM_HIGH - originalHeightMap[x], x, NUM_HIGH);
    // originalHeightMap contains the height map, ranging from 0 to NUM_HIGH
    // originalHeightMap 
}

As you can see, I render the terrain with drawLine so if I precede this with:

terrainImageG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

it doesn't actually antialias the edge. Here's what the terrain looks like if I enable RenderingHints' antialiasing:

I tried doing some manual antialiasing, where I render the top pixel as light gray instead of as black. However, this made flatter areas look fuzzy while not affecting steeper areas. Take a look:

Is there any way I can draw this edge smoothly, either manually or via some API? Thanks

回答1:

Some ideas:

  • Draw an anti-aliased line across the top by connecting the top point of each line to the top point of its neighbor. This might be easier to debug by just drawing the top line to see how it looks and then filling in the vertical lines if you like its look.

  • Try to work out some algorithm similar to the way antialiasing works by taking into account the positions of the tops of the previous and next ones when drawing this one. So, if the current one is lower or higher than the one before and the one after, add a grey dot to the top. If its the same, no dot is added. make the dot lighter or darker depending on how much higher the adjacent ones.

  • Smooth the data using a linear averaging or some sort of curve smoothing before plotting. This won't really anti-alias but may make the graph more appealing.



回答2:

You could try using some of the other rendering hints...

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestPaint20 {

    public static void main(String[] args) {
        new TestPaint20();
    }

    public TestPaint20() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setBackground(Color.WHITE);
            g2d.setStroke(new BasicStroke(1.0F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            g2d.setColor(Color.BLACK);
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

            for (int x = 0; x < getWidth(); x++) {
                g2d.drawLine(x, getHeight() - (int) Math.round(Math.random() * (getHeight() - (getHeight() / 4d))), x, getHeight());
                // originalHeightMap contains the height map, ranging from 0 to NUM_HIGH
                // originalHeightMap 
            }
            g2d.dispose();
        }
    }
}