Viewpoints not only provide you with a window to the world, they let you define the boundaries of that world. At least that's what Kent and his dog found out.
May 01, 1989
URL:http://www.drdobbs.com/architecture-and-design/fencing-the-dog/184408142
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:
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...
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.
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
VUPORT.C in Listing Four provides the source for the new routines. Compile it, then add it to the library with
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.
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
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.
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]
Copyright © 1989, Dr. Dobb's Journal
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.
if (mode = = VGA16 && vga)
default_viewport (480);
So What's a Viewport?
Bring In The New!
LIB grafix -+grafix;
LIB grafix +vuport;
Let's Put It To Work
cury = vp_height( ) -
(int)(data[p] * vint);
Availability
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
[LISTING TWO]
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|
[LISTING THREE]
/* 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 */
[LISTING FOUR]
/* 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;
} /* ------------------------------------------------------ */
[LISTING FIVE]
/* 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);
}
}
[LISTING SIX]
/* 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 */