Java 实施Bresenham';s圆绘制算法

Java 实施Bresenham';s圆绘制算法,java,algorithm,swing,graphics,bresenham,Java,Algorithm,Swing,Graphics,Bresenham,我已经编写了Bresenham的圆绘制算法的一个实现此算法利用圆的高度对称特性(它仅从第一个八分之一计算点,并利用对称性绘制其他点)。所以我希望它会很快。《图形编程黑皮书》第35章的标题是“Bresenham是快的,而fast是好的”,虽然它是关于线条绘制算法的,但我可以合理地预期圆形绘制算法也会很快(因为原理是一样的) 这是我的java,swing实现 public static void drawBresenhamsCircle(int r, double width, double hei

我已经编写了Bresenham的圆绘制算法的一个实现此算法利用圆的高度对称特性(它仅从第一个八分之一计算点,并利用对称性绘制其他点)。所以我希望它会很快。《图形编程黑皮书》第35章的标题是“Bresenham是快的,而fast是好的”,虽然它是关于线条绘制算法的,但我可以合理地预期圆形绘制算法也会很快(因为原理是一样的)

这是我的java,swing实现

public static void drawBresenhamsCircle(int r, double width, double height, Graphics g) {
    int x,y,d;
    y = r;
    x = 0;

    drawPoint(x, y, width, height,g);
    d = (3-2*(int)r);
    while (x <= y) {
        if (d <= 0) {
            d = d + (4*x + 6);
        } else {
            d = d + 4*(x-y) + 10;
            y--;
        }
        x++;

        drawPoint(x, y, width, height,g);

        drawPoint(-x, y, width, height,g);
        drawPoint(x, -y, width, height,g);

        drawPoint(-x, -y, width, height,g);
        drawPoint(y, x, width, height,g);
        drawPoint(-y, x, width, height,g);
        drawPoint(y, -x, width, height,g);

        drawPoint(-y, -x, width, height,g);
    }   
}
getNativeX和getNativeY两种方法用于将坐标从屏幕左上角的原点切换到原点位于面板中心且具有更经典轴方向的系统

public static double getNativeX(double newX, double width) {
    return newX + (width/2);
}

public static double getNativeY(double newY, double height) {
    return (height/2) - newY;
}
我还创建了一个基于三角公式的圆绘制算法的实现(
x=R*Math.cos(angle)
y=R*Math.sin(angle)
)以及第三个使用标准drawArc方法调用的实现(可在图形对象上获得)。这些附加实现的唯一目的是将Bresenham的算法与它们进行比较

然后我创建了一些方法来画一组圆圈,以便能够很好地测量所花费的时间。下面是我使用Bresenham算法绘制一组圆的方法

public static void drawABunchOfBresenhamsCircles(int numOfCircles, double width, double height, Graphics g) {
    double r = 5;
    double step = (300.0-5.0)/numOfCircles;

    for (int i = 1; i <= numOfCircles; i++) {
        drawBresenhamsCircle((int)r, width, height, g);
        r += step;
    }
}
以下是它将生成的渲染类型(每种类型绘制1000个圆)

不幸的是,我的Bresenham的实现非常缓慢。我采取了许多比较措施,Bresenham的实现不仅比Graphics.drawArc慢,而且比三角法慢。请查看以下绘制的不同数量圆的测量值

我的实现中哪一部分更耗时?有什么方法可以改进它吗?谢谢你的帮助

[版本]:根据@higuaro的要求,这里是我画圆的三角算法

public static void drawTrigonometricalCircle (double r, double width, double height, Graphics g) {

    double x0 = 0;
    double y0 = 0;
    boolean isStart = true;

    for (double angle = 0; angle <= 2*Math.PI; angle = angle + Math.PI/36) {

        double x = r * Math.cos(angle);
        double y = r * Math.sin(angle);

        drawPoint((double)x, y, width, height, g);

        if (!isStart) {
            drawLine(x0,  y0, x, y, width, height, g);
        }

        isStart = false;

        x0 = x;
        y0 = y;
    }
}
public static void draw三角圆(双r、双宽、双高、图形g){
双x0=0;
双y0=0;
布尔值isStart=true;

对于(双角度=0;角度您的Bresenham方法本身并不慢,只是相对比较慢

Swing的
drawArc()
实现依赖于机器,使用本机代码。使用Java永远无法打败它,所以不要费心尝试。(事实上,我很惊讶Java Bresenham方法与
drawArc()
相比速度如此之快,这证明了执行Java字节码的虚拟机的质量。)

然而,你的三角法速度太快了,因为你没有在平等的基础上把它和布雷森汉姆进行比较

trig方法的设定角度分辨率为
PI/36
(~4.7度),如
for
语句末尾的操作:

angle = angle + Math.PI/36  
同时,您的Bresenham方法依赖于半径,在每个像素变化时计算一个值
点,将其乘以
8
并除以
2*Pi
将获得等效的角度分辨率。因此,要与Bresenham方法处于同等地位,您的trig方法应具有:

resolution = 4 * r * Math.sqrt(2) / Math.PI;
在循环之外的某个地方,按如下方式为增加

angle += resolution
由于我们现在将回到像素级分辨率,您实际上可以改进trig方法,并删除后续
drawline
调用和
x0
y0
的赋值,消除不必要的强制转换,进一步减少对
Math
的调用

public static void drawTrigonometricalCircle (double r, double width, double height, 
    Graphics g) {

    double localPi = Math.PI;
    double resolution = 4 * r * Math.sqrt(2) / Math.PI;

    for (double angle = 0; angle <= localPi; angle += resolution) {
        double x = r * Math.cos(angle);
        double y = r * Math.sin(angle);
        drawPoint(x, y, width, height, g);
    }

}
public static void draw三角圆(双r、双宽、双高、,
图形(g){
double localPi=Math.PI;
双分辨率=4*r*Math.sqrt(2)/Math.PI;

对于(double angle=0;angle,您的问题在于Bresenham的算法根据圆的大小进行可变次数的迭代,而您的三角法总是进行固定次数的迭代

这也意味着Bresenham的算法将始终生成外观平滑的圆,而随着半径的增加,三角法将生成外观更差的圆

为了使其更加均匀,请更改三角法以生成与Bresenham实现大致相同的点,您将看到它的速度有多快

我编写了一些代码来对此进行基准测试,还打印了生成的点数,以下是初步结果:

三角:181ms,平均73点
布雷森汉姆:120毫秒,平均867.568分

修改三角函数类以对更平滑的圆执行更多迭代后:

    int totalPoints = (int)Math.ceil(0.7 * r * 8);
    double delta = 2 * Math.PI / totalPoints;
    for (double angle = 0; angle <= 2*Math.PI; angle = angle + delta) {
inttotalpoints=(int)Math.ceil(0.7*r*8);
双增量=2*Math.PI/总点;

对于(double angle=0;angle我最近自己为一个sprite光栅化器编写了一个bresenham圆形绘图实现,并尝试对其进行一些优化。我不确定它是否会比您所做的更快或更慢,但我认为它应该有一个相当不错的执行时间

也很不幸,它是用C++编写的。如果我明天有时间,我可以用一个移植的java版本和一个示例图片来编辑我的答案,但是现在你必须自己做(如果有人想花时间编辑它)。 基本上,它所做的是使用bresenham算法获取圆外缘的位置,然后对圆的1/8执行该算法,并通过从中心到外缘绘制直线来镜像其余7个部分的位置

颜色
只是一种颜色
angle += resolution
public static void drawTrigonometricalCircle (double r, double width, double height, 
    Graphics g) {

    double localPi = Math.PI;
    double resolution = 4 * r * Math.sqrt(2) / Math.PI;

    for (double angle = 0; angle <= localPi; angle += resolution) {
        double x = r * Math.cos(angle);
        double y = r * Math.sin(angle);
        drawPoint(x, y, width, height, g);
    }

}
    int totalPoints = (int)Math.ceil(0.7 * r * 8);
    double delta = 2 * Math.PI / totalPoints;
    for (double angle = 0; angle <= 2*Math.PI; angle = angle + delta) {