A line drawing algorithm is a graphical algorithm for approximating a line segment on discrete graphical media such as pixel-based and printers. There are some general requirements for drawing a line, which are:
The equation of the straight line connects two points \((x_0, y_0)\) and \((x_1, y_1)\) is \begin{equation} ax + by + c = 0, \tag{1} \end{equation} in which: \(a = y_1 - y_0\), \(b = -(x_1 - x_0)\), \(c = x_1y_0 - x_0y_1\).
If \(x_1 \neq x_2\), we can frame the equation in the slope-intercept form as below: \begin{equation} y = mx + b, \tag{2} \end{equation} in which:
\[\begin{align} m &= \dfrac{\Delta y}{\Delta x}, \tag{3}\\ b &= y_1 - mx_1 = y_0 - mx_0, \tag{4}\\ \Delta x &= x_1 - x_0, \tag{5}\\ \Delta y &= y_1 - y_0. \tag{6} \end{align}\]For our line drawing algorithms, we have to distinguish between the two following cases. In each case, we can frame the problem of drawing a line in different ways:
They are quite similar, aren’t they? One problem can be framed as the other problem by swapping the roles between \(x\) and \(y\). So for simplicity, We will assume that \(x_0 < x_1\) and the slope of our line is between \(-1\) and \(1\).
With direct implementation using the line equation, we will loop the \(x\) value from \(x_0\) to \(x_1\). For each \(x\) value, we plug it into the equation \((2)\) to find the corresponding \(y\) value.
This algorithm performs a floating-point multiplication for every \(x\) value. Therefore, it requires an enormous number of floating-point multiplications and is therefore expensive.
The following Python function is to draw a line using the line equation directly:
def draw_line_equation(x_0, y_0, x_1, y_1):
vertices = []
if x_0 == x_1 and y_0 == y_1:
vertices += [x_0, y_0]
else:
d_x = x_1 - x_0
d_y = y_1 - y_0
if abs(d_x) >= abs(d_y):
if x_0 > x_1:
x_0, x_1 = x_1, x_0
y_0, y_1 = y_1, y_0
m = float(d_y) / d_x
b = y_0 - m * x_0
for x in range(x_0, x_1 + 1):
vertices += [x, int(math.floor(m * x + b + 0.5))]
else:
if y_0 > y_1:
x_0, x_1 = x_1, x_0
y_0, y_1 = y_1, y_0
m = float(d_x) / d_y
b = x_0 - m * y_0
for y in range(y_0, y_1 + 1):
vertices += [int(math.floor(m * y + b + 0.5)), y]
return vertices
You can find the complete version of the code here.
DDA line drawing algorithm, also known as Digital Differential Analyzer
follows an incremental approach, which performs computations at each step and uses the outcome of the previous step. It draws the given line according to the difference in the pixel points.
We only need to compute \(m\) once, as the start of the scan-conversion. The DDA line drawing algorithm does not use the floating-point multiplication which makes it faster than the direct implementation using the line equation, however, it does use the floating-point addition. It is not precise because of the usage of floating-point representation could cause computed points to drift away from their actual position when the line is relatively long.
The following Python function is to draw a line using DDA algorithm:
def draw_line_dda(x_0, y_0, x_1, y_1):
vertices = []
if x_0 == x_1 and y_0 == y_1:
vertices += [x_0, y_0]
else:
d_x = x_1 - x_0
d_y = y_1 - y_0
if abs(d_x) >= abs(d_y):
if x_0 > x_1:
x_0, x_1 = x_1, x_0
y_0, y_1 = y_1, y_0
m = float(d_y) / d_x
y = y_0
for x in range(x_0, x_1 + 1):
vertices += [x, int(math.floor(y + 0.5))]
y = y + m
else:
if y_0 > y_1:
x_0, x_1 = x_1, x_0
y_0, y_1 = y_1, y_0
m = float(d_x) / d_y
x = x_0
for y in range(y_0, y_1 + 1):
vertices += [int(math.floor(x + 0.5)), y]
x = x + m
return vertices
You can find the complete version of the code here.
Bresenham line drawing algorithm also provides a raster scan method for generating lines where incremental integer calculations are used. The developer of the algorithm is Jack Elton Bresenham, and the algorithm was named after him.
Now let’s understand Bresenham’s approach, considering a scan-conversion technique for a line having a positive slope of less than \(1\). This method allows integer-only arithmetic, which is generally faster than using floating-point arithmetic. It begins from the left endpoint \((x_0, y_0)\) of the given line, then each consecutive column is considered. In one column, a pixel is plotted where the \(y\) value (at that column) of the mathematical line closer to. Let’s suppose that we have the pixel \((x_k, y_k)\) to be displayed, the question becomes which one of the two pixels \((x_k + 1, y_k)\) and \((x_k + 1, y_k + 1)\) we will display next.
Figure 1. Bresenham line drawing algorithm.
In the column \(x_k + 1\), the vertical separations by the mathematical line are labelled as \(d_1\) and \(d_2\). The \(y\) value of the mathematical line at that \(x_k + 1\) column is \begin{equation} y = m(x_k + 1) + b. \tag{7} \end{equation}
So \(d_1\) and \(d_2\) are given as follows:
\[\begin{align} d_1 &= y - y_k\\ &= m(x_k + 1) + b - y_k \tag{8} \end{align}\]and
\[\begin{align} d_2 &= (y_k + 1) - y\\ &= y_k + 1 - m(x_k + 1) - b. \tag{9} \end{align}\]You can use those to determine which pixel is closer to the \(y\) value (at that \(x_k + 1\) column) of the mathematical line. Let’s have a look at the following subtraction:
\[\begin{align} d_1 - d_2 = 2m(x_k + 1) - 2y_k + 2b - 1. \tag{10} \end{align}\]A positive \(d_1 - d_2\) means that pixel \((x_k + 1, y_k + 1)\) is closer to the \(y\) value (at that \(x_k + 1\) column) of the mathematical line; otherwise \((x_k + 1, y_k)\) is the closer pixel. The decision parameter \(p_k\) for the \(k^{th}\) iteration of the Bresenham line drawing algorithm is given by
\[\begin{align} p_k &= \Delta x(d_1 - d_2)\\ &= \Delta x(2m(x_k + 1) - 2y_k + 2b - 1). \tag{11} \end{align}\]From \((3)\) we have \(m = \dfrac{\Delta y}{\Delta x}\), let’s plug it in \((11)\). The formula for decision parameter becomes
\[\begin{align} p_k &= \Delta x(2\dfrac{\Delta y}{\Delta x}(x_k + 1) - 2y_k + 2b - 1)\\ &= 2\Delta y.x_k + 2\Delta y - 2\Delta x.y_k + 2\Delta x.b - \Delta x\\ &= 2\Delta y.x_k - 2\Delta x.y_k + 2\Delta y + 2\Delta x.b - \Delta x\\ &= 2\Delta y.x_k - 2\Delta x.y_k + C. \tag{12} \end{align}\]Here, we have the sign of \(p_k\) is equal to \(d_1 - d_2\) as \(\Delta x\) is greater than \(0\). The value of the parameter \(C\) is \(2\Delta y + 2\Delta x.b -\Delta x\) which is constant, not affected by the pixel position and can be removed in the recursive calculation of \(p_k\).
Integrated changes along the line occur in unit steps in any of the direction \(x\) or \(y\). Therefore, the consequent decision parameters can be calculated using an incremental approach. At \(k + 1\) iteration, the decision parameter is calculated as
\[\begin{align} p_{k + 1} = 2\Delta y.x_{k+ 1} - 2\Delta x.y_{k + 1} + C. \tag{13} \end{align}\]Subtracting the equation \((12)\) from the equation \((13)\), we get
\[\begin{align} p_{k + 1} - p_k = 2\Delta y(x_{k + 1} - x_k) - 2\Delta x(y_{k + 1} - y_k). \tag{14} \end{align}\]But \(x_{k = 1} = x_k + 1\), so that
\[\begin{align} p_{k + 1} = p_k + 2\Delta y - 2\Delta x(y_{k + 1} - y_k). \tag{15} \end{align}\]The term \(y_{k + 1} - y_k\) is either \(0\) or \(1\), according to the sign of parameter \(p_k\). This iterative calculation of decision parameter is carried out at every integer \(x\) position, beginning from \(x_0\). So the only other thing we need is to find the initial parameter \(p_0\). The equation \((12)\) at the pixel \((x_0, y_0)\) is
\[\begin{align} p_0 &= 2\Delta y.x_0 - 2\Delta x.y_0 + C\\ &= 2\Delta y.x_0 - 2\Delta x.y_0 + 2\Delta y + 2\Delta x.b - \Delta x. \tag{16} \end{align}\]From \((3)\) and \((4)\) we have \(b = y_0 - mx_0 = y_0 - \dfrac{\Delta y}{\Delta x}x_0\), let’s plug it in \((16)\). The formula for initial parameter becomes
\[\begin{align} p_0 &= 2\Delta y.x_0 - 2\Delta x.y_0 + 2\Delta y + 2\Delta x(y_0 - \dfrac{\Delta y}{\Delta x}x_0) - \Delta x\\ &= 2\Delta y.x_0 - 2\Delta x.y_0 + 2\Delta y + 2\Delta x.y_0 - 2\Delta y.x_0 - \Delta x\\ &= 2\Delta y - \Delta x. \tag{17} \end{align}\]Similarly, adopt the Bresenham’s approach for a line having a negative slope of greater than \(-1\), we have:
\[\begin{align} p_0 &= 2\Delta y + \Delta x, \tag{18}\\ p_{k + 1} &= p_k + 2\Delta y - 2\Delta x(y_{k + 1} - y_k), \tag{19}\\ y_{k + 1} - y_k &= \left\lbrace \begin{array}{lll} -1 & if & p_k < 0\\ 0 & if & p_k \geq 0\\ \end{array} \right.. \tag{20} \end{align}\]The following Python function is to draw a line using Bresenham algorithm:
def draw_line_bresenham(x_0, y_0, x_1, y_1):
vertices = []
if abs(x_1 - x_0) > abs(y_1 - y_0):
if x_0 > x_1:
x_0, x_1 = x_1, x_0
y_0, y_1 = y_1, y_0
d_x = x_1 - x_0
d_y = y_1 - y_0
if d_y > 0:
p = 2 * d_y - d_x
y = y_0
for x in range(x_0, x_1 + 1):
vertices += [x, y]
if p > 0:
y = y + 1
p = p - 2 * d_x
p = p + 2 * d_y
else:
p = 2 * d_y + d_x
y = y_0
for x in range(x_0, x_1 + 1):
vertices += [x, y]
if p < 0:
y = y - 1
p = p + 2 * d_x
p = p + 2 * d_y
else:
if y_0 > y_1:
x_0, x_1 = x_1, x_0
y_0, y_1 = y_1, y_0
d_x = x_1 - x_0
d_y = y_1 - y_0
if d_x > 0:
p = 2 * d_x - d_y
x = x_0
for y in range(y_0, y_1 + 1):
vertices += [x, y]
if p > 0:
x = x + 1
p = p - 2 * d_y
p = p + 2 * d_x
else:
p = 2 * d_x + d_y
x = x_0
for y in range(y_0, y_1 + 1):
vertices += [x, y]
if p < 0:
x = x - 1
p = p + 2 * d_y
p = p + 2 * d_x
return vertices
You can find the complete version of the code here.