Java Graphics2D正在关闭一个像素(舍入错误?)

时间:2015-09-11 20:59:53

标签: java rounding paintcomponent graphics2d

我一直在尝试使用Graphics2D和PaintComponent在Java中创建一个程序。但是,Java并没有正确地舍入一些值,导致一些点被渲染成一个像素所不同的像素,这会给出一个不洁的图像。你可以在下面的图片中看到我的意思。

enter image description here

这是我的代码。我可以改变什么来解决这个问题?谢谢!

public void paintComponent( Graphics g )
{
  super.paintComponent( g );

  g.setColor(Color.red);

  int orginX = getWidth()/2;
  int orginY = getHeight()/2;

  for(int i=0; i<=360; i+= 10)
  {
      double angle = Math.toRadians(i);
      double centerX = radius * Math.cos(angle) + orginX;
      double centerY = radius * Math.sin(angle) + orginY;
      int[] anglePointsX = {(int) (radius * Math.cos(angle+Math.toRadians(60)) + centerX), (int) (radius * Math.cos(angle-Math.toRadians(60)) + centerX), orginX};
      int[] anglePointsY = {(int) (radius * Math.sin(angle+Math.toRadians(60)) + centerY), (int) (radius * Math.sin(angle-Math.toRadians(60)) + centerY), orginY};
      g.drawPolygon(anglePointsX, anglePointsY, 3);
  }

4 个答案:

答案 0 :(得分:2)

尝试使用i < 360代替i<=360,因为您已经在这个角度画了一条线(0 == 360)

尝试使用Path2DGeneralPath而不是drawPolygon,因为这会为您提供floatdouble精确度

有关详细信息,请查看Drawing Arbitrary Shapes

<强>更新...

所以我将i <= 360更改为i < 360并且没有看到任何立即更改,但是,然后应用了一些渲染提示,它似乎改善了问题。

我首先将元素数量减少到6(这是我开始看到问题的地方)

int dif = (int) (360 / 6d);

for (int i = 0; i < 360; i += dif) {

然后我应用了一些渲染提示......

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    Graphics2D g2d = (Graphics2D) g.create();

    g2d.setColor(Color.red);
    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);

    int orginX = getWidth() / 2;
    int orginY = getHeight() / 2;

    int dif = (int) (360 / 6d);

    for (int i = 0; i < 360; i += dif) {
        double angle = Math.toRadians(i);
        double centerX = radius * Math.cos(angle) + orginX;
        double centerY = radius * Math.sin(angle) + orginY;
        int[] anglePointsX = {(int) (radius * Math.cos(angle + Math.toRadians(60)) + centerX), (int) (radius * Math.cos(angle - Math.toRadians(60)) + centerX), orginX};
        int[] anglePointsY = {(int) (radius * Math.sin(angle + Math.toRadians(60)) + centerY), (int) (radius * Math.sin(angle - Math.toRadians(60)) + centerY), orginY};
        g2d.drawPolygon(anglePointsX, anglePointsY, 3);
    }
    g2d.dispose();
}

比较之前和之后......

Before After

然后我使用drawPolygon更改了图纸以使用Shape API,特别是Path2D

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    Graphics2D g2d = (Graphics2D) g.create();

    g2d.setColor(Color.red);
    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);

    int orginX = getWidth() / 2;
    int orginY = getHeight() / 2;

    int dif = (int) (360 / 6d);
    for (int i = 0; i < 360; i += dif) {
        double angle = Math.toRadians(i);
        double centerX = radius * Math.cos(angle) + orginX;
        double centerY = radius * Math.sin(angle) + orginY;

        Path2D path = new Path2D.Double();
        path.moveTo(radius * Math.cos(angle + Math.toRadians(60)) + centerX, radius * Math.sin(angle + Math.toRadians(60)) + centerY);
        path.lineTo((radius * Math.cos(angle - Math.toRadians(60)) + centerX), (radius * Math.sin(angle - Math.toRadians(60)) + centerY));
        path.lineTo(orginX, orginY);

        g2d.draw(path);
    }
    g2d.dispose();
}

有和没有渲染提示

With Without

尽可能地,即使没有提示提示,问题仍然存在

答案 1 :(得分:2)

我建议你可以画两条不同的线:

  g.drawPolygon(anglePointsX, anglePointsY, 2); // the outter line.
  g.drawLine(orginX, orginY, anglePointsX[0], anglePointsY[0]); // from origin to one outer point

几乎工作,只有一条线不如预期。

那么我的下一个建议是再次绘制两条单独的线条,但这次只是旋转从原点绘制的线条。

super.paintComponent( g );

g.setColor(Color.red);
Graphics2D g2 = (Graphics2D)g.create();

int radius = 100;
int orginX = getWidth()/2;
int orginY = getHeight()/2;
double radians = Math.toRadians(60);

for(int i=0; i < 360; i+= 10)
{
    double angle = Math.toRadians(i);
    double centerX = radius * Math.cos(angle) + orginX;
    double centerY = radius * Math.sin(angle) + orginY;

    int[] anglePointsX = {(int) (radius * Math.cos(angle + radians) + centerX), (int) (radius * Math.cos(angle - radians) + centerX), orginX};
    int[] anglePointsY = {(int) (radius * Math.sin(angle + radians) + centerY), (int) (radius * Math.sin(angle - radians) + centerY), orginY};

    g.drawPolygon(anglePointsX, anglePointsY, 2);
//    g2.drawLine(orginX, orginY, anglePointsX[0], anglePointsY[0]);

    AffineTransform af = new AffineTransform();
    af.translate(orginX, orginY);
    af.rotate( angle );
    g2.setTransform( af );
    g2.drawLine(0, 0, 175, 0);
}

这种方法的问题在于,我不知道如何从原点计算线的长度,只是硬编码175.也许你的数学比我的好,你知道如何计算固定长度这条线。

确定最大线长度的强力方法可以是创建两个循环。第一个循环绘制外部线并确定使用的最大x值。然后第二个循环使用此值作为长度绘制36条旋转线:

protected void paintComponent(Graphics g)
{
    super.paintComponent( g );

    g.setColor(Color.red);
    Graphics2D g2 = (Graphics2D)g.create();

    int radius = 100;
    int orginX = getWidth()/2;
    int orginY = getHeight()/2;
    double radians60 = Math.toRadians(60);
    int maxX = 0;

    for(int i=0; i < 360; i+= 10)
    {
        double angle = Math.toRadians(i);
        double centerX = radius * Math.cos(angle) + orginX;
        double centerY = radius * Math.sin(angle) + orginY;

        int[] anglePointsX = {(int) (radius * Math.cos(angle + radians60) + centerX), (int) (radius * Math.cos(angle - radians60) + centerX), orginX};
        int[] anglePointsY = {(int) (radius * Math.sin(angle + radians60) + centerY), (int) (radius * Math.sin(angle - radians60) + centerY), orginY};

        g.drawPolygon(anglePointsX, anglePointsY, 2);

        maxX = Math.max(maxX, anglePointsX[0]);
    }

    for(int i=0; i < 360; i+= 10)
    {
        double angle = Math.toRadians(i);
        AffineTransform af = new AffineTransform();
        af.translate(orginX, orginY);
        af.rotate( angle );
        g2.setTransform( af );
        g2.drawLine(0, 0, maxX - orginX, 0);
    }

    g2.dispose();
}

答案 2 :(得分:2)

在这种情况下,“粗”线的原因确实是舍入误差。所以你观察到的并不是绘图的问题,而是计算的问题。请注意,由于各种原因(主要是一般double值的精度有限),您不能假设像cossin这样的三角函数的所有结果都是您可能期望的结果成为。例如,

System.out.println(Math.sin(Math.toRadians(180)));

将打印1.2246467991473532E-16,虽然在数学上应该是0.0。同样,当结果应为199.999999999941234234时,您计算的某些值最终可能会像200.0。然后,当您将这些值转换为int时,您将截断小数部分,结果将为199 - 导致您观察到的奇怪视觉效果。

在这种情况下,可以,实际上,通过使用Math.round 舍入结果来解决,就像在{的以下示例中所做的那样{1}}。

为了完整起见,PixelsOffByOnePanel课程会显示如何使用PixelsOffByOnePanelNicerPath2D值执行此操作。

double

附注:通常,通过调用

启用消除锯齿时,绘图可能看起来更好
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.geom.Path2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class PixelsOffByOne
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                JFrame frame = new JFrame("");
                frame.getContentPane().setLayout(new GridLayout(1,2));
                frame.getContentPane().add(new PixelsOffByOnePanel());
                frame.getContentPane().add(new PixelsOffByOnePanelNicer());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

class PixelsOffByOnePanel extends JPanel
{
    int radius = 100; 

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

    @Override
    public void paintComponent( Graphics g )
    {
        super.paintComponent( g );

        g.setColor(Color.RED);

        int orginX = getWidth()/2;
        int orginY = getHeight()/2;

        for(int i=0; i<=360; i+=10)
        {
            double angle = Math.toRadians(i);
            double centerX = radius * Math.cos(angle) + orginX;
            double centerY = radius * Math.sin(angle) + orginY;

            int x0 = (int) Math.round(radius * Math.cos(angle+Math.toRadians(60)) + centerX);
            int x1 = (int) Math.round(radius * Math.cos(angle-Math.toRadians(60)) + centerX);
            int[] anglePointsX = {x0, x1, orginX};

            int y0 = (int) Math.round(radius * Math.sin(angle+Math.toRadians(60)) + centerY);
            int y1 = (int) Math.round(radius * Math.sin(angle-Math.toRadians(60)) + centerY);
            int[] anglePointsY = {y0, y1, orginY};

            g.drawPolygon(anglePointsX, anglePointsY, 3);

        }    
    }
}

class PixelsOffByOnePanelNicer extends JPanel
{
    int radius = 100; 

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

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setColor(Color.RED);

        int originX = getWidth()/2;
        int originY = getHeight()/2;

        double d = Math.toRadians(60);

        Path2D path = new Path2D.Double();
        for(int i=0; i<=360; i+=10)
        {
            double angle = Math.toRadians(i);
            double centerX = radius * Math.cos(angle) + originX;
            double centerY = radius * Math.sin(angle) + originY;

            double x0 = (int) Math.round(radius * Math.cos(angle+d) + centerX);
            double x1 = (int) Math.round(radius * Math.cos(angle-d) + centerX);
            double x2 = originX;

            double y0 = (int) Math.round(radius * Math.sin(angle+d) + centerY);
            double y1 = (int) Math.round(radius * Math.sin(angle-d) + centerY);
            double y2 = originY;

            path.moveTo(x0, y0);
            path.lineTo(x1, y1);
            path.lineTo(x2, y2);
            path.closePath();
        }    
        g.draw(path);
    }
}

开始画画之前。但是,当您多次绘制多行时,如果专用仅绘制每行一次,结果可能看起来不那么令人满意。

答案 3 :(得分:0)

也许是两个不同的问题。

问题1,别名。您需要使用以下内容打开它:

https://docs.oracle.com/javase/tutorial/2d/advanced/quality.html

对于看起来比它们应该更粗的线条,你要画两次,这使它们看起来更厚。因此,最好的解决方案是避免两次绘制它们。尝试重构代码以绘​​制线而不是多边形,因此每行只绘制一次。