130 lines
6.9 KiB
Markdown
130 lines
6.9 KiB
Markdown
Chapter 35\
|
||
Bresenham Is Fast, and Fast Is Good {#Heading1}
|
||
------------------------------------
|
||
|
||
### Implementing and Optimizing Bresenham's Line-Drawing Algorithm {#Heading2}
|
||
|
||
For all the complexity of graphics design and programming, surprisingly
|
||
few primitive functions lie at the heart of most graphics software.
|
||
Heavily used primitives include routines that draw dots, circles, area
|
||
fills, bit block logical transfers, and, of course, lines. For many
|
||
years, computer graphics were created primarily with specialized
|
||
line-drawing hardware, so lines are in a way the *lingua franca* of
|
||
computer graphics. Lines are used in a wide variety of microcomputer
|
||
graphics applications today, notably CAD/CAM and computer-aided
|
||
engineering.
|
||
|
||
Probably the best-known formula for drawing lines on a computer display
|
||
is called Bresenham's line-drawing algorithm. (We have to be specific
|
||
here because there is also a less-well-known Bresenham's circle-drawing
|
||
algorithm.) In this chapter, I'll present two implementations for the
|
||
EGA and VGA of Bresenham's line-drawing algorithm, which provides decent
|
||
line quality and excellent drawing speed.
|
||
|
||
The first implementation is in rather plain C, with the second in
|
||
not-so-plain assembly, and they're both pretty good code. The assembly
|
||
implementation is damned good code, in fact, but if you want to know
|
||
whether it's the fastest Bresenham's implementation possible, I must
|
||
tell you that it isn't. First of all, the code could be sped up a bit by
|
||
shuffling and combining the various error-term manipulations, but that
|
||
results in *truly* cryptic code. I wanted you to be able to relate the
|
||
original algorithm to the final code, so I skipped those optimizations.
|
||
Also, write mode 3, which is unique to the VGA, could be used for
|
||
considerably faster drawing. I've described write mode 3 in earlier
|
||
chapters, and I strongly recommend its use in VGA-only line drawing.
|
||
|
||
Second, horizontal, vertical, and diagonal lines could be special-cased,
|
||
since those particular lines require little calculation and can be drawn
|
||
very rapidly. (This is especially true of horizontal lines, which can be
|
||
drawn 8 pixels at a time.)
|
||
|
||
Third, run-length slice line drawing could be used to significantly
|
||
reduce the number of calculations required per pixel, as I'll
|
||
demonstrate in the next two chapters.
|
||
|
||
Finally, unrolled loops and/or duplicated code could be used to
|
||
eliminate most of the branches in the final assembly implementation, and
|
||
because x86 processors are notoriously slow at branching, that would
|
||
make quite a difference in overall performance. If you're interested in
|
||
unrolled loops and similar assembly techniques, I refer you to the first
|
||
part of this book.
|
||
|
||
That brings us neatly to my final point: Even if I didn't know that
|
||
there were further optimizations to be made to my line-drawing
|
||
implementation, I'd *assume* that there were. As I'm sure the
|
||
experienced assembly programmers among you know, there are dozens of
|
||
ways to tackle any problem in assembly, and someone else always seems to
|
||
have come up with a trick that never occurred to you. I've incorporated
|
||
a suggestion made by Jim Mackraz in the code in this chapter, and I'd be
|
||
most interested in hearing of any other tricks or tips you may have.
|
||
|
||
Notwithstanding, the line-drawing implementation in Listing 35.3 is
|
||
plenty fast enough for most purposes, so let's get the discussion
|
||
underway.
|
||
|
||
### The Task at Hand {#Heading3}
|
||
|
||
There are two important characteristics of any line-drawing function.
|
||
First, it must draw a reasonable approximation of a line. A computer
|
||
screen has limited resolution, and so a line-drawing function must
|
||
actually approximate a straight line by drawing a series of pixels in
|
||
what amounts to a jagged pattern that generally proceeds in the desired
|
||
direction. That pattern of pixels must reliably suggest to the human eye
|
||
the true line it represents. Second, to be usable, a line-drawing
|
||
function must be *fast*. Minicomputers and mainframes generally have
|
||
hardware that performs line drawing, but most microcomputers offer no
|
||
such assistance. True, nowadays graphics accelerators such as the S3 and
|
||
ATI chips have line drawing hardware, but some other accelerators don't;
|
||
when drawing lines on the latter sort of chip, when drawing on the CGA,
|
||
EGA, and VGA, and when drawing sorts of lines not supported by
|
||
line-drawing hardware as well, the PC's CPU must draw lines on its own,
|
||
and, as many users of graphics-oriented software know, that can be a
|
||
slow process indeed.
|
||
|
||
Line drawing quality and speed derive from two factors: The algorithm
|
||
used to draw the line and the implementation of that algorithm. The
|
||
first implementation (written in Borland C++) that I'll be presenting in
|
||
this chapter illustrates the workings of the algorithm and draws lines
|
||
at a good rate. The second implementation, written in assembly language
|
||
and callable directly from Borland C++, draws lines at extremely high
|
||
speed, on the order of three to six times faster than the C version.
|
||
Between them, the two implementations illuminate Bresenham's
|
||
line-drawing algorithm and provide high-performance line-drawing
|
||
capability.
|
||
|
||
The difficulty in drawing a line lies in generating a set of pixels
|
||
that, taken together, are a reasonable facsimile of a true line. Only
|
||
horizontal, vertical, and 1:1 diagonal lines can be drawn precisely
|
||
along the true line being represented; all other lines must be
|
||
approximated from the array of pixels that a given video mode supports,
|
||
as shown in Figure 35.1.
|
||
|
||
Considerable thought has gone into the design of line-drawing
|
||
algorithms, and a number of techniques for drawing high-quality lines
|
||
have been developed. Unfortunately, most of these techniques were
|
||
developed for powerful, expensive graphics workstations and require very
|
||
high resolution, a large color palette, and/or floating-point hardware.
|
||
These techniques tend to perform poorly and produce less visually
|
||
impressive results on all but the best-endowed PCs.
|
||
|
||
Bresenham's line-drawing algorithm, on the other hand, is uniquely
|
||
suited to microcomputer implementation in that it requires no
|
||
floating-point operations, no divides, and no multiplies inside the
|
||
line-drawing loop. Moreover, it can be implemented with surprisingly
|
||
little code.
|
||
|
||
### Bresenham's Line-Drawing Algorithm {#Heading4}
|
||
|
||
The key to grasping Bresenham's algorithm is to understand that when
|
||
drawing an approximation of a line on a finite-resolution display, each
|
||
pixel drawn will lie either exactly on the true line or to one side or
|
||
the other of the true line. The amount by which the pixel actually drawn
|
||
deviates from the true line is the *error* of the line drawing at that
|
||
point. As the drawing of the line progresses from one pixel to the next,
|
||
the error can be used to tell when, given the resolution of the display,
|
||
a more accurate approximation of the line can be drawn by placing a
|
||
given pixel one unit of screen resolution away from its predecessor in
|
||
either the horizontal or the vertical direction, or both.
|
||
|
||
\
|
||
**Figure 35.1** *Approximating a true line from a pixel array.*
|