I want to demonstrate the line renderer presented by Bresenham in [1], therefore I've implemented the algorithm in C as a software renderer. The window management and drawing of individual pixels is handled by SDL2 in this demonstration.
- SDL2
To install the dependencies on Arch Linux, run:
sudo pacman -S sdl2
To build the example, run:
make
To rebuild, run:
make rebuild
To remove the object files and the executable, run:
make clean
Run the example with:
./bin/bresenham
The start and end of the line are marked with red and green respectively.
Before we delve into the implementation of the Bresenham renderer, I want to give a brief overview of the framework for this example.
The window management and drawing is handled by SDL2. First, an SDL window is created, which is accessed with a pointer of type SDL_Window *
. With the window, an SDL renderer is created that can be accessed with a pointer of type SDL_Renderer *
. The SDL renderer can actually render lines already with SDL_RenderDrawLine()
, but for this example I'm going to use my own software implementation. The SDL renderer is only used to draw individual pixels and to render markers (to point out both ends of the line). SDL2 is also used to control the main loop of the example.
At the start of the run, two random points are generated to indicate the start and end of the line. This is done with the standard C library.
The renderer must first figure out in which octant with respect to
Let's consider the mathematical representation of a linear function:
In this case, we can see that the slope is
int xDiff = p_x2 - p_x1;
int yDiff = p_y2 - p_y1;
The absolute values
int xDiffAbs = (xDiff < 0) ? -xDiff : xDiff;
int yDiffAbs = (yDiff < 0) ? -yDiff : yDiff;
Now that we know the relevant details of the slope, we can figure out in which octant we should operate. We'll introduce the boolean variables
We use
bool posXDiff = xDiff >= 0;
We use
bool posYDiff = yDiff >= 0;
We use
bool shallow = xDiffAbs - yDiffAbs >= 0;
We can map
X | Y | Z | Octant |
---|---|---|---|
0 | 0 | 0 | 6 |
0 | 0 | 1 | 5 |
0 | 1 | 0 | 3 |
0 | 1 | 1 | 4 |
1 | 0 | 0 | 7 |
1 | 0 | 1 | 8 |
1 | 1 | 0 | 2 |
1 | 1 | 1 | 1 |
Now that we are able to determine the octant, we can set the frame of reference of the renderer accordingly. The frame of reference has an a- and b-axis, how this relates to the x- and y-axis depends on the octant.
First, let's declare the relevant variables:
int a, b;
int *x, *y;
int aDiff, bDiff;
int aInc, bInc;
int aTerm;
The first pair (a
and b
) represents a point
The important detail to note here is that the a- and b-axis fall along the x- and y-axis respectively if the slope is shallow. The axes are inverted if the slope is steep. All other variables are determined in a similar fashion.
The second pair variables (*x
and *y
) are pointers that are used to translate the point
The third pair (aDiff
and bDiff
) represents the displacement
The fourth pair (aInc
and bInc
) is used to make sure that the a- and b-axis point in the appropriate direction in the SDL screen space. Their values are determined by:
Earlier I mentioned that the renderer only moves in the positive a and b directions in theory. You can see here that it's not necessarily true in practice, as the coordinates are prematurely converted to SDL screen space for convenience. The renderer still works according to the theory in [1] however. In the end, the increments are still moving the points towards what are considered the positive a and b directions of the frame of reference.
The expression can be further simplified if the slope is tested beforehand, which is the case in my implementation. For shallow slopes, the expressions are:
For steep slopes, the expressions are:
Finally the last variable aTerm
is used to terminate the drawing procedure. It sits just outside the bounds of the line and is defined by:
The final value of a
is given by:
To put all of this verbose nonsense into practice, I've written the following:
if (shallow)
{
a = p_x1;
b = p_y1;
x = &a;
y = &b;
aDiff = xDiffAbs;
bDiff = yDiffAbs;
aInc = (posXDiff) ? 1 : -1;
bInc = (posYDiff) ? 1 : -1;
aTerm = p_x2 + aInc;
}
else
{
a = p_y1;
b = p_x1;
x = &b;
y = &a;
aDiff = yDiffAbs;
bDiff = xDiffAbs;
aInc = (posYDiff) ? 1 : -1;
bInc = (posXDiff) ? 1 : -1;
aTerm = p_y2 + aInc;
}
Now that the frame of reference is set, we can move on to the rendering procedure. Let's start with the
The
However, we're not going to figure out the values of
For which the initial value is set with [1]:
Utilizing this expression, the value of
After closer examination, we can see that that the equations for
If we rewrite this, we get a special case of the edge function from [2], where the function is equal to 0:
The edge function detects how much the point
We can now see the relation between
As you can see, expression for
Okay, enough ramling for now, let's see the actual C implementation:
int error = bDiff + bDiff - aDiff;
while (a != aTerm)
{
SDL_RenderDrawPoint(p_renderer, *x, *y);
a += aInc;
error += bDiff + bDiff;
if (error >= 0)
{
b += bInc;
error -= aDiff + aDiff;
}
}
For such a simple concept as drawing a line, the Bresenham algorithm turned out to be more complex than I initially thought, and I learned a lot during the process of implementing it as a software renderer. Also, deriving the equations myself gave me an opportunity to dust off my mathematical skills. I'm fascinated with image and video rendering techniques, and I'd like to learn a lot more about them in the future by working on projects like this.
[1] J. E. Bresenham, "Algorithm for computer control of a digital plotter" IBM Systems Journal, 1965.
[2] J. Pineda, "A Parallel Algorithm for Polygon Rasterization" Computer Graphics, vol. 22, no. 4, Aug. 1988.