how do I create a line of arbitrary thickness using Bresenham?
I am currently using Bresenham's algorithm to draw lines but they are (of course) one pixel in thickness. My question is what is the most efficient way to draw lines of arbitrary thickness?
The language I am using is C.
Take another Bresenham loop and use it to modify the start and end position of original line in rectangular direction. Problem is to efficently find the right starting point and not to draw any pixel twice (or skip a pixel) while drawing next line.
Working and tested C code is available from Github C code .
Here a test page including a few sample lines created by this code. The black pixels are the starting points for the algorithm.
Here is a paper and Delphi implementation of a modified version of Bresenham's algorithm for drawing thickened lines.
You may also want to take a look at Anti-Grain Geometry, a library for high-quality and high-performance software rendering of 2D graphics. Take a look at the demo page to get an idea of what it can do.
That paper on Murphy's Modified Bresenham Line Drawing looks useful, but link-only answers can have limited value here, so here's a little summary of it.
A line with thickness is a rectangle. The algorithm uses an outer Bresenham loop to step along one of the rectangle's edges without actually drawing it. An Bresenham loop draws a perpendicular line at each step of the outer loop. By passing not only the (x, y) coordinate but also the error term from the outer loop to the inner loop, it ensures that all of the perpendicular lines are "in phase" ensuring the rectangle is filled without gaps. Every pixel set is set exactly once.
I think the best way is to draw a rectangle rather than a line since a line with width is a two dimensional object. Tring to draw a set of parallel lines to avoid overdraw (to reduce write bandwidth) and underdraw (missing pixels) would be quite complex. It's not too hard to calculate the corner points of the rectangle from the start and end point and the width.
So, following a comment below, the process to do this would be:-
- Create a rectangle the same length as the required line and width equal to the desired width, so (0,0) to (width,length)
- Rotate and translate the rectangles corner coordinates to the desired position using a 2D transformation
- Rasterise the rotated rectangle, either using a hardware accelerated renderer (an OpenGL quad for example*) or use a software rasteriser. It can be rendered using a quad rasteriser or as a pair of triangles (top left and bottom right for example).
Note *: If you're using OpenGL you can also do Step 2 at the same time. Of course, using OpenGL does mean understanding OpenGL (big and complex) and this application may make that a tricky thing to implement at such a late stage in development.
For best accuracy, and also good performance for thicker lines especially, you can draw the line as a polygon. Some pseudo code:
draw_line(x1,y1,x2,y2,thickness)
Point p[4];
angle = atan2(y2-y1,x2-x1);
p[0].x = x1 + thickness*cos(angle+PI/2);
p[0].y = y1 + thickness*sin(angle+PI/2);
p[1].x = x1 + thickness*cos(angle-PI/2);
p[1].y = y1 + thickness*sin(angle-PI/2);
p[2].x = x2 + thickness*cos(angle-PI/2);
p[2].y = y2 + thickness*sin(angle-PI/2);
p[3].x = x2 + thickness*cos(angle+PI/2);
p[3].y = y2 + thickness*sin(angle+PI/2);
draw_polygon(p,4)
And optionally a circle could be drawn at each end point.
Some simple routes to use:
- for any width n where n is odd. for any point p plotted also plot the points above/below it for n/2 (if the line is > 45 degree angle draw side to side instead).
- not really a proper line of the right thickness, more like an italic pen, but very fast.
- for start point p(x,y) pick the points t0 and b such that they are centred on p but n pixels apart. for the end point do the same resulting in t1 b1. Draw lines from t0 -> t1, t1->b1, b1 -> t0, b0 -> t1. Fill in the resulting rectangle.
- The trick here is picking the points such that they appear orthogonal to the path direction.
- for each point p on the line instead of drawing a point draw a circle.
- This has the advantage of making the end points 'clean' no matter what the orientation.
- there should be no need to render any circles in solid except for the first.
- somewhat slow