Bresenham's line algorithm
About the code
A simple line
y = m * x + b
m is easy to understand. It's the slope of the line - the amount of increase in y divided by the amount of increase
m = (y1 - y0) / (x1 - x0)
I don't want to compute b, so let's rework a little bit our equation. We had:
y = m * x + b
In particular, our first point (x0, y0) belong to this line so it also follow this equation:
y0 = m * x0 + b
Now if we substract this last equation from the previous one, we get:
y - y0 = m * (x - x0) + b - b
And b disappears. So after a little rewriting, we get a new formula for our line:
y = m * (x - x0) + y0
The simplest code to draw our line should then look like that:
// line coordinates
int x0 = 0;
int y0 = 0;
int x1 = 30;
int y1 = 10;
float slope = (float)(y1 - y0) / (float)(x1 - x0);
for (int x = x0; x <= x1; ++x)
{
float y = slope * (x - x0) + y0;
gfx.setPixel(x, y, Color(255, 255, 255));
}
gfx.render();
Download source code
Download executable for Windows
Here is the result:
How to make it faster ?
// line coordinates
int x0 = 0;
int y0 = 0;
int x1 = 30;
int y1 = 10;
float slope = (float)(y1 - y0) / (float)(x1 - x0);
for (int x = x0; x <= x1; ++x)
{
float y = slope * (x - x0) + y0;
gfx.setPixel(x, y, Color(255, 255, 255));
}
Note: I omitted the gfx.render() function because it is not part of the algorithm itself, it is only called at the
Removing the multiplication
float y = slope * (x - x0) + y0;
How does y change while x goes from x0 to x1 ?
float slope = (float)(y1 - y0) / (float)(x1 - x0);
float y = y0;
for (int x = x0; x <= x1; ++x)
{
gfx.setPixel(x, y, Color(255, 255, 255));
y += slope;
}
gfx.render();
Download source code
Download executable for Windows
That's a great improvement. There is a lot less calculation inside the loop.
Fixing the rounding
oldY = y + error
Now let's recapitulate our algorithm:
float slope = (float)(y1 - y0) / (float)(x1 - x0);
int y = y0;
float error = 0.0;
for (int x = x0; x <= x1; ++x)
{
gfx.setPixel(x, y, Color(255, 255, 255));
error += slope;
if (error >= 0.5)
{
y++;
error -= 1.0;
}
}
gfx.render();
Download source code
Download executable for Windows
And this is what we get:
Removing the floats
int y = y0;
float slope = (float)(y1 - y0) / (float)(x1 - x0);
float error = -0.5;
for (int x = x0; x <= x1; ++x)
{
gfx.setPixel(x, y, Color(255, 255, 255));
error += slope;
if (error >= 0.0)
{
y++;
error -= 1.0;
}
}
gfx.render();
Download source code
Download executable for Windows
Now we will do a little renaming job as it will be clearer for the next stage.
int y = y0;
int dx = x1 - x0;
int dy = y1 - y0;
float slope = (float)dy / (float)dx;
float error = -0.5;
float errorInc = -1.0;
for (int x = x0; x <= x1; ++x)
{
gfx.setPixel(x, y, Color(255, 255, 255));
error += slope;
if (error >= 0.0)
{
y++;
error += errorInc;
}
}
gfx.render();
Download source code
Download executable for Windows
Now we will convert the floating point values to integers.
int y = y0;
int dx = x1 - x0;
int dy = y1 - y0;
int slope = 2 * dy;
int error = -dx;
int errorInc = -2 * dx;
for (int x = x0; x <= x1; ++x)
{
gfx.setPixel(x, y, Color(255, 255, 255));
error += slope;
if (error >= 0)
{
y++;
error += errorInc;
}
}
gfx.render();
Download source code
Download executable for Windows
Was it worth the effort ?
void StartPerfCounter();
float StopPerfCounter();
You just call StartPerfCounter() at the beginning of the function you want to time, and StopPerfCounter() at the
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "main.h"
#include "Graphics.h"
#include "System.h"
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
void drawLineSlow(int x0, int y0, int x1, int y1, Color c)
{
float slope = (float)(y1 - y0) / (float)(x1 - x0);
for (int x = x0; x <= x1; ++x)
{
float y = slope * (x - x0) + y0;
gfx.setPixel(x, y, c);
}
}
void drawLine(int x0, int y0, int x1, int y1, Color c)
{
int y = y0;
int dx = x1 - x0;
int dy = y1 - y0;
int slope = 2 * dy;
int error = -dx;
int errorInc = -2 * dx;
for (int x = x0; x <= x1; ++x)
{
gfx.setPixel(x, y, c);
error += slope;
if (error >= 0)
{
y++;
error += errorInc;
}
}
}
int main(int argc, char* argv[])
{
// init the window
gfx.init("Bresenham Line 7", SCREEN_WIDTH, SCREEN_HEIGHT);
gfx.init2D();
// test the slow function
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
sys.StartPerfCounter();
for (int i = 10; i < SCREEN_WIDTH; ++i)
drawLineSlow(0, 0, i, 10, Color(i & 0xff, 255, 0));
float time1 = sys.StopPerfCounter();
printf("time1: %f ms\n", time1);
// test the fast function
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
sys.StartPerfCounter();
for (int i = 10; i < SCREEN_WIDTH; ++i)
drawLine(0, 0, i, 10, Color(i & 0xff, 255, 0));
float time2 = sys.StopPerfCounter();
printf("time2: %f ms\n", time2);
gfx.render();
while (sys.isQuitRequested() == false)
{
sys.processEvents();
}
gfx.quit();
return EXIT_SUCCESS;
}
Download source code
Download executable for Windows
Runing it 3 times on my computer that has an Intel Core i3-4330, I get an average of 0.938 ms for the first
Generalizing the algorithm
int x0 = 0;
int y0 = 0;
int x1 = 10;
int y1 = 30;
We get a 45 degrees line:
#define ABS(_x) ((_x) >= 0 ? (_x) : -(_x))
SGN returns the sign of a value: either -1, 1 or 0 if the number is negative, positive or null.
#define SGN(_x) ((_x) < 0 ? -1 : \
((_x) > 0 ? 1 : 0))
Now we will use 2 increment variables for x and y to handle all the cases.
void drawLine(int x0, int y0, int x1, int y1, Color c)
{
int y = y0;
int dx = x1 - x0;
int dy = y1 - y0;
int incX = SGN(dx);
int incY = SGN(dy);
int slope = 2 * ABS(dy);
int error = -ABS(dx);
int errorInc = -2 * ABS(dx);
for (int x = x0; x != x1 + incX; x += incX)
{
gfx.setPixel(x, y, c);
error += slope;
if (error >= 0)
{
y += incY;
error += errorInc;
}
}
}
Download source code
Download executable for Windows
Now it's easy to write all the cases. The vertical ones are just symetrical.
#define ABS(_x) ((_x) >= 0 ? (_x) : -(_x))
#define SGN(_x) ((_x) < 0 ? -1 : \
((_x) > 0 ? 1 : 0))
void drawLine(int x0, int y0, int x1, int y1, Color c)
{
int dx = x1 - x0;
int dy = y1 - y0;
int incX = SGN(dx);
int incY = SGN(dy);
dx = ABS(dx);
dy = ABS(dy);
if (dy == 0)
{
// horizontal line
for (int x = x0; x != x1 + incX; x += incX)
gfx.setPixel(x, y0, c);
}
else if (dx == 0)
{
// vertical line
for (int y = y0; y != y1 + incY; y += incY)
gfx.setPixel(x0, y, c);
}
else if (dx >= dy)
{
// more horizontal than vertical
int slope = 2 * dy;
int error = -dx;
int errorInc = -2 * dx;
int y = y0;
for (int x = x0; x != x1 + incX; x += incX)
{
gfx.setPixel(x, y, c);
error += slope;
if (error >= 0)
{
y += incY;
error += errorInc;
}
}
}
else
{
// more vertical than horizontal
int slope = 2 * dx;
int error = -dy;
int errorInc = -2 * dy;
int x = x0;
for (int y = y0; y != y1 + incY; y += incY)
{
gfx.setPixel(x, y, c);
error += slope;
if (error >= 0)
{
x += incX;
error += errorInc;
}
}
}
}
Download source code
Download executable for Windows
Testing
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "main.h"
#include "Graphics.h"
#include "System.h"
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
int main(int argc, char* argv[])
{
// init the window
gfx.init("Bresenham Line 10", SCREEN_WIDTH, SCREEN_HEIGHT);
gfx.init2D();
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
srand(time(NULL));
int x0 = rand() % SCREEN_WIDTH;
int y0 = rand() % SCREEN_HEIGHT;
int spdX0 = (rand() % 7) - 3;
int spdY0 = (rand() % 7) - 3;
int x1 = rand() % SCREEN_WIDTH;
int y1 = rand() % SCREEN_HEIGHT;
int spdX1 = (rand() % 7) - 3;
int spdY1 = (rand() % 7) - 3;
int r = rand() % 256;
int g = rand() % 256;
int b = rand() % 256;
int spdR = (rand() % 7) - 3;
int spdG = (rand() % 7) - 3;
int spdB = (rand() % 7) - 3;
while (sys.isQuitRequested() == false)
{
gfx.line(x0, y0, x1, y1, Color(r, g, b));
x0 += spdX0;
if (x0 < 0 || x0 >= SCREEN_WIDTH)
{
spdX0 = -spdX0;
x0 += spdX0;
}
y0 += spdY0;
if (y0 < 0 || y0 >= SCREEN_HEIGHT)
{
spdY0 = -spdY0;
y0 += spdY0;
}
x1 += spdX1;
if (x1 < 0 || x1 >= SCREEN_WIDTH)
{
spdX1 = -spdX1;
x1 += spdX1;
}
y1 += spdY1;
if (y1 < 0 || y1 >= SCREEN_HEIGHT)
{
spdY1 = -spdY1;
y1 += spdY1;
}
r += spdR;
if (r < 0 || r > 255)
{
spdR = -spdR;
r += spdR;
}
g += spdG;
if (g < 0 || g > 255)
{
spdG = -spdG;
g += spdG;
}
b += spdB;
if (b < 0 || b > 255)
{
spdB = -spdB;
b += spdB;
}
gfx.render();
sys.wait(10);
sys.processEvents();
}
gfx.quit();
return EXIT_SUCCESS;
}
Download source code
Download executable for Windows