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.
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.
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:-
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.
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.
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.
atan2(y2-y1, x2-x1)
. –
Trinitrophenol draw_polygon
(or, in fact, fill_polygon
) then? –
Fiden 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:-
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.
Some simple routes to use:
The easiest way to create a line of almost arbitrary thickness would be to first do a bresenham, then apply as many dilation iterations as you wish. Each dilation pads both sides of your line equally.
EDIT: It is also worth noting that this method has the nice feature of being easily generalizable to 3D, because both Bresenham and dilation are easily generalizable to 3D.
Bresenham → thickness 1:
Dilation mask:
0 1 0
1 1 1
0 1 0
Bresenham + 1 dilation → thickness 2
Bresenham + 2 dilations → thickness 3
etc.
http://members.chello.at/~easyfilter/bresenham.html
The example at the bottom of this link is javascript, but should be easy enough to adapt to C. It's a fairly straightforward antialiasing algorithm to draw lines of variable thickness.
I assume that you would draw horizontal spans from one bounding line to another, and compute the x-value of each of the lines by Bresenham's method as you go (in a single loop).
Haven't tried it.
The end points may need some attention, lest they look oddly cut-off.
I do this quite often to generate images of fibers and spheres for porous media simulations. I have a nice simple way do this using a very standard image analysis technique know as the 'distance transform'. This requires having access to some image analysis package. I use Python with Scipy, so this is no problem. Here is a demo to convert randomly distributed points into spheres:
import scipy as sp
import scipy.ndimage as spim
im1 = sp.rand(100, 100) < 0.995 # Create random points in space
dt = spim.distance_transform_edt(im1)
im2 = dt < 5 # To create sphere with a radius of 5
And that is it! The distance transform can be slow for very large images, but there are efficient version out there. For instance, ImageJ has a parallelized one. Obviously, to create thick fibers you just create your image of thin ones, then apply the steps 2 and 3 above.
For those who want a Python version here goes the code (based on the answer of @Fabel):
def drawline(x1,y1,x2,y2,**kwargs):
if kwargs.get('thickness')==None:
thickness=1
else:
thickness=kwargs['thickness']
if kwargs.get('roundcap')==None:
roundcap=False
else:
roundcap=True
angle = np.arctan2(y2-y1,x2-x1)
xx = np.zeros(4)
yy = np.zeros(4)
xx[0] = np.round(x1 + thickness*np.cos(angle+np.pi/2))
yy[0] = np.round(y1 + thickness*np.sin(angle+np.pi/2))
xx[1] = np.round(x1 + thickness*np.cos(angle-np.pi/2))
yy[1] = np.round(y1 + thickness*np.sin(angle-np.pi/2))
xx[2] = np.round(x2 + thickness*np.cos(angle-np.pi/2))
yy[2] = np.round(y2 + thickness*np.sin(angle-np.pi/2))
xx[3] = np.round(x2 + thickness*np.cos(angle+np.pi/2))
yy[3] = np.round(y2 + thickness*np.sin(angle+np.pi/2))
u,v=polygon(xx,yy)
if roundcap:
temp1x, temp1y = circle(x1,y1,thickness)
temp2x, temp2y = circle(x1,y1,thickness)
u = np.append(u,temp1x,temp2x)
v = np.append(v,temp1y,temp2y)
return u,v
When calling the function you can optionally specify thickness and roundcap. For example:
drawline(10,10,50,50,thickness=3,roundcap=False)
polygon
here. What is that? –
Batwing polygon
which has no import or implementation. Where is that? –
Batwing For my embedded thermal printer application, using Bresenham's algorithm, the line was way too thin. I don't have GL or anything fancy. I ended up simply decrementing the Y value and drawing more lines under the first one. Each number of thickness added another line. Very quick to implement and made for the desired results printing from monochrome bitmap to the thermal.
1
, and always longer than requested by the thickness. –
Fiden © 2022 - 2024 — McMap. All rights reserved.