Basic Raster Algorithms
Basic Raster Algorithms
81
82 Computer Graphics : From Pixels to Programmable Graphics Hardware
Note that if two pixels are 4-connected then they are 8-connected, but the
opposite is not true – pixels (0, 0) and (1, 1) are 8-connected, but they are not
4-connected.
A set of pixels on a raster grid is 4- or 8-connected if for any two pixels from
this set there exists a sequence of pixels starting at the first pixel and ending
at the second such that any two subsequent pixels are 4- or 8-connected.
When we rasterize lines, arcs and other linear shapes (which are connected
in 2D-/3D-space) we must require that pixels resulting from this rasteriza-
tion also form a connected set according to the chosen connectedness criteria.
Thus, we come to raster algorithms which generate 4-connected pixels and
8-connected pixels. Note that the same shape can be rasterized differently
depending on the type of connectivity used.
Basic raster algorithms 83
Later we will show how a generic case can be built from this case.
We will consider that the segment is a part of the line with an equation
y = kx + b where the coefficient k is in the range from 0 to 1. Then for every
integer value xi starting with xa and up to xb there will be exactly one pixel
(xi , yi ) with that x-coordinate.
Since we need some way to access individual pixels for the rest of the
chapter we will assume that there are two functions – putP ixel(x, y, c) and
getP ixel(x, y). First one draws a pixel at position (x, y) with color c and the
second one reads the color of the pixel at the given position (x, y).
Using these functions we can write the simplest (very inefficient) code to
draw segment AB.
void drawLine ( i nt xa , i nt ya , i nt xb , i nt yb ,
i nt c o l o r )
{
f l o a t k = ( f l o a t ) ( yb−ya ) / ( f l o a t ) ( xb − xa ) ;
f l o a t b = ya − k ∗ xa ;
84 Computer Graphics : From Pixels to Programmable Graphics Hardware
xi = xa + i,
(5.1)
ci = {kxi + b}.
If for i-th pixel ci ≤ 1/2 we will round kxi + b down and set yi = kxi + b,
otherwise we set yi = kxi + b + 1.
FIGURE 5.4: Line segment and fractional parts ci for three consecutive
pixels.
Basic raster algorithms 85
p u t P i x e l ( xa , ya , c o l o r ) ;
for ( i nt x = xa +1; x <= xb ; x++ )
{
i f ( ( c += k ) > 0 . 5 f )
{
c−−;
y++;
}
putPixel ( x , y , color ) ;
}
}
Note that comparing ci with zero is much easier than with 1/2. So we can
use di = 2ci − 1 instead of ci and compare it with zero. Since c0 = 0 and
c1 = k we have d0 = −1 and d1 = 2k − 1.
void drawLine ( i nt xa , i nt ya , i nt xb , i nt yb ,
i nt c o l o r )
{
f l o a t k = ( f l o a t ) ( yb−ya ) / ( f l o a t ) ( xb − xa ) ;
f l o a t y = ya ;
f l o a t d = 2∗k − 1 ;
i nt y = ya ;
p u t P i x e l ( xa , ya , c o l o r ) ;
for ( i nt x = xa +1; x <= xb ; x++ )
{
if ( d > 0 )
{
86 Computer Graphics : From Pixels to Programmable Graphics Hardware
d += 2∗ k + 2 ;
y++;
}
else
d += 2∗ k ;
putPixel ( x , y , color ) ;
}
}
But all these functions heavily use floating-point mathematics which can
be expensive. So the final step is to completely get rid of all floating-point
numbers. All input parameters are integer and for simple CPUs using integer-
only arithmetic is highly efficient. Since k = (yb − ya )/(xb − xa ) and all other
floating-point values are computed by adding multiples of k and integer values
n
then all di are in the form of fractions xb −x a
.
So if we multiply di by xb − xa we will get integer-only arithmetic (note
that deltas we are using should be multiplied by it too).
void drawLine ( i nt xa , i nt ya , i nt xb , i nt yb ,
i nt c o l o r )
{
i nt dx = xb − xa ;
i nt dy = yb − ya ;
i nt d = ( dy<<1) − dx ;
i nt d1 = dy << 1 ;
i nt d2 = ( dy − dx ) << 1 ;
i nt y = ya ;
p u t P i x e l ( xa , ya , c o l o r ) ;
for ( i nt x = xa +1; x <= xb ; x++ )
{
if ( d > 0 )
{
d += d2 ;
y++;
}
else
d += d1 ;
putPixel ( x , y , color ) ;
}
}
This code can be easily converted to get a 4-connected line rasterization.
void drawLine4 ( i nt xa , i nt ya , i nt xb , i nt yb ,
Basic raster algorithms 87
i nt c o l o r )
{
i nt dx = xb − xa ;
i nt dy = yb − ya ;
i nt d = ( dy<<1) − dx ;
i nt d1 = dy << 1 ;
i nt d2 = ( dy − dx ) << 1 ;
i nt x = xa ;
i nt y = ya ;
p u t P i x e l ( xa , ya , c o l o r ) ;
for ( i nt i = 1 ; i <= dy + dx ; i++ )
{
if ( d > 0 )
{
d += d2 ;
y++;
}
else
{
d += d1 ;
x++;
}
putPixel ( x , y , color ) ;
}
}
Now that we have a line rasterization algorithm for the case 0 ≤ yb − ya ≤
xb − xa we can adapt it the to the general case.
Note that the case 0 ≤ −(yb − ya ) ≤ xb − xa is symmetric to the already
considered case – we need to change y by −1 instead of 1 and the case 0 ≤
yb − ya > xb − xa is reduced to a previously considered case by swapping x
and y variables.
So we get the following C++ code for rendering any line segments (note
that it may be more efficient to detect horizontal and vertical lines and process
them separately).
void drawLine ( i nt xa , i nt ya , i nt xb , i nt yb ,
i nt c o l o r )
{
i nt dx = abs ( xb − xa ) ;
i nt dy = abs ( yb − ya ) ;
i nt sx = xb >= xa ? 1 : −1; // s i g n o f xb−xa
i nt sy = yb >= xy ? 1 : −1; // s i g n o f yb−ya
88 Computer Graphics : From Pixels to Programmable Graphics Hardware
i f ( dy <= dx )
{
i nt d = ( dy<<1) − dx ;
i nt d1 = dy << 1 ;
i nt d2 = ( dy − dx ) << 1 ;
i nt x = xa + sx ;
i nt y = ya ;
p u t P i x e l ( xa , ya , c o l o r ) ;
for ( i nt i = 1 ; i <= dx ; i ++, x += sx )
{
if ( d > 0 )
{
d += d2 ;
y += sy ;
}
else
d += d1 ;
putPixel ( x , y , color ) ;
}
}
else
{
i nt d = ( dx<<1) − dy ;
i nt d1 = dx << 1 ;
i nt d2 = ( dx − dy ) << 1 ;
i nt x = xa ;
i nt y = ya + sy ;
p u t P i x e l ( xa , ya , c o l o r ) ;
for ( i nt i = 1 ; i <= dy ; i ++, y += sy )
{
if ( d > 0 )
{
d += d2 ;
x += sx ;
}
else
d += d1 ;
putPixel ( x , y , color ) ;
}
}
}
Basic raster algorithms 89
The code contains only integer arithmetic and has no multiplications and
divisions. Because of it the code may be easily implemented even on very
simple processors.
putPixel ( xc + y, yc − x, color );
putPixel ( xc + x, yc − y, color );
putPixel ( xc − x, yc − y, color );
putPixel ( xc − y, yc − x, color );
putPixel ( xc − y, yc + x, color );
putPixel ( xc − x, yc + y, color );
}
To check points for being inside/outside the circle we introduce the test
function
F (x, y) = x2 + y 2 − R2 . (5.2)
The function is equal to zero on the circle, negative inside circle and posi-
tive outside it.
Now we can start building a sequence of pixels. The first pixel is obvious
– x0 = 0, y0 = R.
Next, the x-value of a pixel based on the previous one is obvious too –
xi+1 = xi + 1. For yi+1 we have only two possible choices (due to the slope
being in [−1, 0] range) – yi or yi −1. We check the midpoint between these two
values to see whether it is inside or outside the circle. To do so we introduce
the decision variable
If di < 0 the midpoint (xi + 1, yi − 1/2) is inside our circle, and we set
yi+1 = yi . Otherwise (di ≥ 0) we set yi+1 = yi − 1. Now we need to see how
our decision variable changes depending on our choice.
If di < 0 we have the following equation for di+1
d r a w C i r c l e P o i n t s ( xc , yc , x , y , c o l o r ) ;
while ( y > x )
{
if ( d < 0 )
{
d += 2∗ x + 3 ;
x++;
}
else
{
d += 2 ∗ ( x−y )+ 5 ;
x++;
y−−;
}
d r a w C i r c l e P o i n t s ( xc , yc , x , y , c o l o r ) ;
}
}
Note that we always change our decision variable by an integer value and
since its initial value has the fractional part of 1/4, it means that for every
pixel the fractional part of a decision variable will still be 1/4. Because of it we
can simply ignore the fractional part of di and di becomes an integer variable.
Thus, we get the following integer-only code.
void d r a w C i r c l e ( i nt xc , i nt yc , i nt r , i nt c o l o r )
92 Computer Graphics : From Pixels to Programmable Graphics Hardware
{
i nt x = 0;
i nt y = r;
i nt d = 1 − r;
i nt delta1 = 3;
i nt delta2 = −2∗ r +5;
d r a w C i r c l e P o i n t s ( xc , yc , x , y , c o l o r ) ;
while ( y > x )
{
if ( d < 0 )
{
d += d e l t a 1 ;
d e l t a 1 += 2 ;
d e l t a 2 += 2 ;
x++;
}
else
{
d += d e l t a 2 ;
d e l t a 1 += 2 ;
d e l t a 2 += 4 ;
x++;
y−−;
}
d r a w C i r c l e P o i n t s ( xc , yc , x , y , c o l o r ) ;
}
}
FIGURE 5.7: Rasterization of triangle edges can produce pixels which are
outside of the triangle.
To cope with this problem the top-left rule is often used. According to this
rule the pixel is produced if its center lies inside the triangle or it lies on the
top or left edge.
Here a top edge is the horizontal edge above all other edges of the triangle.
A left edge is a non-horizontal edge on the left side of the triangle (see Figure
5.9). Note that the triangle can have either one or two left edges.
In Figure 5.10 the rasterization of several triangles are shown.
Figure 5.11 shows rasterizations of two triangles and a rectangle, composed
of two such triangles. As you can see, there are no duplicate pixels or holes in
94 Computer Graphics : From Pixels to Programmable Graphics Hardware
the rectangle rasterization. Note that if you have a group of such rectangles
touching each other then there also will be no holes nor duplicate pixels.
Here for triangle rasterization we will use a simple but effective algorithm
often used by graphics hardware.
Basic raster algorithms 95
// c h e c k w h e t h e r p3 i s i n p o s i t i v e
i f ( a ∗p3 . x+b∗ p3 . y+c < 0 )
{
a = −a ;
b = −b ;
c = −c ;
96 Computer Graphics : From Pixels to Programmable Graphics Hardware
}
}
Then the rasterizing code will look as shown below.
void r a s t e r i z e T r i a n g l e ( p o i n t p [ ] )
{
i nt xMin = p [ 0 ] . x ;
i nt yMin = p [ 0 ] . y ;
i nt xMax = p [ 0 ] . x ;
i nt yMax = p [ 0 ] . y ;
i nt a [ 3 ] , b [ 3 ] , c [ 3 ] ;
if ( p [ i ] . y < yMin )
yMin = p[ i ]. y;
else
if ( p [ i ] . y > yMax )
yMax = p[ i ]. y;
}
// b u i l d l i n e equations
buildEquation (p [ 0 ] , p[1] , p[2] , a[0] , b[0] , c [2]);
buildEquation (p [ 0 ] , p[2] , p[1] , a[1] , b[1] , c [1]);
buildEquation (p [ 1 ] , p[2] , p[0] , a[2] , b[2] , c [2]);
// f i n d f u n c t i o n s a t lower − l e f t c o r n e r
i nt d0 = a [ 0 ] ∗ xMin+b [ 0 ] ∗ yMin+c [ 0 ] ;
i nt d1 = a [ 1 ] ∗ xMin+b [ 1 ] ∗ yMin+c [ 1 ] ;
i nt d2 = a [ 2 ] ∗ xMin+b [ 2 ] ∗ yMin+c [ 2 ] ;
// c h e c k p o i n t s
for ( i nt y = yMin ; y <= yMax ; y++ )
{
i nt f 0 = d0 ,
f 1 = d1 ,
f 2 = d2 ;
Basic raster algorithms 97
d0 += b [ 0 ] ;
d1 += b [ 1 ] ;
d2 += b [ 2 ] ;
f 0 += a [ 0 ] ;
f 1 += a [ 1 ] ;
f 2 += a [ 2 ] ;
}
}
}
Note that sometimes the requirement to include only pixels inside the
triangle can lead to undesirable artifacts. Figure 5.12 shows a triangle where
this requirement results in missing pixels and a raster representation of the
triangle is not connected.
The area may be complex and have holes in it, but all its pixels must be
reachable from the seed pixel (see Figure 5.13).
The simplest boundary fill algorithm can be written in just a few lines of
code.
void b o u n d a r y F i l l ( i nt x , i nt y , i nt b o r d e r C o l o r ,
i nt f i l l C o l o r )
{
i nt c = g e t P i x e l ( x , y ) ;
i f ( ( c != b o r d e r C o l o r ) && ( c != f i l l C o l o r ) )
{
putPixel ( x, y, fillColor );
boundaryFill ( x − 1 , y , borderColor , f i l l C o l o r );
boundaryFill ( x + 1 , y , borderColor , f i l l C o l o r );
boundaryFill ( x , y − 1 , borderColor , f i l l C o l o r );
boundaryFill ( x , y + 1 , borderColor , f i l l C o l o r );
}
}
Despite its simplicity this algorithm can handle a very complex area. How-
ever, it’s very inefficient and leads to very deep recursion, so it can be used
only for small areas.
To build an efficient boundary filling algorithm we should utilize coherence
– if some pixel (x, y) is inside the area to be filled then its neighboring pixels
are probably inside the area too.
We can fill the area by horizontal spans – for the current point (x, y) inside
the area we find a maximum span (xl , y) − (xr , y) , which is completely inside
the area and contains our point. After the found span is filled we move down
– all pixels on the next line are checked which can result in the forming of
several non-overlapping spans (see Figure 5.14).
Basic raster algorithms 99
FIGURE 5.15: For some spans we may find an unfilled area above it.
The algorithm below leads to the following code (note that the algorithm
is recursive but the level of recursion is not very deep).
i nt l i n e F i l l (
i nt x , i nt y , i nt d i r , i nt prevXl , i nt prevXr ,
i nt b o r d e r C o l o r , i nt f i l l C o l o r )
{
i nt x l = x ;
i nt xr = x ;
i nt c ;
do
c = g e t P i x e l ( −−xl , y ) ;
while ( ( c != b o r d e r C o l o r ) && ( c != f i l l C o l o r ) ) ;
100 Computer Graphics : From Pixels to Programmable Graphics Hardware
do
c = g e t P i x e l ( ++xl , y ) ;
while ( ( c != b o r d e r C o l o r ) && ( c != f i l l C o l o r ) ) ;
x l ++;
xr −−;
drawLine ( xl , y , xr , y ) ; // f i l l t h e span
return xr ;
}
Then the resulting boundary fill function will be very simple and is shown
below.
void b o u n d a r y F i l l ( i nt x , i nt y , i nt b o r d e r C o l o r ,
i nt f i l l C o l o r )
{
l i n e F i l l ( x , y , 1 , x , x , borderColor , f i l l C o l o r ) ;
}