请为我解释这张Bresenham Line绘图代码

时间:2015-08-27 14:00:01

标签: c++ algorithm graphics line bresenham

我在整个互联网上搜索过,发现了数百个Bresenham的线条绘制算法的实现。但是,有一件事我觉得很奇怪,其中只有两三个可以覆盖所有八个八位字节。但是,它们正在许多应用中使用。

例如,this lady实施了Bresenham算法的this version (line 415)。但是,它并没有覆盖整个360度。 This guy here似乎正在开发一个图书馆。但它仍然无法正常工作。

你能告诉我为什么吗?

This guy's implementation运行正常。但是,我想这不是布雷森汉姆的算法。它与the theory的相似性很小。

最后,我发现Bresenham的线绘制算法的the following version可以正常工作。

#include "utils.h"

void Bresenham(int x1, int y1, int const x2, int const y2, int color)
{
    int dx = x2 - x1;
    // if x1 == x2, then it does not matter what we set here
    int ix((dx > 0) - (dx < 0));

    dx = abs(dx) << 1;

    int dy = y2 - y1;
    // if y1 == y2, then it does not matter what we set here
    int iy((dy > 0) - (dy < 0));
    dy = abs(dy) << 1;

    PlotPixel(x1, y1, color);

    if (dx >= dy)
    {
        // error may go below zero
        int error(dy - (dx >> 1));

        while (x1 != x2)
        {
            if ((error >= 0) && (error || (ix > 0)))
            {
                error -= dx;
                y1 += iy;
            }
            // else do nothing

            error += dy;
            x1 += ix;

            PlotPixel(x1, y1, color);
        }
    }
    else
    {
        // error may go below zero
        int error(dx - (dy >> 1));

        while (y1 != y2)
        {
            if ((error >= 0) && (error || (iy > 0)))
            {
                error -= dy;
                x1 += ix;
            }
            // else do nothing

            error += dx;
            y1 += iy;

            PlotPixel(x1, y1, color);
        }
    }
}

int main()
{
    int gm = DETECT;
    int gd = DETECT;

    initgraph(&gm, &gd, "");

    double x1 = 0;
    double y1 = 0;
    double r = 50;
    double x2 = 0;
    double y2 = 0;
    double signx = 0;
    double signy = 0;

    for(int theta=0 ; theta<=360 ; theta++)
    {
        x2 = r * cos(DegreeToRad((double) theta));
        y2 = r * sin(DegreeToRad((double) theta));

        x1 = 5 * cos(DegreeToRad((double) theta));
        y1 = 5 * sin(DegreeToRad((double) theta));

        Bresenham(x1, y1, x2, y2, YELLOW);

        //delay(10);
    }

    getch();
    closegraph();
    return 0;
}

原始代码很奇怪。所以,我需要你的帮助来理解这一点。

  • 他为什么要离开dx和dy然后在计算之前再次右移它们?

  • the theory谈到的dt和ds在哪里?

  • 根据理论,dt和ds应该在while循环的每个步骤中进行测试。但是,这段代码并没有这样做。为什么呢?

  • 该理论似乎没有任何错误值处理的迹象。代码计算的error有什么用?他如何计算error?他如何使用error值?

  • 测试if(dx >= dy)背后的逻辑是什么?

3 个答案:

答案 0 :(得分:3)

以下是我自己版本的Bresenham的解释。

我们从一行(X + t.Dx, Y + t.Dy)的参数方程开始,其中t是范围[0, 1]中的参数。端点显然是(X, Y)(X + Dx, Y + Dy)

要将其转换为数字线,我们只需要每行或每列一个像素,以确保连续线为准。因此,定义D = Max(|Dx|, |Dy|),我们将在D+1中绘制与小数t(X + I.Dx/D, Y + I.Dy/D)相对应的I点,[0, D]

让我们暂时假设0 <= Dy < Dx = D,并将等式简化为(X + I, Y + I.Dy/Dx)。由于最后一个术语可能不是整数,我们将使用round(I.Dy/Dx) = floor(I.Dy/Dx + 1/2) = floor((I.Dy + Dx/2) / Dx)对其进行舍入。

后一个表达式是在大于公共差值的分母上的算术级数之后的数字的商。因此,当您增加I时,该比率将保持固定或增加1。对于无分区实现,我们使用一个技巧:保留商和除法的余数,让QR,并且每次增加I时,更新这些。 / p>

允许N = I.Dy + Dx/2Q = N / DxR = N % Dx。然后增加II' = I + 1N' = N + DyQ' = (N + Dy) / DxR' = (N + Dy) % Dx。您可以检查以下规则:

if R + Dy >= Dx
    Q' = Q + 1; R' = R + Dy - Dx
else
    Q' = Q; R' = R + Dy

我们现在将所有元素放在一起,调整符号并区分更水平和更垂直的情况(正如您将注意到的,无需明确表示Q):

# Increments
Sx= Sign(Dx); Sy= Sign(Dy)

# Segment length
Dx= |Dx|; Dy= |Dy|; D= Max(Dx, Dy)

# Initial remainder
R= D / 2

if Dx > Dy:
    # Main loop
    for I= 0..D:
        Set(X, Y)

        # Update (X, Y) and R
        X+= Sx; R+= Dy # Lateral move
        if R >= Dx
            Y+= Sy; R-= Dx # Diagonal move
else:
    # Main loop
    for I= 0..D:
        Set(X, Y)

        # Update (X, Y) and R
        Y+= Sy; R+= Dx # Lateral move
        if R >= Dy
            X+= Sx; R-= Dy # Diagonal move

C / C ++实施(@匿名)

void Set(int x, int y, int color)
{
    PlotPixel(x, y, color);
}

int Sign(int dxy)
{
    if(dxy<0) return -1; 
    else if(dxy>0) return 1; 
    else return 0;
}
void Bresenham(int x1, int y1, int x2, int y2, int color)
{
    int Dx = x2 - x1;
    int Dy = y2 - y1;

    //# Increments
    int Sx = Sign(Dx); 
    int Sy = Sign(Dy);

    //# Segment length
    Dx = abs(Dx); 
    Dy = abs(Dy); 
    int D = max(Dx, Dy);

    //# Initial remainder
    double R = D / 2;

    int X = x1;
    int Y = y1;
    if(Dx > Dy)
    {   
        //# Main loop
        for(int I=0; I<D; I++)
        {   
            Set(X, Y, color);
            //# Update (X, Y) and R
            X+= Sx; R+= Dy; //# Lateral move
            if (R >= Dx)
            {
                Y+= Sy; 
                R-= Dx; //# Diagonal move
            }
        }
    }
    else
    {   
        //# Main loop
        for(int I=0; I<D; I++)
        {    
            Set(X, Y, color);
            //# Update (X, Y) and R
            Y+= Sy; 
            R+= Dx; //# Lateral move
            if(R >= Dy)
            {    
                X+= Sx; 
                R-= Dy; //# Diagonal move
            }
        }
    }
}

答案 1 :(得分:2)

  

为什么他会在计算之前再次移动dx和dy   右移他们?

他以一种期望它准确的方式使用一半的int。但是半个int会被截断。因此,通过使用本质上加倍的表示,他可以使用右移作为未截断的除以2。这不是我很久以前学习布雷森汉姆的方式。但意图似乎很清楚。

  

理论谈论的dt和ds在哪里?

我没有仔细阅读你的理论链接,但我学习Bresenham的方式比这简单。最初的观点是非常原始的CPU,您希望在其中最小化计算。

  

该理论似乎没有任何错误值处理的迹象。   代码计算错误的用途是什么?他怎么样   计算错误?他是如何使用错误值的?

我希望只是一个术语上的区别让你感到困惑。算法的关键点(以任何形式)是一个累加器,表示累积的&#34;错误&#34;与非数字化线。那&#34;错误&#34;通常可能有不同的名称,但在任何名称下,它都是代码的核心。您应该能够看到在每个步骤中使用它来确定该步骤的数字化方向。

  

测试背后的逻辑是什么(dx&gt; = dy)?

这个想法是,每一步更快的变化方向改变1,并且慢速改变方向每步改变0或1,这取决于累积的&#34;错误&#34;。回到代码大小是一个主要因素时,这个算法的真正诀窍就是对它进行编码,以便代码在X的快速与Y之间的主要差异中共享。但很明显,如果你看看X改变更快的情况与Y改变更快的情况分开,整个事情很容易理解。

答案 2 :(得分:0)

  • 为什么他会在计算之前再次移动dx和dy 右移他们?

我在下面解释。

  • 理论谈论的dt和ds在哪里?

它们消失了,实现实现了中点线绘制算法。您可以从另一个算法中派生出一个算法。这是读者的练习: - )

  • 根据理论,dt和ds应该在每一个中都经过测试 while循环的一步。但是,这段代码并没有这样做。为什么?

与上述相同。它正在测试错误,这是一回事。

  • 该理论似乎没有任何错误值处理的迹象。 代码计算错误的用途是什么?他怎么样 计算错误?他如何使用错误值?

a*x + b*y + c = 0的等式,其中a = x2 - x1b = -(y2 - y1)可以指示错误,因为a*x + b*y + c与点(x, y)的距离成正比1}}来自该行,具有实数系数abc。我们可以将方程乘以任意实常数k,不等于0,它仍然适用于该线上的每个点。

假设我们仅在第一象限中绘制。在每个步骤中,我们希望评估a*x + b*y + c (x + 1, y + 1/2)以查看距离(中间)点的距离是多远,并根据该决定是否增加y,但距离无关紧要,只有它的标志。对于第一象限,如果线在中点(x + 1, y + 1/2)之上,则距离为正,如果在下方,则为负。如果该线高于中点,我们必须去&#34;向上&#34;。

所以我们知道最初的(x, y)a*x + b*y + c = 0,但(x + 1, y + 1/2)怎么样?错误术语等于a + b/2。我们不喜欢一半,所以我们将({1}}和a乘以({1}}和b乘以1,因此获得2*a + b的初始误差,这就是正确的原因转移。如果误差为正,则该线高于中点(x + 1, y + 1/2),我们必须上升。如果是负数,则该线低于中点,我们离开y。我们在每一步都会逐步更新错误,具体取决于我们是否增加了y

有些人认为你可以将算法扩展到所有象限。

  • 测试背后的逻辑是什么(dx&gt; = dy)?

我们测试线的陡度。这使交换变得不必要。