Java Graphics.fillPolygon: How to also render righ

2020-03-26 01:40发布

问题:

When drawing polygons, Java2D leaves off the right and bottom edges. I understand why this is done. However, I would like to draw something that includes those edges. One thing that occurred to me was to follow fillPolygon with drawPolygon with the same coordinates, but this appears to leave a gap. (See the little triangular image at the bottom.) There are two possibilities, but I can't tell which. To enable antialiasing, I'm doing this:

renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                  RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHints(renderHints);

One possibility is that the antialiasing is not being done on the alpha channel, so the gap is caused by overdraw. In that case, if the alpha channel were what was being antialiased, the edges would abut properly. The other possibility is that there is just a gap here.

How can I fix this?

Also, I'm not sure, but it appears that the polygon outline may actually be TOO BIG. That is, it may be going further out than the right and bottom edges that I want to include.

Thanks.

-- UPDATE --

Based on a very nice suggestion by Hovercraft Full of Eels, I have made a compilable example:

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

public class polygon {
   private static final int WIDTH = 20;

   public static void main(String[] args) {
      BufferedImage img = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = img.createGraphics();
      int[] xPoints = {WIDTH / 3, (2*WIDTH) / 3, WIDTH / 3};
      int[] yPoints = {0, WIDTH / 2, WIDTH};
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setColor(Color.green);
      g2.drawLine(0, WIDTH-1, WIDTH, WIDTH-1);
      g2.drawLine(0, 0, WIDTH, 0);
      g2.drawLine(WIDTH/3, 0, WIDTH/3, WIDTH);
      g2.drawLine((2*WIDTH/3), 0, (2*WIDTH/3), WIDTH);
      g2.setColor(Color.black);
      g2.drawPolygon(xPoints, yPoints, xPoints.length);
      g2.setColor(Color.black);
      g2.fillPolygon(xPoints, yPoints, xPoints.length);
      g2.dispose();

      ImageIcon icon = new ImageIcon(img);
      JLabel label = new JLabel(icon);

      JOptionPane.showMessageDialog(null, label);
   }
}

If you leave the filled polygon red, you get the image below (zoomed by 500%), which shows that the polygon does not extend all the way to the right edge. That is, the vertical green line is corresponds to x=(2*WIDTH)/2, and although the red polygon includes that coordinate, it does not paint any pixels there.

To see the gap problem, I changed red in the program to black. In this image, you can see a subtle gap on the lower right side, where the outline drawn by drawPolygon does not quite meet up with what was drawn with fillPolygon.

回答1:

Show us your code for your drawing in a simple compilable runnable program. For instance when I try to imitate your image and used RenderingHints, it seemed to produce an appropriate sized image with complete right/bottom edges:

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class Foo002 {
   private static final int WIDTH = 20;

   public static void main(String[] args) {
      BufferedImage img = new BufferedImage(WIDTH, WIDTH,
            BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = img.createGraphics();
      int[] xPoints = { WIDTH / 3, (2 * WIDTH) / 3, WIDTH / 3 };
      int[] yPoints = { 0, WIDTH / 2, WIDTH };
      g2.setColor(Color.black);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setRenderingHint(RenderingHints.KEY_RENDERING,
            RenderingHints.VALUE_RENDER_QUALITY);
      g2.fillPolygon(xPoints, yPoints, xPoints.length);
      g2.dispose();

      ImageIcon icon = new ImageIcon(img);
      JLabel label = new JLabel(icon);
      label.setBorder(BorderFactory.createLineBorder(Color.black));
      JPanel panel = new JPanel();
      panel.add(label);

      JOptionPane.showMessageDialog(null, panel);
   }
}

If you can show us a similar program that reproduces your problem, then we can give you better help.



回答2:

I like the convenience of ImageIcon, shown by @HFOE, but this variation may make it a little easier to see what's happening. From the Graphics API,

Operations that draw the outline of a figure operate by traversing an infinitely thin path between pixels with a pixel-sized pen that hangs down and to the right of the anchor point on the path. Operations that fill a figure operate by filling the interior of that infinitely thin path.

In contrast, Graphics2D must follow more complex rules for antialiasing, which allow it to "draw outside the lines."

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;

/** @see http://stackoverflow.com/questions/7701097 */
public class PixelView extends JPanel {

    private static final int SIZE = 20;
    private static final int SCALE = 16;
    private BufferedImage img;

    public PixelView(Color fill) {
        this.setBackground(Color.white);
        this.setPreferredSize(new Dimension(SCALE * SIZE, SCALE * SIZE));
        img = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = img.createGraphics();
        int[] xPoints = {SIZE / 3, (2 * SIZE) / 3, SIZE / 3};
        int[] yPoints = {0, SIZE / 2, SIZE};
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(Color.green);
        g2.drawLine(0, SIZE - 1, SIZE, SIZE - 1);
        g2.drawLine(0, 0, SIZE, 0);
        g2.drawLine(SIZE / 3, 0, SIZE / 3, SIZE);
        g2.drawLine((2 * SIZE / 3), 0, (2 * SIZE / 3), SIZE);
        g2.setColor(Color.black);
        g2.drawPolygon(xPoints, yPoints, xPoints.length);
        g2.setColor(fill);
        g2.fillPolygon(xPoints, yPoints, xPoints.length);
        g2.dispose();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
    }

    private static void display() {
        JFrame f = new JFrame("PixelView");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLayout(new GridLayout(1, 0));
        f.add(new PixelView(Color.black));
        f.add(new PixelView(Color.red));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                display();
            }
        });
    }
}