5

绘制多边形时,Java2D 会去掉右边缘和下边缘。我明白为什么要这样做。但是,我想画一些包含这些边缘的东西。我想到的一件事是跟随相同fillPolygondrawPolygon坐标,但这似乎留下了一个空白。(见底部的小三角形图像。)有两种可能性,但我不知道是哪一种。为了启用抗锯齿,我这样做:

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

一种可能是 alpha 通道上没有进行抗锯齿处理,因此间隙是由过度绘制引起的。在这种情况下,如果 Alpha 通道是被抗锯齿的,那么边缘会正确邻接。另一种可能性是这里只是有一个差距。

我怎样才能解决这个问题?

另外,我不确定,但看起来多边形轮廓实际上可能太大了。也就是说,它可能比我想要包含的右边缘和下边缘更远。

谢谢。

在此处输入图像描述

- 更新 -

根据 Hovercraft Full of Eels 的一个非常好的建议,我做了一个可编译的例子:

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);
   }
}

如果将填充的多边形保留为红色,则会得到下图(放大 500%),这表明多边形没有一直延伸到右边缘。也就是说,垂直的绿线对应于x=(2*WIDTH)/2,尽管红色多边形包含该坐标,但它并没有在那里绘制任何像素。

在此处输入图像描述

为了查看差距问题,我red在程序中更改为black. 在这张图片中,您可以在右下方看到一个细微的间隙,其中 绘制的轮廓与 绘制的轮廓drawPolygon不完全吻合fillPolygon

在此处输入图像描述

4

3 回答 3

3

在一个简单的可编译可运行程序中向我们展示您的绘图代码。例如,当我尝试模仿您的图像并使用 RenderingHints 时,它似乎生成了具有完整右/下边缘的适当大小的图像:

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);
   }
}

如果您可以向我们展示一个可以重现您的问题的类似程序,那么我们可以为您提供更好的帮助。

于 2011-10-09T02:56:09.873 回答
2

我喜欢ImageIcon@HFOE 所展示的便利性,但这种变化可能会让您更容易看到正在发生的事情。从GraphicsAPI,

绘制图形轮廓的操作是通过使用像素大小的笔穿过像素之间的无限细路径来操作的,该笔悬挂在路径上锚点的右侧。填充图形的操作是通过填充无限细路径的内部来进行的。

相反,Graphics2D必须遵循更复杂的抗锯齿规则,这允许它“画线外”。

像素视图

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();
            }
        });
    }
}
于 2011-10-09T05:44:09.553 回答
1

有时“图形笔从其经过的路径向右下方垂下”,有时则不然。我不清楚如何预测它何时会或不会,但我观察到 RenderingHints.VALUE_STROKE_PURE 有时可用于通过反复试验来改变行为。特别是,我发现如果您在程序中的 drawPolygon() 调用期间打开 STROKE_PURE,它会使它们与您的 fillPolygon() 调用匹配,如您所愿。

我做了一个小研究,展示了 STROKE_CONTROL 提示的效果,它是以下之一:

  • STROKE_NORMALIZE(默认值,在我的系统上)
  • STROKE_PURE

在以下电话中:

  • 画线()
  • 绘制多边形()
  • 填充多边形()

在两种抗锯齿模式下:

  • ANTIALIASING_OFF
  • ANTIALIASING_ON

还有一个更烦人的维度显然也很重要:

  • 直接渲染到屏幕上的 JComponent
  • 渲染为 BufferedImage。

以下是直接渲染到可见 JComponent 时的结果: 在此处输入图像描述

以下是渲染成 BufferedImage 时的结果: 在此处输入图像描述

(请注意两张图片不同的两种情况,即直接渲染与 BufferedImage 渲染不同:ANTIALIAS_OFF/STROKE_NORMALIZE/fillPolygon 和 ANTIALIAS_OFF/STROKE_PURE/drawPolygon。)

总的来说,整件事似乎没有太多押韵或理由。但我们可以根据以上图片做出以下具体观察:

观察 #1:如果您希望您的抗锯齿 drawPolygon()s 和抗锯齿 fillPolygon()s 匹配良好(原始问题),然后在抗锯齿 drawPolygon() 调用期间打开 STROKE_PURE。(在抗锯齿的 fillPolygon() 调用期间它是否打开并不重要。)

观察 #2:如果您希望您的抗锯齿 fillPolygon()s 和抗锯齿 fillPolygon()s 匹配(因为,比如说,您的应用程序允许用户打开和关闭抗锯齿,而您不希望图片每次他们这样做时都会向西北和东南跳跃),然后在非抗锯齿的 fillPolygon() 调用期间打开 STROKE_PURE。

这是我用来生成上述图片的程序。我的结果来自在 linux 上使用 opensdk11 编译和运行它;我很想知道是否有人在不同的平台上得到任何不同的结果。

/** Study the effect of STROKE_PURE. */

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

@SuppressWarnings("serial")
public final class AntiAliasingStudy {

  // These can be fiddled with.
  final static int patchWidth = 24;  // keep this a multiple of 4 for sanity
  final static int patchHeight = 20;  // keep this a multiple of 4 for sanity
  final static int borderThickness = 4;
  final static int mag = 6;

  // derived quantities
  final static int totalWidth = 5*borderThickness + 4*patchWidth;
  final static int totalHeight = 4*borderThickness + 3*patchHeight;

  private static void drawLittleStudy(Graphics2D g2d,
                                      int x00, int y00,
                                      int patchWidth, int patchHeight, int borderThickness, int totalWidth, int totalHeight) {
    g2d.setColor(new java.awt.Color(240,240,240));
    g2d.fillRect(x00,y00,totalWidth, totalHeight);

    for (int row = 0; row < 3; ++row) {
      for (int col = 0; col < 4; ++col) {
        int x0 = x00 + borderThickness + col*(patchWidth+borderThickness);
        int y0 = y00 + borderThickness + row*(patchHeight+borderThickness);
        int x1 = x0 + patchWidth;
        int y1 = y0 + patchHeight;
        g2d.setColor(java.awt.Color.WHITE);
        g2d.fillRect(x0, y0, patchWidth, patchHeight);

        boolean antialias = (col >= 2);
        boolean pure = (col % 2 == 1);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, pure ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE);

        g2d.setColor(java.awt.Color.RED);
        if (row == 0) {
          // lines (drawLine)
          // diagonals
          g2d.drawLine(x0,y1, x1,y0);
          g2d.drawLine(x0,y0, x1,y1);
          // orthogonals
          g2d.drawLine((x0+patchWidth/4),y0, (x0+patchWidth*3/4),y0);
          g2d.drawLine((x0+patchWidth/4),y1, (x0+patchWidth*3/4),y1);
          g2d.drawLine(x0,(y0+patchHeight/4), x0,(y0+patchHeight*3/4));
          g2d.drawLine(x1,(y0+patchHeight/4), x1,(y0+patchHeight*3/4));
        } else if (row == 1) {
          // outlines (drawPolygon)
          // A stopsign
          g2d.drawPolygon(new int[] {x0+patchWidth/2-2, x0, x0, x0+patchWidth/2-2, x0+patchWidth/2+2, x1, x1, x0+patchWidth/2+2},
                          new int[] {y0, y0+patchHeight/2-2, y0+patchHeight/2+2, y1, y1, y0+patchHeight/2+2, y0+patchHeight/2-2, y0},
                          8);
        } else if (row == 2) {
          // fill (fillPolygon)
          // A stopsign
          g2d.fillPolygon(new int[] {x0+patchWidth/2-2, x0, x0, x0+patchWidth/2-2, x0+patchWidth/2+2, x1, x1, x0+patchWidth/2+2},
                          new int[] {y0, y0+patchHeight/2-2, y0+patchHeight/2+2, y1, y1, y0+patchHeight/2+2, y0+patchHeight/2-2, y0},
                          8);
        }
      }
    }
  }  // drawLittleStudy

  // Show a study, previously created by drawLittleStudy(), magnified and annotated.
  private static void showMagnifiedAndAnnotatedStudy(Graphics g,
                                              BufferedImage studyImage,
                                              int x00, int y00,
                                              int patchWidth, int patchHeight, int borderThickness, int totalWidth, int totalHeight, int mag,
                                              ImageObserver imageObserver) {
    // Magnify the image
    g.drawImage(studyImage,
                /*dst*/ x00,y00,x00+totalWidth*mag,y00+totalHeight*mag,
                /*src*/ 0,0,totalWidth,totalHeight,
                imageObserver);

    // Draw annotations on each picture in black,
    // in the highest quality non-biased mode
    // (now that we know what that is!)
    g.setColor(java.awt.Color.BLACK);
    ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    for (int row = 0; row < 3; ++row) {
      for (int col = 0; col < 4; ++col) {
        int x0 = borderThickness + col*(patchWidth+borderThickness);
        int y0 = borderThickness + row*(patchHeight+borderThickness);
        int x1 = x0 + patchWidth;
        int y1 = y0 + patchHeight;
        if (false) {
          g.drawLine(x00+x0*mag,y00+y0*mag, x00+x1*mag,y00+y0*mag);
          g.drawLine(x00+x1*mag,y00+y0*mag, x00+x1*mag,y00+y1*mag);
          g.drawLine(x00+x1*mag,y00+y1*mag, x00+x0*mag,y00+y1*mag);
          g.drawLine(x00+x0*mag,y00+y1*mag, x00+x0*mag,y00+y0*mag);
        }
        if (row == 0) {
          // diagonals
          g.drawLine(x00+x0*mag,y00+y1*mag, x00+x1*mag,y00+y0*mag);
          g.drawLine(x00+x0*mag,y00+y0*mag, x00+x1*mag,y00+y1*mag);
          // orthogonals
          g.drawLine(x00+(x0+patchWidth/4)*mag,y00+y0*mag, x00+(x0+patchWidth*3/4)*mag,y00+y0*mag);
          g.drawLine(x00+(x0+patchWidth/4)*mag,y00+y1*mag, x00+(x0+patchWidth*3/4)*mag,y00+y1*mag);
          g.drawLine(x00+x0*mag,y00+(y0+patchHeight/4)*mag, x00+x0*mag,y00+(y0+patchHeight*3/4)*mag);
          g.drawLine(x00+x1*mag,y00+(y0+patchHeight/4)*mag, x00+x1*mag,y00+(y0+patchHeight*3/4)*mag);
        } else {  // row 1 or 2
          // A stopsign
          g.drawPolygon(new int[] {x00+(x0+patchWidth/2-2)*mag, x00+x0*mag, x00+x0*mag, x00+(x0+patchWidth/2-2)*mag, x00+(x0+patchWidth/2+2)*mag, x00+x1*mag, x00+x1*mag, x00+(x0+patchWidth/2+2)*mag},
                        new int[] {y00+y0*mag, y00+(y0+patchHeight/2-2)*mag, y00+(y0+patchHeight/2+2)*mag, y00+y1*mag, y00+y1*mag, y00+(y0+patchHeight/2+2)*mag, y00+(y0+patchHeight/2-2)*mag, y00+y0*mag},
                        8);
        }
      }
    }
    FontMetrics fm = g.getFontMetrics();
    {
      String[][] texts = {
          {"ANTIALIAS_OFF", "STROKE_NORMALIZE"},
          {"ANTIALIAS_OFF", "STROKE_PURE"},
          {"ANTIALIAS_ON", "STROKE_NORMALIZE"},
          {"ANTIALIAS_ON", "STROKE_PURE"},
      };
      for (int col = 0; col < 4; ++col) {
        int xCenter = borderThickness*mag + col*(patchWidth+borderThickness)*mag + patchWidth*mag/2;
        {
          int x = x00 + xCenter - fm.stringWidth(texts[col][0])/2;
          int y = y00 + 3*(patchHeight+borderThickness)*mag + fm.getAscent();
          g.drawString(texts[col][0], x,y);
          x = xCenter - fm.stringWidth(texts[col][1])/2;
          y += fm.getHeight();
          g.drawString(texts[col][1], x,y);
        }
      }
    }
    {
      String[] texts = {
        "drawLine",
        "drawPolygon",
        "fillPolygon",
      };
      for (int row = 0; row < 3; ++row) {
        int yCenter = y00 + borderThickness*mag + row*(patchHeight+borderThickness)*mag + patchHeight*mag/2;
        int x = x00 + 4*(patchWidth+borderThickness)*mag + 10;
        g.drawString(texts[row], x,yCenter);
      }
    }
  }  // showMagnifiedAndAnnotatedStudy


  private static Dimension figureOutPreferredSize(FontMetrics fm) {
    int preferredWidth = (totalWidth-borderThickness)*mag + 10 + fm.stringWidth("drawPolygon") + 9;
    int preferredHeight = fm.getHeight() + totalHeight + (totalHeight-borderThickness)*mag + 2*fm.getHeight() + 2;
    return new Dimension(preferredWidth, preferredHeight);
  }

  private static class IndirectExaminationView extends JComponent {
    public IndirectExaminationView() {
      setFont(new Font("Times", Font.PLAIN, 12));
      setPreferredSize(figureOutPreferredSize(getFontMetrics(getFont())));
    }
    @Override public void paintComponent(Graphics g) {
      FontMetrics fm = g.getFontMetrics();
      g.setColor(java.awt.Color.BLACK);
      g.drawString("through BufferedImage:", 0,fm.getAscent());
      // The following seem equivalent
      java.awt.image.BufferedImage studyImage = new java.awt.image.BufferedImage(totalWidth, totalHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
      //java.awt.image.BufferedImage studyImage = (BufferedImage)this.createImage(totalWidth, totalHeight);
      drawLittleStudy(studyImage.createGraphics(),
                      0,0,
                      patchWidth, patchHeight, borderThickness, totalWidth, totalHeight);
                    Graphics2D studyImageGraphics2D = studyImage.createGraphics();
      g.drawImage(studyImage,
                  /*dst*/ 0,fm.getHeight(),totalWidth,fm.getHeight()+totalHeight,
                  /*src*/ 0,0,totalWidth,totalHeight,
                  this);
      showMagnifiedAndAnnotatedStudy(g, studyImage,
                                     0,fm.getHeight()+totalHeight,
                                     patchWidth, patchHeight, borderThickness, totalWidth, totalHeight, mag, this);
    }
  }  // DirectExaminationView

  private static class DirectExaminationView extends JComponent {
    public DirectExaminationView() {
      setFont(new Font("Times", Font.PLAIN, 12));
      setPreferredSize(figureOutPreferredSize(getFontMetrics(getFont())));
    }
    private BufferedImage imgFromTheRobot = null;
    @Override public void paintComponent(Graphics g) {
      final FontMetrics fm = g.getFontMetrics();
      g.setColor(java.awt.Color.BLACK);
      g.drawString("direct to JComponent:", 0,fm.getAscent());
      drawLittleStudy((Graphics2D)g,
                      0,fm.getHeight(),
                      patchWidth, patchHeight, borderThickness, totalWidth, totalHeight);

      if (imgFromTheRobot != null) {
        System.out.println("              drawing image from robot");
        showMagnifiedAndAnnotatedStudy(g, imgFromTheRobot,
                                       0, fm.getHeight()+totalHeight,
                                       patchWidth, patchHeight, borderThickness, totalWidth, totalHeight, mag, this);
        imgFromTheRobot = null;
      } else {
        System.out.println("              scheduling a robot");
        g.drawString("*** SCREEN CAPTURE PENDING ***", 0, fm.getHeight()+totalHeight+fm.getHeight()+fm.getHeight());
        // Most reliable way to do it seems to be to put it on a timer after a delay.
        Timer timer = new Timer(1000/2, new ActionListener() {
          @Override public void actionPerformed(ActionEvent ae) {
            System.out.println("            in timer callback");
            Robot robot;
            try {
              robot = new Robot();
            } catch (AWTException e) {
              System.err.println("caught AWTException: "+e);
              throw new Error(e);
            }

            Point myTopLeftOnScreen = getLocationOnScreen();
            Rectangle rect = new Rectangle(
              myTopLeftOnScreen.x, myTopLeftOnScreen.y + fm.getHeight(),
              totalWidth,totalHeight);
            BufferedImage img = robot.createScreenCapture(rect);
            imgFromTheRobot = img;
            repaint();
            System.out.println("            out timer callback");
          }
        });
        timer.setRepeats(false);
        timer.start();
      }
    }
  }  // DirectExaminationView

  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      @Override public void run()
      {
        final JFrame directFrame = new JFrame("direct to JComponent") {{
          getContentPane().add(new DirectExaminationView());
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          pack();
          setLocation(0,0);
          setVisible(true);
        }};
        new JFrame("through BufferedImage") {{
          getContentPane().add(new IndirectExaminationView());
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          pack();
          setLocation(directFrame.getWidth(),0);
          setVisible(true);
        }};
      }
    });
  }
}  // class AntiAliasingStudy
于 2020-08-29T08:41:59.633 回答