Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 700
user366312 Avatar asked Oct 30 '25 08:10

user366312


2 Answers

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

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.

like image 31
JSF Avatar answered Oct 31 '25 23:10

JSF



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!