Rasterization

by William Shoaff with lots of help


Contents

Audio Clips

1.
Bresenham's Algorithms
2.
Scan Line Filling Polygons

Rasterization

Rasterization is the process of converting a vertex representation to a pixel representation; rasterization is also called scan conversion. Included in this definition are geometric objects such as circles where you are given a center and radius. In these notes I will cover:

Scan conversion algorithms use incremental methods that exploit coherence. An incremental method computes a new value quickly from an old value, rather than computing the new value from scratch, which can often be slow. Coherence in space or time is the term used to denote that nearby objects (e.g., pixels) have qualities similar to the current object.

The DDA Algorithm

Let $p_0=(x_0,\,y_0)$ and $p_1=(x_1,\,y_1)$ be two endpoints of a line segment. We will assume that these points are in device space so that the coordinates $x_0,\,y_0,\,x_1,\,y_1$ are integers. The point-intercept form of the equation of the line from p0 to p1 is

y=mx+b

where the slope is

\begin{displaymath}m=\frac{y_1-y_0}{x_1-x_0}\end{displaymath}

and the y intercept is

b = y1 - mx1,

although it is not necessary to compute the y intercept.

Notice that if x is incremented to x+1, then y changes to y+m. Similarly, if y is incremented to y+1, then x changes to $x+\frac{1}{m}$. Of course, the slope (or its reciprocal) often won't be integers. Let's consider the two cases where these increments occur.

Lines with shallow slope

Pretend the slope is between minus one and one ( $-1\leq m \leq 1$) With little lost of generality, pretend x0 <x1 so the line is drawn from left to right. We know that if $(x,\,y)$ is on the line, then so is (x+1, y+m), and we simply iterate on this fact. The pseudo-code for the algorithm is given below.

The DDA code is presented in ../dda.html

Steep lines

In the second case the slope is less than minus one or greater than one (m < -1 or 1 < m). In this case we interchange roles of x and y. That is, starting from the lowest y value, increment y by 1 and increment x by 1/m.

Example 1:

Given end-points $p_0=(1,\,3)$ and $p_1=(8,\,9)$, find the pixels illuminated by the DDA algorithm.

In this case, the slope is m=6/7, which is a shallow line. Thus, x is incremented by 1 and y by m at each step. The first point is $(1,\,3)$. The next point on the line is $(2,\,27/7)$, which is rounded to the pixel value $(2,\,4)$. Continuing, the plotted pixels are:

Step y pixel
1 21/7 $(1,\,3)$
2 27/7 $(2,\,4)$
3 33/7 $(3,\,5)$
4 39/7 $(4,\,6)$
5 45/7 $(5,\,6)$
6 51/7 $(6,\,7)$
7 57/7 $(7,\,8)$
8 63/7 $(8,\,9)$
What pixels illuminated by the DDA algorithm given end-points $p=(1,\,3)$ and $q=(8,\,11)$?

Bresenham's Algorithm

The DDA algorithm requires floating point arithmetic and rounding inside the main loop. Bresenham's algorithm works solely with integers and integer arithmetic.

Let $p_0=(x_0,\,y_0)$ and $p_1=(x_1,\,y_1)$ be two endpoints of a line segment (in device coordinate, so $x_0,\,y_0,\,x_1,\,y_1$ are integers). The implicit equation of the line from p0 to p1 is

Ax+By+C=0

where

\begin{displaymath}A=(y_1-y_0) = \triangle y,\end{displaymath}


\begin{displaymath}B=-(x_1-x_0) = -\triangle x,\end{displaymath}

and

C= -(Ax0 + By0).

We'll pretend that

Since slope is between 0 and 1, and since x0 < x1, the only two valid moves are

\begin{displaymath}\mbox{Right (R):}\: x \rightarrow x+1\end{displaymath}

or

\begin{displaymath}\mbox{Diagonally up (D):}\: x \rightarrow x+1,\: y \rightarrow y+1.\end{displaymath}

Now define an error e = Ax+By+C which measures whether or not a point $(x,\,y)$ is on the line or not. Consider an R move, the error becomes

eR = A(x+1) + By + C = e + A.

Consider a D move, the error becomes

eD = A(x+1) + B(y+1) + C = e + A + B.

We want to move in whichever direction produces a smaller error.

If $\mid e_R\mid\,<\, \mid e_D\mid$ move to right; if $\mid e_D\mid\,\leq\, \mid e_R\mid$ move to diagonally up. The sign of the difference

\begin{displaymath}\mid e_R\mid - \mid e_D\mid\end{displaymath}

can be used to judge which of these errors is smaller. Notice that the errors $e_R,\,e_D$ can be either non-negative (+) or negative (-). There are four possible combinations:
eR eD $\mid e_R \mid - \mid e_D \mid $ next pixel
+ + -B > 0 D
+ - er + eD = 2e+2A+B don't know
- + can't happen  
- - B < 0 R

Notice that in the $(+,\,+)$ case we have

\begin{displaymath}\mid e_R \mid - \mid e_D \mid = e_r - e_D = (e+A) - (e+A+B) = -B,\end{displaymath}

and the $(-,\, -)$ case is similar. The $(-,\, +)$ can not happen since B is less than zero. That is, if e+A < 0 then eD = e+A+B <0 also.

Bresenham uses the indeterminate $(+,\,-)$ case, where we don't know which direction to move, to define a biased error

g = eR + eD = 2e + 2A + B,

which can be use in all cases to determine the move that produces the smallest error.

If eR and eD are both non-negative, then so is g and the above chart shows that a diagonal move is best. If eR and eD are both negative, so is g and so we move to the right as the chart indicates. In the indeterminate case, if g is non-negative we move diagonally, while if g is negative, we move to the right.

Now once a move is made we must update the biased error. For a diagonal move:

gD = g + 2A + 2B.

For a right move:

gR = g + 2A.

Finally, we need an initial value for the biased error. But this is easy since g = 2e+2A +B and the first point in on the line so the error e is zero: The initial value of the biased error is

g=2A+B.

Code for Bresenham's Algorithm

We'll only develop the code for Bresenham's algorithm for the special case discuss above. Let $p_0=(x_0,\,y_0)$ and $p_1=(x_1,\,y_1)$ be line segment end-points, pretend the slope of line is between 0 and 1 and let x0 < x1.

The Bresenham line code is presented in ../bresenham.html

Example 2:

Given end-points $p_0=(1,\,3)$ and $p_1=(8,\,9)$, find the pixels illuminated by Bresenham's algorithm.

Here we have the change in y is A = 9 - 3 = 6 and minus the change in x is B = -(8 - 1) = -7. The initial biased error is g = 2A+B = 5. The biased error update for a diagonal move is diagonalInc = 2(A+B) = -2, and the biased error update for a right move is rightInc = 2A=12.

Step old g pixel new g
1 5 $(1,\,3)$ 3
2 3 $(2,\,4)$ 1
3 1 $(3,\,5)$ -1
4 -1 $(4,\,6)$ 11
5 11 $(5,\,6)$ 9
6 9 $(6,\,7)$ 7
7 7 $(7,\,8)$ 5
8 5 $(8,\,9)$ 3

Lines of arbitrary slope

The remaining question is how do we scan convert a line when the slope is not between 0 and 1 or the first x coordinate is not less than the second.

Bresenham's Circle Algorithm

Now let's consider how we can use the ideas developed above to rasterize a circle.

Let

x2 + y2 -r2 = 0

be the equation of a circle centered at the origin with integer radius r. Starting at the top of the circle $(0,\,r)$, we want to determine which pixels to illuminate in the clockwise direction until we hit the line x=y. The remainder of the circle can be determined by symmetry, in particular if $(x,\,y)$ is on the circle, then so are

\begin{displaymath}(x,\,-y),\,(-x,\,y),\,(-x,\,-y),\,(y,\,x),\,(y,\,-x),\,(-y,\,x),\quad\mbox{and}\quad
(-y,\,-x).\end{displaymath}

At each step we choose to move either to the right (R): x=x+1, or diagonally down (D): $x=x+1,\,y=y-1$. Define the error to be

e = x2 + y2 - r2.

If we move right, then the error will become

eR = (x+1)2 + y2 - r2 = e + (2x+1).

If we move diagonally down, then the error will become

eD = (x+1)2 + (y-1)2 - r2 = e + (2x+1) - (2y-1).

The errors $e_R,\,e_D$ can be either non-negative (+) or negative (-). There are four possibilities:
eR eD $\mid e_R \mid - \mid e_D \mid $ next pixel
+ + 2y-1 > 0 D
+ - er + eD don't know
- + can't happen  
- - -(2y-1) < 0 R
The ``biased'' error g = eR+eD = 2e + (4x+2) - (2y-1) = 2e+4x-2y+3 is used in all cases. If eR and eD are both non-negative, then so is g and so we move diagonally down. If eR and eD are both negative, then so is g and so we move to the right. In indeterminate case, if g is non-negative, move diagonally down; if g is negative, move to the right.

We also need the initial value for g: It is g = 3 - 2r, since the initial point $(0,\,r)$ is on the circle.. And we need updates for the biased error for diagonal and right moves. Note that a diagonal and right moves results in changes to the biased error:

\begin{displaymath}g_D = g + 4x - 4y + 10,\quad g_R = g + 4x + 6.\end{displaymath}

Code for Bresenham's circle algorithm

The Bresenham circle code is presented in ../brescircle.html Note that symmetricplot(x, y) calls plot with all 8 values $(\pm x,\,\pm y),\, (\pm y,\,\pm x)$.

Polygon Filling Algorithms

We want to develop scan conversion algorithms that generate all pixels in the interior of a polygon. There are two general classes of polygon filling algorithms:

Flood fill and boundary fill algorithms are examples of the first class. Flood fill algorithm color all pixels (recursively) adjacent to the seed that are not equal to a given boundary color. Boundary fill algorithm color all pixels interior to a region's boundary. Fill algorithms are common in ``paint'' programs.

Scan line algorithms typically work only with polygons but fill algorithms work for more general objects Scan line algorithms can be used for hidden surface removal.

A Scan Line Fill Algorithm

Following convention, a raster will be defined as a two dimensional grid numbers from $(0,\,0)$ in the upper left corner to $(x_{\max}-1,\,y_{\max}-1)$ in the lower left corner. To be specific, let's use the HDTV resolution of $1080\times 1920$which has a 9 to 16 aspect ratio. Here's a small $10\times 10$ raster.


\begin{picture}(300,200)
\put(0,195){0}
\put(0,175){1}
\put(0,155){2}
\put(0,135...
...){11}{\line(1,0){200}}
\multiput(10,10)(20,0){11}{\line(0,1){200}}
\end{picture}

We want to fill a polygon defined by 3 or more vertices. We will make some assumptions (which are not quite right, but they get us started). First, we define scan-line order:

\begin{displaymath}(x_1,\,y_1) \leq (x_2,\,y_2)\:\mbox{in scan-line order if}\end{displaymath}


y1 < y2

or

\begin{displaymath}y_1 = y_2 \quad\mbox{and} \quad x_1 \leq x_2\end{displaymath}

1.
An infinite line intersects a closed bounded region R an even number of times (this is a little white lie)
2.
We can fill a polygon one scan line at a time, by filling between successive intersections of a scan line with polygon edges.
The basic algorithm is:
Given a polygon P, with edges $e_1,\,e_2,\,\ldots,\,e_n$
1.
Use DDA or Bresenham's algorithm to find each pixel on P's boundary
2.
Sort the pixels in scan-line order, storing them in a list L
3.
Pull off pairs of points $(x_1,\,y_1),\,(x_2,\,y_2)$ from L and color each pixel $(x,\,y)$ such that

\begin{displaymath}x_1 \leq x \leq x_2\end{displaymath}

(Note it must be the case that y1=y=y2)

You've been lied to, scan-lines that hit polygon vertices intersect the region an odd number of times and horizontal edges have infinitely many intersections with a scan-line Corrections are:

For a high resolution device there may be thousands of intersections, so even an $n\log n$ sort routine may take time Bucket sorting (a form of hashing) can save time

However, not all pixels need to be save, we can compute them on the fly. There are three quantities that need to be stored in a record place into a scan line list of buckets indexed by the y values of the scan lines (0 to $y_{\max}-1=1919$). The record looks like


\begin{displaymath}\begin{array}{\vert c\vert c\vert c\vert}\hline
\delta y & \delta x & x \\ \hline\end{array}\end{displaymath}

For each non-horizontal edge, let

\begin{displaymath}(x_1,\,y_1) \leq (x_2,\,y_2)\end{displaymath}

denote end points. So that y2-y1+1 scan-lines are intersected by the edge, and let

\begin{displaymath}\delta y = y_2 - y_1 + 1\end{displaymath}

be denote number of scan-lines that are active for this edge.

Starting at the top pixel of the edge $(x_1,\,y_1)$, when y is incremented by 1, x will change by

\begin{displaymath}\delta x = 1/m\end{displaymath}

where m is the slope of the edge.

For each non-horizontal edge $(x_1,\,y_1)$ to $(x_2,\,y_2)$,

Now, the algorithm to fill the polygon is:

1.
Starting from top scan-line, pull off all pairs of stored data from the highest (lowest value) scan line and place them in an active edge list.
2.
Plot all points on the scan-line between the x values.
3.
Update: $\delta y \rightarrow \delta y-1,\, x \rightarrow x + \delta x$.
4.
If $\delta y$ becomes zero, drop corresponding triple from active-edge list
5.
Increment scan line by 1, merge any data from the bucket list into active-edge list, sorted on x values
6.
Repeat steps 2 through 4 until all active edges have been processed.

Active-Edge List Example

Consider the polygon defined by vertices

\begin{displaymath}P_0=(0,0),\, P_1=(8,1),\, P_2=(9,5),\, P_3=(5,7)\end{displaymath}



\begin{picture}(300,200)
\put(19,199){\circle*{20}}
\put(179,179){\circle*{20}}
...
...){11}{\line(1,0){200}}
\multiput(10,10)(20,0){11}{\line(0,1){200}}
\end{picture}

The scan line bucket-list then looks like:

bucket $\delta y,\,\delta x,\,x$ $\delta y,\,\delta x,\,x$
0 8, 5/7, 0 2, 8, 0
1    
2 4, 1/4, 8.25  
3    
4    
5    
6 2, -2, 7  
7    
8    
9    
(Note the edge information for edge 4 was placed before the edge information for edge 1: why?

Now we pull off from the top the first non empty list from the scan line buckets.

Active-Edge List:

y=0 8, 5/7, 0 2, 8, 0

From this active edge list we plot: $(0,\,0)$.

One thing to note about this implementation is its dependence on floating point arithmetic and rounding. It is possible to separate the denominator and numerator information in $\delta x$ and implement an integer only algorithm that is similar to Bresenham's line algorithm.

A Scan line fill program

A Java applet that executes a version of scan line fill is available for you to test. http://www.cs.fit.edu/wds/java/html/RasterDriver.html

Including Shading/Rendering Information

We can extend the data in the record store for each vertex to implement Gouraud (or Phong) shading as the polygon is filled. For Gouraud shading, this would be done by keeping the red, green, blue (RGB) intensity at each vertex and the change in these values as the edge is traced out. For simplicity, we'll illustrate this with gray scale using only an intensity I at each vertex.

Pretend we are given polygon vertices, as before, with intensity data:

P_0=(0,0), I_0 = 2, P_1=(8,1), I_1 = 10, P_2=(9,5), I_2 = 5, P_3=(5,7), I_3 = 8

Consider edge 1 from P0 to P1. We need to know that the intensity changes by

\begin{displaymath}\delta I = \frac{I_1-I_0}{y_2-y_1} = \frac{8}{1}=8\end{displaymath}

as we step from one scan line to the next.

The scan line bucket-list then looks like:

bucket $\delta y,\,\delta x,\, x,\,\delta I,\, I$ $\delta y,\,\delta x,\, x,\,\delta I,\, I$
0 8, 5/7, 0, 6/7, 2 2, 8, 0, 8, 2
1    
2 4, 1/4, 8.25,-5/4,8.75  
3    
4    
5    
6 2, -2, 7,3/2,6.5  
7    
8    
9    

Now we pull off from the top the first non empty list from the scan line buckets.

Active-Edge List:

y=0 8, 5/7, 0 , 6/7, 2 2, 8, 0,8,2

From this active edge list we plot: $(0,\,0)$ with intensity 2.

Update Active-Edge List:

y=1 7, 5/7, 5/7,6/7,20/7 1, 8, 8,8,10
and plot the pixels $(1,\,1)$ to (8,1), but with what intensities?

You must have observed that we now want to interpolate (via constant increments) from intensity I=20/7 to intensity I=10 as we step across the scan line. So let's define a double difference (that corresponds to what's called bilinear interpolation)

\begin{displaymath}\delta \delta I = \frac{\delta I_1 - \delta I_0}{x_1-x_0} = \frac{10 -20/7}{8-1} = 50/49\end{displaymath}

Plot the 8 pixels $(1,\,1)$ to $(8,\,1)$ with intensities:

20/7, 190/49, 240/49, 290/49, 340/49, 390/49, 440/49, 490/49=10

When the active edge list is updated, one record (for edge 1) drops out and an new record (for edge 2) is merged in:

y=2 6, 5/7, 10/7,6/7,26/7 4, 1/4, 8.25,-5/4,8.75
Now the ``double difference''

\begin{displaymath}\delta \delta I = \frac{35/4 - 26/7}{8-1}\end{displaymath}

And this value is added to the intensity starting at 26/7 until the right edge of the polygon is met at pixel (8,2).

Note in practice the intensity values would be truncated or rounded to integers.

A Boundary Fill Algorithm

Now to change directions, we present fill algorithms that you might find in a ``paint'' program.

Assume a seed point inside the region to be filled and empty stack

Pseudo-code for Flood Fill Algorithm

Assume a seed point inside the region to be filled and an algorithm that uses a 4-point topology

Pseudo-code for Flood Fill Algorithm

Filling Using Coherence

No References!



William D. Shoaff
2002-04-04