Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Fencing The Dog


MAY89: GRAPHICS PROGRAMMING

If you were awake, you noticed that viewports disappeared from the GRAFIX library during March and April. Didn't matter much. We hadn't talked specifically about viewports, so I decided to send them on vacation while rethinking the whole subject. Now they're back, ready to go.

In a way, I'm sorry I brought them up at all in the first incarnation of the GRAFIX library. As so often happens with any programming project, I had plans for viewports that changed as the project progressed. Now we have to undo some old damage before we can begin with the new.

Fortunately, that's not a Big Deal. All you have to do is delete a few lines from GRAFIX.C. In general, all references to the VUPORT structure go. Here's what to do:

Luckily, that's all there is to it: an annoyance rather than a hardship.

There's one more thing you have to do with GRAFIX.C. At the end of the init_video( ) function, just before the return statement, add the following:

  if (mode = = VGA16 && vga)
         default_viewport (480);

Now save GRAFIX.C. Don't recompile it yet, though, because we have some other fish to fry first.

In a strategic sense, what's the point of all this? As the project has advanced, it's becoming rapidly apparent that we can't keep adding stuff to GRAFIX.C. The source file would soon swell to enormous size if we did. This would force us to search through zillions of functions to find the one we want and to wait interminably during each recompile. That's the whole oomph behind the concept of modular development: Keep related things together in their own files.

So what we've done here is to remove the VUPORT references from GRAFIX.C, where they don't belong, in preparation for constructing a new viewport support file, where they do. The reference to default_viewport( ) merely calls a viewport initialization function that lives among its relatives.

Now we can bring in the new. But first let's consider the question...

So What's a Viewport?

A viewport is to graphics what a window is to text: a subset of the display that thinks it's an entire self-contained screen. After you define a viewport, all operations are confined within its boundaries, and the coordinate system is relative to the upper left corner.

For example, say you define a viewport with its upper left corner at X=320, Y=175. If the device is an EGA, you have opened the viewport in the lower right quadrant. This implies that, while the viewport is active, everything above and to the left of the display area is inaccessible. All coordinate references thus become relative to the viewport's origin. This leads to the concept of coordinate remapping: A pixel written at {0, 0} is automatically remapped to absolute location {320,175}. Don't worry right now about how it's done. We're still at the conceptual level, with mechanics to follow later.

The second major concept of viewports is clipping. Say the viewport at {320,175} has a width of 100 pixels and a height of 50. The right side of the viewport is therefore at x=420 and the bottom is at y=225. These coordinates form the boundaries of the clipping region. You can't write pixels outside of them, just as you can't write pixels off-screen.

The idea is analogous to fencing the backyard. The dog can go anywhere it wants within the yard, but that's all because the fence defines the limits of its world. The dog can look through the fence and bark at the cat on the other side, thus conceiving of a world beyond, but it can't get there. That's clipping.

If a dog thought about such things, it might regard one corner of its world as point {0, 0}, with the capability to move 100 paces along one axis and 50 along the other. The dog might decide to start at some arbitrary point and walk 200 paces along a straight line. It could move for some distance, but eventually its travel would be arrested -- that is, clipped -- upon encountering the fence. The dog would thus have completed as much of the actual journey as possible, with the rest existing only in its imagination.

Similarly, we can draw a line from a point within a viewport to a point outside it, and the line stops when it hits the edge of the clipping region. By extension, we can also draw a line between two points, both of which are outside the clipping region, and we'll see only the portion that actually falls within the viewport it crosses.

How? The obvious place to control remapping and clipping is at the pixel-writing level. In DRAWPT.ASM (Listing One) the magic happens between lines 24 and 43. This passage of code points ES:BX at the current viewport structure, then compares the X and Y coordinates with the width and height, respectively. If either is outside the viewport, the code jumps to the exit, thus inhibiting a pixel write. When both coordinates are inside the viewport, lines 39-43 remap the viewport coordinates to absolute screen coordinates relative to the viewport's origin. HLINE.ASM (Listing Two) performs similar clipping and remapping in lines 63-82.

These routines could be made more robust. For example, both assume that the coordinates are in the positive domain, and so don't check for coordinates to the left of or above the viewport origin (either of which would be negative values). The intent here is to show how it works, so I've deliberately omitted the extensive validity checks that rightfully characterize industrial-grade software.

Note that I've added a short "hack" to each routine (lines 76 - 79 in DRAWPT.ASM and lines 104 - 107 in HLINE.ASM). These enhancements program the graphics chip to replace affected pixels. The 6845 can also perform bitwise operations (AND, OR, XOR) on pixels. By setting bits 3 and 4 of the 6845's Data Rotate/Function Select register to zeros, you tell the chip not to perform any of these special color effects, but instead to change the affected pixel to the new value without gamesmanship.

Make sure line 14 in each assembly-language routine reads as shown in the listings, then reassemble them. You can implement the enhanced versions in your copy of GRAFIX.LIB with the command

LIB grafix -+drawpt -+hline;

Now we're ready to take on viewports themselves.

Bring In The New!

The real work of coordinate remapping and clipping is done by the two pixel-writing routines. A viewport descriptor is itself a rather simple structure defining the origin coordinates, width, and height. However, programs shouldn't have to concern themselves with viewport management, instead dealing with strategic issues. Consequently the new VUPORT.C module defines eight functions for operating on viewports, relieving applications of the details and even hiding the descriptor structure itself.

The overall thrust of the VUPORT module is to treat viewports in a manner analogous to files. Like a file, a given viewport has a handle (type VP_HAN) assigned at its birth and remaining with it during its lifetime. The library module provides functions to open and close viewports, to switch among existing viewports, and to make inquiries.

Listing Three shows the additions to GRAFIX.H, the library header file, to define the new viewport functions. After appending these entries to the end of the header file, reccompile GRAFIX.C, then put it into the library with the command

  LIB grafix -+grafix;

VUPORT.C in Listing Four provides the source for the new routines. Compile it, then add it to the library with

  LIB grafix +vuport;

Now let's examine the VUPORT module to see what it does.

The VPNODE structure at the top of the file defines a viewport descriptor node to be used in a dynamic list. Several of the functions manage this list, which contains information about the viewports your program opens. In addition, the module maintains the default viewport definition def_vp, which describes the display as a whole, and the vuport variable, which always points to the descriptor for the currently active viewport.

The default_viewport( ) function is called by init_video( ) from GRAFIX.C to change the default viewport's height. The default structure is born with a height of 350 pixels (EGA). The call from init_video( ) automatically adjusts the height to 480 when the graphics mode is VGA 640 x 480. Though accessible to your programs, there is little reason for them to call the default_viewport( ) function.

The vp_open( ) routine creates a new viewport. The XY position of the upper left corner is expressed in absolute coordinates and is not relative to the currently active viewport. Upon completion of the routine, the newly opened viewport is the active one and subsequent drawing occurs within it. Like the familiar file open( ) function, vp_open( ) returns a handle identifying the viewport. Handles are integers working upward from 1; the 0 handle refers to the default full-screen viewport defined in def-vp.

vp-open( ) adds the new viewport to a doubly linked dynamic list, to which the vplist variable acts as a head pointer. The list contains descriptor nodes for all user-defined viewports that currently exist. A new viewport is appended at the tail and assigned a handle numerically one greater than that of the old tail. The routine makes the new viewport active by repointing the vuport variable to it, and then returns the handle.

You can switch around among existing viewports by calling vp-use( ). The argument is a handle identifying the desired viewport, or 0 for the full-screen default. This routine searches the viewport list for a node whose handle matches the non-zero argument, returning TRUE or FALSE to indicate the outcome. When successful, vp_use( ) repoints the vuport variable to the indicated viewport descriptor, thus activating it. Subsequent pixel operations occur relative to the selected viewport.

A viewport ceases to exist when you pass its nonzero handle to vp-close( ). The act of closing entails removing the descriptor node from the viewport list. The routine does this by searching for the handle, then rearranging the neighbors' pointers to bypass the closed node. This has the effect of pinching the node out of the list. The memory space can then be freed. vp_close( ) also checks the closed node against the global vplist and vuport pointers. If you've closed the head node, its successor is promoted to the head of the list, and if you've closed the active node, the default node becomes active.

Viewports are handy for confining drawing activity to a certain part of the screen, and for dynamically placing visual objects according to conditions encountered at runtime. Another use is analogous to text windows, in which a region of the screen is set aside for some purpose such as displaying a graph. In the latter case, programmers usually want to make the viewport visually distinctive by outlining it. That's the purpose of the vp_outline( ) function.

vp_outline( ) draws a rectangle in the current foreground color. The border is immediately outside the viewport itself, so that it doesn't impinge on the drawing area or get clobbered by activity inside the viewport. The viewport to be outlined doesn't have to be active at the time, but it must be open inasmuch as the function requires a valid handle. After calculating the border's location and dimensions, vp_outline( ) switches to full-screen mode and draws the outline, then reinstates the former active viewport.

The library routines hide the structure of the viewport descriptor from applications. Consequently some way must be available for programs to inquire about important characteristics of the current viewport: its handle and dimensions. The functions vp_active( ), vp_width( ), and vp_height( ) provide those services. Using them, subprograms with no knowledge of the current viewport can make intelligent decisions about, say, scaling output to fit within the available drawing area. You'll see examples Real Soon Now.

Before we move on, don't forget to compile GRAFIX.C and VUPORT.C and then put them into your GRAFIX.LIB.

Let's Put It To Work

A couple of application programs will prove the pudding we've cooked up here, and probably give you some other ideas about how to use viewports.

Listing Five is VP.C, a simple program that draws some six-pointed stars in viewports. The first star (magenta) is drawn in default full-screen mode. The second (blue near the bottom of the screen) is inside a viewport, but you can't tell that because there's no border. It appears to have been drawn with different coordinates than the first, even though the positions of both are the same in relation to their enclosing viewports. The third star near the center of the screen is within a bordered viewport. The area is too small to contain the star, so clipping occurs. The white star and blue border clearly reveal that there is no conflict between the outline and objects within the viewport, and also (lower right corner) that the portion of a line beginning and ending outside the viewport shows up within the drawing area. The final viewport is outlined in white, filled with green, and bears a red star. The call to fill_rect( ) illustrates one application of inquiry functions.

The second program is LINEGRAF.C (Listing Six). This program illustrates how a graphics subroutine can intelligently adjust its behavior according to the dimensions of the active viewport. It plots the same series of data points in three viewports of different sizes and shapes.

The smarts are in the graph( ) function. The first three expressions calculate the factors required to scale the line graph. The number of data points is the size of the data series array divided by the size of its type (assuming that the array is always full). If there are n points, then the number of intervals across the width of the plot area is n-1. Therefore the horizontal scaling factor is the width divided by the number of intervals, which must be a floating point value in order to avoid roundoff errors. This program knows that all values in the data series are positive integers less than 100, so it factors the vertical interval per unit on a scale of 100.

The graph itself is drawn by the loop, which pulls each line segment forward from the previous point to the current point. Accordingly, before entering the loop the program computes the location for element 0 as the previous point, so that it has a valid place to draw from during the first iteration. Now let's consider the sanity of the statement

  cury = vp_height( ) -
         (int)(data[p] * vint);

The second factor is fairly obvious: It finds the height of the point scaled by the vertical factor, and because coordinates are integers, the cast converts it appropriately. But why subtract the Y coordinate from the viewport height? Because display coordinate systems are upside down. The rest of the world thinks of Y as increasing upward, while on a display Y increases downward. By subtracting the computed value from the viewport height we "flip" the Y so that the graph comes out right side up.

So the old is out and the new is in, and that's how viewports work.

Availability

All source code for articles in this issue is available on a single disk. To order, send $14.95 (Calif. residents add sales tax) to Dr. Dobb's Journal, 501 Galveston Dr., Redwood City, CA 94063; or call 800-356-2002 (from inside Calif.) or 800-533-4372 (from outside Calif.). Please specify the issue number and format (MS-DOS, Macintosh, Kaypro).

_Graphics Programming_ by Kent Porter

[LISTING ONE]

<a name="00fa_0009">

   1| ; DRAWPT.ASM: Writes pixel directly to 6845 Video Controller
   2| ; Microsoft MASM 5.1
   3| ; C prototype is
   4| ;      void far draw_point (int x, int y);
   5| ; To be included in GRAFIX.LIB
   6| ; K. Porter, .MDUL/DDJ.MDNM/ Graphics Programming Column, February '89
   7|
   8| .MODEL  LARGE
   9| .CODE
  10|         PUBLIC  _draw_point
  11|
  12| ; Externals in GRAFIX.LIB
  13|         EXTRN   _color1 : BYTE          ; Pixel color reg value
  14|         EXTRN   _vuport : WORD          ; far ptr to vuport structure
  15|
  16| ; Arguments passed from C
  17| x       EQU     [bp+6]                  ; Arguments passed from C
  18| y       EQU     [bp+8]
  19|
  20| _draw_point     PROC FAR
  21|         push    bp                      ; Entry processing
  22|         mov     bp, sp
  23|
  24| ; Point ES:[BX] to vuport structure
  25|         mov     ax, _vuport+2           ; get pointer segment
  26|         mov     es, ax
  27|         mov     bx, _vuport             ; get offset
  28|
  29| ; Clip if coordinates outside viewport
  30|         mov     cx, y                   ; get y
  31|         cmp     cx, es:[bx+6]           ; is y within viewport?
  32|         jl      checkx                  ; ok if so
  33|         jmp     exit                    ; else quit
  34| checkx: mov     ax, x                   ; get x
  35|         cmp     ax, es:[bx+4]           ; is x within viewport?
  36|         jl      remap                   ; ok if so
  37|         jmp     exit                    ; else quit
  38|
  39| ; Map pixel coordinates to current viewport
  40| remap:  add     ax, es:[bx]             ; offset x by vuport.left
  41|         mov     x, ax                   ; save remapped X
  42|         add     cx, es:[bx+2]           ; offset y by vuport.top
  43|         mov     y, cx                   ; save remapped Y
  44|
  45| ; Point ES to video memory segment
  46| vmem:   mov     ax, 0A000h
  47|         mov     es, ax
  48|
  49| ; Row offset = y * 80;
  50|         mov     bx, y                   ; Get y argument
  51|         mov     ax, 80
  52|         mul     bx                      ; Result in AX
  53|         mov     bx, ax                  ; Row offset in BX
  54|
  55| ; Column offset = x SHR 3
  56|         mov     ax, x                   ; Get x
  57|         mov     cl, 3                   ; Shift operand
  58|         shr     ax, cl                  ; Column offset
  59|
  60| ; Complete address of pixel byte
  61|         add     bx, ax                  ; ES:BX = address
  62|
  63| ; Build bit mask for pixel
  64|         mov     cx, x                   ; Get x again
  65|         and     cx, 7                   ; Isolate low-order bits
  66|         xor     cl, 7                   ; Number of bits to shift
  67|         mov     ah, 1                   ; Start bit mask
  68|         shl     ah, cl                  ; Shift for pixel
  69|         mov     cl, ah                  ; Save it
  70|
  71| ; Use write mode 2 (single-pixel update)
  72|         mov     dx, 03CEh               ; 6845 command register
  73|         mov     al, 5                   ; Specify mode register
  74|         mov     ah, 2                   ; Read mode 0, write mode 2
  75|         out     dx, ax                  ; Send
  76|         ; Following added May '89
  77|         mov     al, 3                   ; Specify function select reg
  78|         xor     ah, ah                  ; Replace-pixel mode
  79|         out     dx, ax                  ; Send
  80|
  81| ; Set 6845 bit mask register
  82|         mov     al, 8                   ; Specify bit mask register
  83|         mov     ah, cl                  ; al = mask
  84|         out     dx, ax                  ; Send bit mask
  85|
  86| ; Draw the pixel
  87|         mov     al, es:[bx]             ; Load 6845 latch registers
  88|         xor     al, al                  ; Clear
  89|         mov     byte ptr es:[bx], al    ; Zero the pixel for replace
  90|         mov     al, _color1             ; Get the pixel value
  91|         mov     es:[bx], al             ; Write the pixel
  92|
  93| ; Restore video controller to default state
  94|         mov     dx, 03CEh
  95|         mov     ax, 0005h               ; write mode 0, read mode 0
  96|         out     dx, ax
  97|         mov     ax, 0FF08h              ; default bit mask
  98|         out     dx, ax
  99|         mov     ax, 0003h               ; default function select
 100|         out     dx, ax
 101|         xor     ax, ax                  ; zero Set/Reset
 102|         out     dx, ax
 103|         mov     ax, 0001h               ; zero Enable Set/Reset
 104|         out     dx, ax
 105|         mov     dx, 03C4h               ; 6845 address reg
 106|         mov     ax, 0F02h               ; Data reg, enable all planes
 107|         out     dx, ax
 108|
 109| exit:
 110|         mov     sp, bp
 111|         pop     bp
 112|         retf
 113| _draw_point     ENDP
 114|                 END





<a name="00fa_000a"><a name="00fa_000a">
<a name="00fa_000b">
[LISTING TWO]
<a name="00fa_000b">

   1| ; HLINE.ASM: Fast horizontal line drawing routine
   2| ; Uses 6845 Write Mode 0 to update 8 pixels at a time on EGA/VGA
   3| ; C prototype is
   4| ;     void far hline (int x, int y, int length_in_pixels);
   5| ; Writes in current color1 from GRAFIX.LIB
   6| ; Microsoft MASM 5.1
   7| ; K. Porter, .MDUL/DDJ.MDNM/ Graphics Programming Column, March 89
   8|
   9| .MODEL  LARGE
  10| .CODE
  11|         PUBLIC  _hline
  12|         EXTRN   _color1 : BYTE          ; Current palette reg for pixel
  13|         EXTRN   _draw_point : PROC      ; Pixel writing routine
  14|         EXTRN   _vuport : WORD          ; far ptr to vuport structure
  15|
  16| ; Declare arguments passed by caller
  17|         x       EQU     [bp+6]
  18|         y       EQU     [bp+8]
  19|         len     EQU     [bp+10]
  20|
  21| ; Declare auto variables
  22|         last    EQU     [bp- 2]         ; Last byte to write
  23|         solbits EQU     [bp- 4]         ; Mask for start of line
  24|         oddsol  EQU     [bp- 6]         ; # odd bits at start of line
  25|         eolbits EQU     [bp- 8]         ; Mask for end of line
  26|         oddeol  EQU     [bp-10]         ; # odd bits at end of line
  27| ; ----------------------------
  28|
  29| _hline          PROC FAR                ; ENTRY POINT TO PROC
  30|         push    bp                      ; entry processing
  31|         mov     bp, sp
  32|         sub     sp, 10                  ; make room for auto variables
  33|         xor     ax, ax                  ; initialize auto variables
  34|         mov     last, ax
  35|         mov     solbits, ax
  36|         mov     oddsol, ax
  37|         mov     eolbits, ax
  38|         mov     oddeol, ax
  39|
  40| ; Do nothing if line length is zero
  41|         mov     bx, len                 ; get line length
  42|         cmp     bx, 0                   ; length = 0?
  43|         jnz     chlen                   ; if not, go on
  44|         jmp     quit                    ; else nothing to draw
  45|
  46| ; Call draw_point() with a loop if line length < 8
  47| chlen:  cmp     bx, 8
  48|         jnb     getvp                   ; go if len >= 8
  49|         mov     ax, y                   ; get args
  50|         mov     cx, x
  51| drpt:   push    bx                      ; push remaining length
  52|         push    ax                      ; push args to draw_point()
  53|         push    cx
  54|         call    _draw_point             ; draw next pixel
  55|         pop     cx                      ; clear args from stack
  56|         pop     ax
  57|         pop     bx                      ; fetch remaining length
  58|         inc     cx                      ; next x
  59|         dec     bx                      ; count pixel drawn
  60|         jnz     drpt                    ; loop until thru
  61|         jmp     quit                    ; then exit
  62|
  63| ; Point ES:[BX] to vuport structure
  64| getvp:  mov     ax, _vuport+2           ; get pointer segment
  65|         mov     es, ax
  66|         mov     bx, _vuport             ; get offset
  67|
  68| ; Clip if starting coordinates outside viewport
  69|         mov     cx, y                   ; get y
  70|         cmp     cx, es:[bx+6]           ; is y within viewport?
  71|         jl      checkx                  ; ok if so
  72|         jmp     quit                    ; else quit
  73| checkx: mov     ax, x                   ; get x
  74|         cmp     ax, es:[bx+4]           ; is x within viewport?
  75|         jl      remap                   ; ok if so
  76|         jmp     quit                    ; else quit
  77|
  78| ; Map starting coordinates to current viewport
  79| remap:  add     ax, es:[bx]             ; offset x by vuport.left
  80|         mov     x, ax                   ; save remapped X
  81|         add     cx, es:[bx+2]           ; offset y by vuport.top
  82|         mov     y, cx                   ; save remapped Y
  83|
  84| ; Clip line length to viewport width
  85|         mov     ax, es:[bx+4]           ; get vuport.width
  86|         sub     ax, x                   ; maxlength = width - starting x
  87|         add     ax, es:[bx]             ;     + vuport.left
  88|         cmp     ax, len                 ; if maxlength > length
  89|         jg      wm0                     ;   length is ok
  90|         mov     len, ax                 ;   else length = maxlength
  91|
  92| ; Set 6845 for write mode 0, all planes enabled, color selected
  93| wm0:    mov     dx, 03CEh
  94|         mov     ax, 0005h               ; Set write mode
  95|         out     dx, ax
  96|         mov     ax, 0FF00h              ; Set/Reset reg, enable all planes
  97|         out     dx, ax
  98|         mov     ax, 0FF01h              ; Enable set/reset reg, all planes
  99|         out     dx, ax
 100|         mov     dx, 03C4h               ; 6845 address reg
 101|         mov     al, 2                   ; Data reg
 102|         mov     ah, _color1             ; Palette reg planes enabled
 103|         out     dx, ax                  ; Set color code
 104|         ; Following added May '89
 105|         mov     ah, 3                   ; Function select reg
 106|         xor     al, al                  ; Pixel-replace mode
 107|         out     dx, ax
 108|
 109| ; Compute x coord for last byte to be written
 110|         mov     bx, x                   ; get start of line
 111|         add     bx, len                 ; end = start + length
 112|         mov     cx, bx
 113|         and     cx, 0FFF8h              ; x coordinate where odd bits
 114|         mov     last, cx                ;   at end of line begin
 115|
 116| ; Compute number of odd pixels at end of line
 117|         sub     bx, cx
 118|         mov     oddeol, bx              ; save it
 119|
 120| ; Construct pixel mask for last byte of line
 121|         cmp     bx, 0
 122|         jz      bsol                    ; go if no odd pixels
 123|         xor     ax, ax
 124| eolb:   shr     ax, 1                   ; shift right and
 125|         or      ax, 80h                 ;   set H/O bit
 126|         dec     bl                      ;   until mask is built
 127|         jnz     eolb
 128|         mov     eolbits, ax             ; then save mask
 129|
 130| ; Compute number of odd pixels at start of line
 131| bsol:   mov     cx, x                   ; get starting X again
 132|         and     cx, 7                   ; # of pixels from start of byte
 133|         jz      saddr                   ; go if none
 134|         mov     bx, 8
 135|         sub     bx, cx                  ; # of pixels to write
 136|         mov     oddsol, bx              ; save
 137|
 138| ; Construct pixel mask for first byte of line
 139|         xor     ax, ax
 140| solb:   shl     ax, 1                   ; shift left and
 141|         or      ax, 1                   ;   set L/O bit
 142|         dec     bl                      ;   until mask is built
 143|         jnz     solb
 144|         mov     solbits, ax             ; then save mask
 145|
 146| ; Translate last byte X into an address
 147| saddr:  mov     ax, 0A000h
 148|         mov     es, ax                  ; ES ==> video buffer
 149|         mov     bx, y                   ; get row
 150|         mov     ax, 80
 151|         mul     bx
 152|         mov     bx, ax                  ; BX = row offset = row * 80
 153|         push    bx                      ; save row offset
 154|         mov     ax, last                ; get last byte X
 155|         mov     cl, 3
 156|         shr     ax, cl                  ; shift for col offset
 157|         add     bx, ax                  ; last offs = row offs + col offs
 158|         mov     last, bx
 159|
 160| ; Compute address of first byte (ES:[BX])
 161|         pop     bx                      ; fetch row offset
 162|         mov     ax, x                   ; get col offset
 163|         mov     cl, 3
 164|         shr     ax, cl                  ; shift right 3 for col offset
 165|         add     bx, ax                  ; offset = row offs + col offs
 166|         cmp     bx, last                ; is first byte also last?
 167|         jz      weol                    ; skip to end mask if so
 168|
 169| ; Write start of line
 170|         mov     dx, 03CEh               ; 6845 port
 171|         mov     ah, solbits             ; start-of-line mask
 172|         cmp     ah, 0
 173|         jz      w8                      ; go if empty mask
 174|         mov     al, 8                   ; set bit mask reg
 175|         out     dx, ax

 176|         mov     cl, es:[bx]             ; load 6845 latches
 177|         mov     ax, solbits
 178|         neg     al                      ; complement
 179|         dec     al                      ;   for reversed bit mask
 180|         and     al, cl                  ; filter previously unset pixels
 181|         mov     es:[bx], al             ; clear affected bits
 182|         mov     al, _color1
 183|         mov     es:[bx], al             ; set affected bits
 184|         inc     bx                      ; next byte
 185|         cmp     bx, last                ; ready for end of line yet?
 186|         jae     weol                    ; go if so
 187|
 188| ; Write 8 pixels at a time until last byte in line
 189| w8:     mov     ax, 0FF08h              ; update all pixels in byte
 190|         out     dx, ax                  ; set bit mask reg
 191|         mov     al, es:[bx]             ; load 6845 latches
 192|         xor     al, al
 193|         mov     es:[bx], al             ; clear all pixels
 194|         mov     al, _color1
 195|         mov     es:[bx], al             ; set all bits
 196|         inc     bx                      ; next byte
 197|         cmp     bx, last                ; thru?
 198|         jnz     w8                      ; loop if not
 199|
 200| ; Write end of line
 201| weol:   mov     dx, 03CEh               ; 6845 port
 202|         mov     ah, eolbits             ; end-of-line mask
 203|         cmp     ah, 0
 204|         jz      rvc                     ; go if empty mask
 205|         mov     al, 8                   ; set bit mask reg
 206|         out     dx, ax
 207|         mov     cl, es:[bx]             ; load 6845 latches
 208|         mov     ax, eolbits
 209|         neg     al                      ; complement
 210|         dec     al                      ;   for reversed bit mask
 211|         and     al, cl                  ; filter previously unset pixels
 212|         mov     es:[bx], al             ; clear affected bits
 213|         mov     al, _color1
 214|         mov     es:[bx], al             ; set affected bits
 215|
 216| ; Restore video controller to default state
 217| rvc:    mov     dx, 03CEh
 218|         mov     ax, 0005h               ; write mode 0, read mode 0
 219|         out     dx, ax
 220|         mov     ax, 0FF08h              ; default bit mask
 221|         out     dx, ax
 222|         mov     ax, 0003h               ; default function select
 223|         out     dx, ax
 224|         xor     ax, ax                  ; zero Set/Reset
 225|         out     dx, ax
 226|         mov     ax, 0001h               ; zero Enable Set/Reset
 227|         out     dx, ax
 228|         mov     dx, 03C4h               ; 6845 address reg
 229|         mov     ax, 0F02h               ; Data reg, enable all planes
 230|         out     dx, ax
 231|
 232| ; End of routine
 233| quit:   mov     sp, bp
 234|         pop     bp
 235|         retf
 236| _hline  ENDP
 237|         END
 238|
 239|





<a name="00fa_000c"><a name="00fa_000c">
<a name="00fa_000d">
[LISTING THREE]
<a name="00fa_000d">


/* From May, '89 */
/* ------------- */
typedef int VP_HAN;                         /* viewport handle type */

void far default_viewport (int height);    /* init default viewport */

VP_HAN far vp_open (int x, int y, int width, int height);
                                   /* open viewport, make it active */

int far vp_use (VP_HAN vp);                       /* make vp active */

void far vp_close (VP_HAN vp);                    /* close viewport */

void far vp_outline (VP_HAN vp);                      /* outline vp */

VP_HAN far vp_active (void);             /* get handle of active vp */

int far vp_width (void)                      /* get active vp width */

int far vp_height (void)                    /* get active vp height */






<a name="00fa_000e"><a name="00fa_000e">
<a name="00fa_000f">
[LISTING FOUR]
<a name="00fa_000f">


/* VUPORT.C: Library source for viewport support       */
/* This is part of the GRAFIX library                  */
/* K. Porter, .MDUL/DDJ.MDNM/ Graphics Programming Column, May '89 */

#include <stdio.h>
#include <stdlib.h>
#include "grafix.h"

typedef struct vpnode {         /* viewport descriptor node */
  int            left, top, width, height,
                 handle;
  struct vpnode  far *next, far *prev;
} VPNODE;

#if !defined TRUE
#define FALSE 0
#define TRUE  !FALSE
#endif

VPNODE far *vplist = NULL;    /* pointer to list of vpnodes */
VPNODE def_vp = {0, 0, 640, 350, 0};    /* default viewport */
VPNODE far *vuport = &def_vp;    /* ptr to current viewport */
/* -------------------------- */

void far default_viewport (int height)
{                            /* initialize default viewport */
  vuport->height = height;
} /* ------------------------ */

VP_HAN far vp_open (int x, int y, int width, int height)
{                                        /* open a viewport */
VPNODE far *newvp, far *listnode;

  newvp = malloc (sizeof *newvp);             /* make space */
  newvp->next = newvp->prev = NULL;       /* init list ptrs */
  newvp->left   = x;                      /* set descriptor */
  newvp->top    = y;
  newvp->width  = width;
  newvp->height = height;
  newvp->handle   = 1;
  if (vplist == NULL)
    vplist = newvp;                   /* set first viewport */
  else {
    listnode = vplist;              /* add viewport to list */
    while (listnode->next != NULL)
      listnode = listnode->next;               /* find tail */
    listnode->next = newvp;              /* update pointers */
    newvp->prev    = listnode;
    newvp->handle  = listnode->handle + 1;    /* and handle */
  }
  vuport = newvp;               /* make new viewport active */
  return newvp->handle;
} /* ------------------------------------------------------ */

int far vp_use (VP_HAN vp)                /* make vp active */
{
VPNODE far *port;
int success = FALSE;

  if (vp == 0) {              /* switch to default viewport */
    vuport = &def_vp;
    success = TRUE;
  } else {                         /* find desired viewport */
    port = vplist;
    do {
      if (port->handle == vp) {
        success = TRUE;
        break;               /* jump out of loop when found */
      }
      port = port->next;
    } while (port != NULL);
    if (success)
      vuport = port;                         /* make active */
  }
  return success;
} /* ------------------------------------------------------ */

void far vp_close (VP_HAN vp)           /* close a viewport */
{
VPNODE far *port;

  if (vp == 0) return;            /* can't close default vp */
  port = vplist;
  while (port->next != NULL) {     /* find desired viewport */
    if (port->handle == vp)
      break;
    port = port->next;
  }
  if (port != NULL) {                 /* viewport was found */
    if (port->next != NULL)          /* so remove from list */
      port->next->prev = port->prev;
    if (port->prev != NULL)
      port->prev->next = port->next;
    if (port == vplist)                 /* if removing head */
      vplist = port->next;
    if (port == vuport)            /* if removing active vp */
      vuport = &def_vp;
    free (port);
  }
} /* ------------------------------------------------------ */

void far vp_outline (VP_HAN vp)               /* outline vp */
            /* outline is immediately outside viewport area */
{
VP_HAN  active;
int     x, y, w, h;

  active = vp_active();            /* save current viewport */
  if (vp_use (vp)) {
    x = vuport->left-1;
    y = vuport->top-1;
    w = vuport->width+1;
    h = vuport->height+1;

    /* draw outline */
    vp_use (0);                              /* full screen */
    draw_rect (x, y, w, h);
    vp_use (active);             /* restore active viewport */
  }
} /* ------------------------------------------------------ */

VP_HAN far vp_active (void)      /* get handle of active vp */
{
  return vuport->handle;
} /* ------------------------------------------------------ */

int far vp_width (void)              /* get active vp width */
{
  return vuport->width;
} /* ------------------------------------------------------ */

int far vp_height (void)            /* get active vp height */
{
  return vuport->height;
} /* ------------------------------------------------------ */





<a name="00fa_0010"><a name="00fa_0010">
<a name="00fa_0011">
[LISTING FIVE]
<a name="00fa_0011">


/* VP.C: Draws several viewports */
/* Hex star inside each */
/* K. Porter, .MDUL/DDJ.MDNM/ Graphics Programming Col, 5/89 */

#include "grafix.h"
#include <conio.h>

int hex1[] = {25,55, 110,20, 110,90,  25,55};
int hex2[] = {55,20, 135,55,  55,90,  55,20};

void main()
{
VP_HAN  vp1, vp2, vp3;

  if (init_video (EGA)) {

    /* Draw star in default viewport */
    set_color1 (5);       /* magenta */
    polyline (3, hex1);
    polyline (3, hex2);
    set_color1 (9);

    /* Draw star in unbordered viewport */
    set_color1 (1);             /* blue */
    vp1 = vp_open (60, 250, 200, 95);
    polyline (3, hex1);
    polyline (3, hex2);

    /* Draw star in small bordered viewport */
    vp2 = vp_open (200, 150, 100, 75);
    vp_outline (vp2);
    set_color1 (15);               /* white */
    polyline (3, hex1);
    polyline (3, hex2);

    /* Draw star in filled bordered viewport */
    vp3 = vp_open (400, 60, 140, 100);
    vp_outline (vp3);
    set_color1 (2);                 /* green */
    fill_rect (0, 0, vp_width(), vp_height());
    set_color1 (4);                   /* red */
    polyline (3, hex1);
    polyline (3, hex2);

    /* Wait for keypress, then close vp's and quit */
    getch();
    vp_close (vp1);
    vp_close (vp2);
    vp_close (vp3);
  }
}





<a name="00fa_0012"><a name="00fa_0012">
<a name="00fa_0013">
[LISTING SIX]
<a name="00fa_0013">

/* LINEGRAF.C: Self-scaling viewports showing a line graph */
/* K. Porter, .MDUL/DDJ.MDNM/ Graphics Programming Column, May '89     */

#include "grafix.h"
#include <conio.h>

int data [] = {30, 70, 10, 40, 30, 80, 60, 90, 40, 50, 40};

void main ()
{
void graph (void);
VP_HAN vp1, vp2, vp3;

  if (init_video (EGA)) {

    /* Open three viewports */
    vp1 = vp_open (  1,   1, 360, 150);
    vp2 = vp_open (  1, 170, 638, 170);
    vp3 = vp_open (500,  50,  80,  50);

    /* First version upper left quadrant */
    vp_use (vp1);
    set_color1 (14);  /* yellow */
    vp_outline (vp1);
    graph();
    getch();          /* wait for keypress */
    vp_close (vp1);

    /* Second across bottom */
    vp_use (vp2);
    set_color1 (2);   /* green */
    vp_outline (vp2);
    graph();
    getch();          /* wait for keypress */
    vp_close (vp2);

    /* Third at upper right */
    vp_use (vp3);
    set_color1 (3);   /* cyan */
    vp_outline (vp3);
    graph();
    getch();          /* wait for keypress */
    vp_close (vp3);
  }
} /* ------------------------ */

void graph (void)   /* draw self-scaling line graph */
{
int    npts, p, prevx, prevy, curx, cury;
double vint, hint;

  npts = sizeof (data) / sizeof (int);  /* # data points */

  /* Scaling factors for data points */
  hint = (double) vp_width() / (npts - 1); /* horizontal */
  vint = (double) vp_height() / 100.0;       /* vertical */

  /* Draw the graph */
  prevx = 0; prevy = vp_height() - (int)(data[0] * vint);
  for (p = 1; p < npts; p++) {
    curx = (int)(p * hint);
    cury = vp_height() - (int)(data[p] * vint);
    draw_line (prevx, prevy, curx, cury);
    prevx = curx; prevy = cury;
  }
}




[GRAFIX.C, NOT A NUMBER LISTING IN DDJ 5/89]

/* Library source file GRAFIX.C               */
/* EGA/VGA graphics subsystem                 */
/* Following library routines are external:   */
/*     DRAWPT.ASM       Feb '89               */
/*     HLINE.ASM        Mar '89               */
/*     EGAPALET.C       Apr '89               */
/*     PIXEL.ASM        May '89               */
/*     VUPORT.C         May '89               */
/* K. Porter, DDJ Graphics Programming Column */
/* ------------------------------------------ */

#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "grafix.h"

#if !defined TRUE
#define FALSE 0
#define TRUE  !FALSE
#endif

/* Variables global to this library */
int  color1,                            /* foreground color */
     oldmode = 0,                      /* pre-graphics mode */
     grafixmode = 0,               /* default graphics mode */
     ega = FALSE,                     /* equipment Booleans */
     vga = FALSE,
     colormonitor = FALSE,
     curpos,                        /* text cursor position */
     textpage;                          /* active text page */
unsigned vidmem;                    /* video buffer segment */
char far *vidsave;                /* video buffer save area */
/* -------------------------------------------------------- */

int far init_video (int mode)

/* Initializes video adapter and defaults for mode          */
/* Sets up pc_textmode() to be called on pgm termination    */
/* Returns TRUE or FALSE indicating success                 */
/* This function will be expanded in a later version        */
{
union REGS r;
int result = FALSE;

  /* Determine attached adapter and monitor */
  r.h.ah = 0x1A;                    /* VGA inquiry function */
  r.h.al = 0;
  int86 (0x10, &r, &r);                    /* ROM BIOS call */
  if (r.h.al == 0x1A)
    switch (r.h.bl) {
      case 4 : ega = TRUE;                     /* EGA color */
               colormonitor = TRUE;
               break;
      case 5 : ega = TRUE;                      /* EGA mono */
               break;
      case 7 : ega = TRUE;                      /* VGA mono */
               vga = TRUE;
               break;
      case 8 : ega = TRUE;                     /* VGA color */
               vga = TRUE;
               colormonitor = TRUE;
    }
  else {                        /* No VGA, so check for EGA */
    r.h.ah = 0x12;
    r.x.bx = 0x10;
    int86 (0x10, &r, &r);
    if (r.x.bx != 0x10) {              /* if EGA present... */
      ega = TRUE;                               /* set flag */
      r.h.ah = 0x12;
      r.h.bl = 0x10;              /* find out which monitor */
      int86 (0x10, &r, &r);
      if (r.h.bl != 0)
        colormonitor = TRUE;                   /* EGA color */
    }
  }

  /* Proceed only if EGA or VGA present */
  if (ega | vga) {
    set_color1 (15);                 /* default pixel color */
    r.h.ah = 0x0F;               /* get current screen mode */
    int86 (0x10, &r, &r);
    oldmode = r.h.al;                           /* store it */
    textpage = r.h.bh;             /* also active text page */

    if (colormonitor)              /* point to video memory */
      vidmem = 0xB800;
    else
      vidmem = 0xB000;
    vidsave = malloc (4096);          /* allocate save area */
    movedata                   /* save text screen contents */
        (vidmem, 0, FP_SEG (vidsave), FP_OFF (vidsave), 4096);

    r.h.ah = 3;                 /* get text cursor position */
    r.h.bh = textpage;
    int86 (0x10, &r, &r);
    curpos = r.x.dx;                         /* and save it */

    if ((mode == EGA) && ega) {
      r.h.ah = 0;
      r.h.al = mode;                        /* set EGA mode */
      int86 (0x10, &r, &r);
      grafixmode = mode;                       /* save mode */
      atexit (pc_textmode);       /* register exit function */
      result = TRUE;
    } else
      if ((mode == VGA16) && vga) {      /* typo fixed 5/89 */
        r.h.ah = 0;
        r.h.al = mode;
        int86 (0x10, &r, &r);
        grafixmode = mode;
        atexit (pc_textmode);
        result = TRUE;
      }
  }
  if (!result) {            /* unable to switch to graphics */
    oldmode = 0;              /* so cancel text screen save */
    free (vidsave);
    vidsave = 0;
  }
  if (mode == VGA16 && vga)  /* viewport init added May '89 */
    default_viewport (480);
  return result;
} /* ------------------------------------------------------ */


void far pc_textmode (void)
/* SPECIFIC TO MS-DOS */
/* Restore text mode */
/* Automatically called on pgm termination */
{
union REGS r;

  if (oldmode) {              /* if not in text mode now... */
    r.h.ah = 0;
    r.h.al = oldmode;                  /* restore text mode */
    int86 (0x10, &r, &r);
    movedata                         /* restore text screen */
        (FP_SEG (vidsave), FP_OFF (vidsave), vidmem, 0, 4096);
    free (vidsave);                /* free allocated memory */
    vidsave = 0;                            /* zero pointer */
    oldmode = 0;                                   /* reset */
    r.h.ah = 2;              /* restore old cursor position */
    r.h.bh = textpage;
    r.x.dx = curpos;
    int86 (0x10, &r, &r);
  }
} /* ------------------------------------------------------ */

void far set_color1 (int palette_reg)
/* Select pixel color from palette register */
{
  color1 = palette_reg;
} /* ------------------------------------------------------ */

void far draw_line (int x1, int y1, int x2, int y2)
                        /* Bresenham line drawing algorithm */
                        /* x1, y1 and x2, y2 are end points */
{
int  w, h, d, dxd, dyd, dxn, dyn, dinc, ndinc, p;
register x, y;

  /* Set up */
  x = x1; y = y1;                          /* start of line */
  w = x2 - x1;                      /* width domain of line */
  h = y2 - y1;                     /* height domain of line */

  /* Determine drawing direction */
  if (w < 0) {                     /* drawing right to left */
    w = -w;                               /* absolute width */
    dxd = -1;                    /* x increment is negative */
  } else                           /* drawing left to right */
    dxd = 1;                       /* so x incr is positive */
  if (h < 0) {                     /* drawing bottom to top */
    h = -h;                       /* so get absolute height */
    dyd = -1;                         /* y incr is negative */
  } else                           /* drawing top to bottom */
    dyd = 1;                       /* so y incr is positive */

  /* Determine major axis of motion */
  if (w < h) {                           /* major axis is Y */
    p = h, h = w, w = p;       /* exchange height and width */
    dxn = 0;
    dyn = dyd;
  } else {                               /* major axis is X */
    dxn = dxd;
    dyn = 0;
  }

  /* Set control variables */
  ndinc = h * 2;                  /* Non-diagonal increment */
  d = ndinc - w;                /* pixel selection variable */
  dinc = d - w;                       /* Diagonal increment */

  /* Loop to draw the line */
  for (p = 0; p <= w; p++) {
    draw_point (x, y);
    if (d < 0) {                     /* step non-diagonally */
      x += dxn;
      y += dyn;
      d += ndinc;
    } else {                             /* step diagonally */
      x += dxd;
      y += dyd;
      d += dinc;
    }
  }
} /* ------------------------------------------------------ */

void far draw_rect (int xleft, int ytop, int w, int h)
/* Draw outline rectangle in color1 from top left corner */
/* w and h are width and height */
/* xleft and ytop are top left corner */
{
  draw_line (xleft, ytop, xleft+w, ytop);            /* top */
  draw_line (xleft+w, ytop, xleft+w, ytop+h);      /* right */
  draw_line (xleft+w, ytop+h, xleft, ytop+h);     /* bottom */
  draw_line (xleft, ytop+h, xleft, ytop);           /* left */
} /* ------------------------------------------------------ */

void far polyline (int edges, int vertex[])
/* Draw multipoint line of n edges from n+1 vertices where: */
/*        vertex [0] = x0    vertex [1] = y0                */
/*        vertex [2] = x1    vertex [3] = y1                */
/*          etc.                                            */
{
int x1, y1, x2, y2, v;

  x1 = vertex[0];
  y1 = vertex[1];
  for (v = 2; v < (edges+1)*2; v+= 2) {
    x2 = vertex[v];
    y2 = vertex[v+1];
    draw_line (x1, y1, x2, y2);
    x1 = x2;
    y1 = y2;
  }
} /* ------------------------------------------------------ */

void far fill_rect (int xleft, int ytop, int w, int h)
/* Draw solid rectangle in color1 from top left corner */
{
register y;

  for (y = ytop; y < ytop+h; y++)
    hline (xleft, y, w);
} /* ------------------------------------------------------ */


[GRAPHIX.H, NOT A NUMBERED LISTING IN DDJ, 5/89]


/* Include file for GRAFIX.LIB                */
/* EGA/VGA graphics subsystem                 */
/* K. Porter, DDJ Graphics Programming Column */
/* ------------------------------------------ */

/* Color constants from April, 89 */
#define Black     0        /* standard colors */
#define Blue      1
#define Green     2
#define Cyan      3
#define Red       4
#define Magenta   5
#define Brown     0x14
#define LtGray    7
#define DkGray    0x38
#define LtBlue    0x39
#define LtGreen   0x3A
#define LtCyan    0x3B
#define LtRed     0x3C
#define LtMagenta 0x3D
#define Yellow    0x3E
#define White     0x3F

#define RED0      0x00  /* basic hues for mixing */
#define RED1      0x20
#define RED2      0x04
#define RED3      0x24
#define GRN0      0x00
#define GRN1      0x10
#define GRN2      0x02
#define GRN3      0x12
#define BLU0      0x00
#define BLU1      0x08
#define BLU2      0x01
#define BLU3      0x09

#if !defined byte
#define byte unsigned char
#endif

/* Supported video modes */
#define  EGA       0x10              /* EGA 640 x 350, 16/64 colors */
#define  VGA16     0x11              /* VGA 640 x 480, 16/64 colors */

/* Function prototypes */
/* From February, '89 */
/* ------------------ */
int far init_video (int mode);        /* init display in video mode */

void far pc_textmode (void);                        /* PC text mode */

void far draw_point (int x, int y);        /* write pixel in color1 */

void far set_color1 (int palette_reg);      /* set foreground color */

/* From March, '89 */
/* --------------- */
void far draw_line (int x1, int y1, int x2, int y2);
                                /* Bresenham line drawing algorithm */

void far draw_rect (int left, int top, int width, int height);
                             /* draw rectangle from top left corner */

void far polyline (int edges, int vertices[]);     /* draw polyline */

void far hline (int x, int y, int len);          /* horizontal line */

void far fill_rect (int left, int top, int width, int height);
      /* draw solid rectangle in color1 starting at top left corner */

/* From April, '89 */
/* --------------- */
byte far ega_palreg (int preg);         /* color in EGA palette reg */

void far set_ega_palreg (int reg, int color);    /* set palette reg */

byte far colorblend (byte r, byte g, byte b);         /* blend hues */

void far get_ega_colormix (int preg, int *r, int *g, int *b);
        /* get mix of red, green, and blue in EGA pal register preg */

/* From May, '89 */
/* ------------- */
typedef int VP_HAN;                         /* viewport handle type */

void far default_viewport (int height);    /* init default viewport */

VP_HAN far vp_open (int x, int y, int width, int height);
                                   /* open viewport, make it active */

int far vp_use (VP_HAN vp);                       /* make vp active */

void far vp_close (VP_HAN vp);                    /* close viewport */

VP_HAN far vp_active (void);             /* get handle of active vp */

void far vp_outline (VP_HAN vp);                      /* outline vp */

int far vp_width (void);               /* get active viewport width */

int far vp_height (void);                             /* and height */








Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.