abrash-black-book/35-01.md
2013-12-30 20:26:41 +11:00

130 lines
6.9 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.
![](images/35-01.jpg)\
**Figure 35.1**  *Approximating a true line from a pixel array.*