please explain this Bresenham Line drawing code for me

一世执手 提交于 2019-12-22 09:49:45

问题


I have searched throughout the Internet and found hundreds of implementation of Bresenham's line drawing algorithm. 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. But, it doesn't cover the whole 360 degrees. 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.

#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?

  • Where are the dt and ds that the theory talks about?

  • According to the theory, dt and ds should have been tested in every step of while loop. 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? How is he calculating the error? How is he using the error value?

  • What is the logic behind the test if(dx >= dy)?


回答1:


Here is an explanation of my own version of 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]. The endpoints are obviously (X, Y) and (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].

Let us assume for a moment 0 <= Dy < Dx = D, and the equation simplifies to (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).

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

Let N = I.Dy + Dx/2, and Q = N / Dx, R = N % Dx. Then increasing I, I' = I + 1, N' = N + Dy, Q' = (N + Dy) / Dx, R' = (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):

# 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)

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



回答2:


Why is he left shifting dx and dy and then, before calculation, again right-shifting them?

He uses half of an int in a way that expects it to be accurate. But half an int would be truncated. So by using a representation that is inherently doubled, he can use a right shift as an untruncated divide by two. 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?

I didn't read your theory link carefully, but the way I learned Bresenham's is simpler than that. The original point was for very primitive CPU's in which you want to minimize the computation.

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)?

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". 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. 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.




回答3:


  • Why is he left shifting dx and dy and then, before calculation, again right-shifting them?

I explain below.

  • Where are the dt and ds that the theory talks about?

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

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. 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. 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)? The error term equals 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. If the error is positive, then the line is above the midpoint (x + 1, y + 1/2) and we must go up. If negative, then the line is below the midpoint and we leave y be. We update the error incrementally at each step, depending on whether we incremented y or not.

With some thought you can extend the algorithm to all quadrants.

  • What is the logic behind the test if(dx >= dy)?

We test for steepness of the line. This makes swaps unnecessary.



来源:https://stackoverflow.com/questions/32251437/please-explain-this-bresenham-line-drawing-code-for-me

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!