简体   繁体   English

请为我解释一下Bresenham Line绘图代码

[英]please explain this Bresenham Line drawing code for me

I have searched throughout the Internet and found hundreds of implementation of Bresenham's line drawing algorithm. 我在整个互联网上搜索过,发现了数百个Bresenham的绘图算法。 But, one thing I found strange is, only two or three of them can cover all of the eight octets. 但是,有一件事我觉得很奇怪,其中只有两三个可以覆盖所有八个八位字节。 still, they are being used in many applications. 但是,它们正在许多应用中使用。

For example, this lady implemented this version (line 415) of Bresenham's algorithm. 例如, 这位女士实施了Bresenham算法的这个版本(第415行) But, it doesn't cover the whole 360 degrees. 但是,它并没有涵盖整个360度。 This guy here seems to be developing a library. 这家伙似乎在开发一个图书馆。 But still it doesn't work properly. 但它仍然无法正常工作。

Can you tell me why? 你能告诉我为什么吗?

This guy's implementation works fine. 这家伙的实施工作正常。 But, I suppose it is not Bresenham's Algorithm. 但是,我想这不是布雷森汉姆的算法。 It has a very few similarity with the theory . 它与该理论的相似性很小。

Finally, I found the following version of Bresenham's Line Drawing Algorithm that works properly. 最后,我发现以下版本的Bresenham线绘制算法正常工作。

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

The original code is quite strange. 原始代码很奇怪。 So, I need your help to understand that. 所以,我需要你的帮助来理解这一点。

  • Why is he left shifting dx and dy and then, before calculation, again right-shifting them? 他为什么要离开dx和dy,然后在计算之前再次右移它们?

  • Where are the dt and ds that the theory talks about? 理论谈到的dt和ds在哪里?

  • According to the theory, dt and ds should have been tested in every step of while loop. 根据该理论,dt和ds应该在while循环的每个步骤中进行测试。 But, this code isn't doing so. 但是,这段代码并没有这样做。 Why? 为什么?

  • The theory seems to have no indication of any error value processing. 该理论似乎没有任何错误值处理的迹象。 What is the use of error that the code is calculating? 代码计算error的用途是什么? How is he calculating the error ? 他是如何计算error How is he using the error value? 他是如何使用error值的?

  • What is the logic behind the test if(dx >= dy) ? if(dx >= dy) ,测试背后的逻辑是什么?

Here is an explanation of my own version of Bresenham. 这是我自己的Bresenham版本的解释。

We start with the parametric equation of a line, (X + t.Dx, Y + t.Dy) , where t is a parameter in range [0, 1] . 我们从一条线的参数方程开始, (X + t.Dx, Y + t.Dy) ,其中t是范围[0, 1]的参数。 The endpoints are obviously (X, Y) and (X + Dx, Y + Dy) . 端点显然是(X, Y)(X + Dx, Y + Dy)

To turn it to a digital line, we want exactly one pixel per row or per column, whichever ensures a continuous line. 要将其转换为数字线,我们只需要每行或每列一个像素,以确保连续线为准。 So defining D = Max(|Dx|, |Dy|) , we will draw D+1 points corresponding to fractional t : (X + I.Dx/D, Y + I.Dy/D) , with I in [0, D] . 因此定义D = Max(|Dx|, |Dy|) ,我们将绘制对应于分数t D+1个点: (X + I.Dx/D, Y + I.Dy/D) ,其中I[0, D]

Let us assume for a moment 0 <= Dy < Dx = D , and the equation simplifies to (X + I, Y + I.Dy/Dx) . 让我们假设片刻0 <= Dy < Dx = D ,并且该等式简化为(X + I, Y + I.Dy/Dx) As the last term may not be an integer, we will round it with round(I.Dy/Dx) = floor(I.Dy/Dx + 1/2) = floor((I.Dy + Dx/2) / Dx) . 由于最后一项可能不是整数,我们将用round(I.Dy/Dx) = floor(I.Dy/Dx + 1/2) = floor((I.Dy + Dx/2) / Dx)

The latter expression is the quotient of numbers following an arithmetic progression over a denominator larger than the common difference. 后一个表达式是在大于公共差异的分母上的算术级数之后的数字的商。 Hence, when you increment I , the ratio either stays fixed or increases by 1 . 因此,当您增加I ,该比率要么保持固定,要么增加1 For a division-less implementation, we use a trick: keep the quotient and the remainder of the division, let Q and R , and every time you increase I , update these. 对于无分区实现,我们使用一个技巧:保持商和除法的余数,让QR ,每次增加I ,更新这些。

Let N = I.Dy + Dx/2 , and Q = N / Dx , R = N % Dx . N = I.Dy + Dx/2 ,并且Q = N / DxR = N % Dx Then increasing I , I' = I + 1 , N' = N + Dy , Q' = (N + Dy) / Dx , R' = (N + Dy) % Dx . 然后增加II' = I + 1N' = N + DyQ' = (N + Dy) / DxR' = (N + Dy) % Dx As you can check, the following rule holds: 您可以检查,以下规则成立:

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

We now put all elements together, adjust for signs and distinguish the more-horizontal and more-vertical cases (as you will note, there is no need to represent Q explicitly): 我们现在将所有元素放在一起,调整符号并区分更水平和更垂直的情况(正如您将注意到的,没有必要明确表示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++ Implementation (by @anonymous) C / C ++实现 (@anonymous)

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

Why is he left shifting dx and dy and then, before calculation, again right-shifting them? 他为什么要离开dx和dy,然后在计算之前再次右移它们?

He uses half of an int in a way that expects it to be accurate. 他以一种期望它准确的方式使用一半的int。 But half an int would be truncated. 但是半个int会被截断。 So by using a representation that is inherently doubled, he can use a right shift as an untruncated divide by two. 因此,通过使用本质上加倍的表示,他可以使用右移作为未截断的除以2。 That is not how I learned Bresenham's long long ago. 这不是我很久以前学习布雷森汉姆的原因。 But the intent seems clear. 但意图似乎很清楚。

Where are the dt and ds that the theory talks about? 理论谈到的dt和ds在哪里?

I didn't read your theory link carefully, but the way I learned Bresenham's is simpler than that. 我没有仔细阅读你的理论链接,但我学习Bresenham的方式比这简单。 The original point was for very primitive CPU's in which you want to minimize the computation. 原始点是非常原始的CPU,您希望在其中最小化计算。

The theory seems to have no indication of any error value processing. 该理论似乎没有任何错误值处理的迹象。 What is the use of error that the code is calculating? 代码计算错误的用途是什么? How is he calculating the error? 他是如何计算错误的? How is he using the error value? 他是如何使用错误值的?

I expect just a terminology difference is confusing you. 我希望只是一个术语上的区别让你感到困惑。 The key point of the algorithm (in any form) is an accumulator representing the cumulative "error" vs. a non digitized line. 算法的关键点(以任何形式)是表示累积“误差”与非数字化线的累加器。 That "error" might normally have a different name, but under any name, it is the heart of the code. “错误”通常可能有不同的名称,但在任何名称下,它都是代码的核心。 You should be able to see that it is used at each step to decide which direction that step is digitized in. 您应该能够看到在每个步骤中使用它来确定该步骤的数字化方向。

What is the logic behind the test if(dx >= dy)? 如果(dx> = dy),测试背后的逻辑是什么?

The idea is that the faster changing direction changes by 1 on every step and the slower changing direction changes by 0 or 1 per step depending on that cumulative "error". 这个想法是每个步骤的更快方向改变方向改变1,并且较慢的改变方向每步改变0或1,这取决于累积的“错误”。 Back when code size was a major factor, the real trick to this algorithm was to code it so the code was shared across the major difference of X faster vs. Y faster. 回到代码大小是一个主要因素时,这个算法的真正诀窍就是对它进行编码,以便代码在X的快速与Y之间的主要差异中共享。 But clearly the whole thing is simple to understand if you look at the X changing faster case separately from the Y changing faster case. 但很明显,如果你看看X改变更快的情况与Y改变更快的情况分开,整个事情很容易理解。

  • Why is he left shifting dx and dy and then, before calculation, again right-shifting them? 他为什么要离开dx和dy,然后在计算之前再次右移它们?

I explain below. 我在下面解释。

  • Where are the dt and ds that the theory talks about? 理论谈到的dt和ds在哪里?

They are gone, the implementation implements the midpoint line-drawing algorithm. 它们消失了,实现实现了中点线绘制算法。 You can derive one algorithm from the other though. 您可以从另一个算法中派生出一个算法。 It's an exercise for the reader :-) 这是读者的练习:-)

  • According to the theory, dt and ds should have been tested in every step of while loop. 根据该理论,dt和ds应该在while循环的每个步骤中进行测试。 But, this code isn't doing so. 但是,这段代码并没有这样做。 Why? 为什么?

Same as above. 与上述相同。 It is testing for error, which is the same thing. 它正在测试错误,这是同样的事情。

  • The theory seems to have no indication of any error value processing. 该理论似乎没有任何错误值处理的迹象。 What is the use of error that the code is calculating? 代码计算错误的用途是什么? How is he calculating the error? 他是如何计算错误的? How is he using the error value? 他是如何使用错误值的?

The equation of the line a*x + b*y + c = 0 , where a = x2 - x1 and b = -(y2 - y1) can give an indication of the error, as a*x + b*y + c is proportional to the distance of a point (x, y) from the line, with the real coefficients a , b and c . 线a*x + b*y + c = 0 a = x2 - x1 ,其中a = x2 - x1b = -(y2 - y1)可以给出误差的指示,如a*x + b*y + c与点(x, y)距离线的距离成正比,与实数系数abc成正比。 We can multiply the equation with an arbitrary real constant k , not equal to 0 and it will still hold for every point on the line. 我们可以将方程乘以任意实常数k ,不等于0,它仍然适用于线上的每个点。

Assume we draw in the first quadrant only. 假设我们仅在第一象限中绘制。 At each step we wish to evaluate a*x + b*y + c for (x + 1, y + 1/2) to see how distant from the line that (mid)point is and based on that decide whether we increment y or not, but the distance does not matter, only its sign. 在每个步骤,我们希望评估a*x + b*y + c(x + 1, y + 1/2)以查看与(中间)点的距离有多远并且基于该决定我们是否增加y或不,但距离无关紧要,只有它的标志。 For the first quadrant the distance will be positive, if the line is above the midpoint (x + 1, y + 1/2) and negative if below. 对于第一象限,如果线在中点(x + 1, y + 1/2)之上,则距离为正,如果在下方,则为负。 If the line is above the midpoint, we must go "up". 如果线高于中点,我们必须“向上”。

So we know for initial (x, y) , a*x + b*y + c = 0 , but how about (x + 1, y + 1/2) ? 所以我们知道初始(x, y)a*x + b*y + c = 0 ,但是(x + 1, y + 1/2)怎么样? The error term equals a + b/2 . 误差项等于a + b/2 We don't like halves, so we multiply (shift to left) a and b by 1 and so obtain the initial error of 2*a + b , this is the reason for the right shift. 我们不喜欢一半,所以我们乘以(向左移动) ab乘以1,因此得到2*a + b的初始误差,这就是右移的原因。 If the error is positive, then the line is above the midpoint (x + 1, y + 1/2) and we must go up. 如果误差为正,则该线高于中点(x + 1, y + 1/2) ,我们必须上升。 If negative, then the line is below the midpoint and we leave y be. 如果是负数,则该线低于中点,我们离开y We update the error incrementally at each step, depending on whether we incremented y or not. 我们在每一步都会逐步更新错误,具体取决于我们是否递增y

With some thought you can extend the algorithm to all quadrants. 通过一些思考,您可以将算法扩展到所有象限。

  • What is the logic behind the test if(dx >= dy)? 如果(dx> = dy),测试背后的逻辑是什么?

We test for steepness of the line. 我们测试线路的陡度。 This makes swaps unnecessary. 这使交换变得不必要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM