PROGRAMMER'S GUIDE TO PC(R) AND PS/2(TM) VIDEO SYSTEMS



���������������������������������������������������������������������������



               PROGRAMMER'S GUIDE TO PC(R) AND PS/2(TM)

                            VIDEO SYSTEMS


    Maximum Video Performance from the EGA(TM), VGA, HGC, and MCGA




                            RICHARD WILTON



���������������������������������������������������������������������������



PUBLISHED BY
Microsoft Press
A Division of Microsoft Corporation
16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717

Copyright(C) 1987 by Richard Wilton
All rights reserved. No part of the contents of this book may
be reproduced or transmitted in any form or by any means without
the written permission of the publisher.

Library of Congress Cataloging in Publication Data
Wilton, Richard, 1953-
The programmer's guide to PC and PS/2 video systems.
Includes index.
1. IBM Personal Computer--Programming.  2. Expansion boards
   (Microcomputers).
3. Computer graphics.  I. Title.
QA76.8.I2594W55    1987    005.265    87-20264
ISBN 1-55615-103-9

Printed and bound in the United States of America.

1 2 3 4 5 6 7 8 9  FGFG  8 9 0 9 8 7

Distributed to the book trade in the
United States by Harper & Row.

Distributed to the book trade in
Canada by General Publishing Company, Ltd.

Distributed to the book trade outside the
United States and Canada by Penguin Books Ltd.

Penguin Books Ltd., Harmondsworth, Middlesex, England
Penguin Books Australia Ltd., Ringwood, Victoria, Australia
Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand

British Cataloging in Publication Data available


Acquisitions Editor: Claudette Moore
Technical Editor: Jeff Hinsch


IBM(R) is a registered trademark and PC/AT(TM), PC-DOS(TM), PC/XT(TM), and
PS/2(TM) are trademarks of International Business Machines Corporation.
Microsoft(R) and MS-DOS(R) are registered trademarks of Microsoft
Corporation.



     Contents



     Acknowledgments

     Introduction

      1     IBM Video Hardware and Firmware

      2     Programming the Hardware

      3     Alphanumeric Modes

      4     Graphics Modes

      5     Pixel Programming

      6     Lines

      7     Circles and Ellipses

      8     Region Fill

      9     Graphics Text

     10     Alphanumeric Character Sets

     11     Bit Blocks and Animation

     12     Some Advanced Video Programming Techniques

     13     Graphics Subroutines in High-Level Languages

     Appendix A: Video BIOS Summary

     Appendix B: Printing the Screen

     Appendix C: Identifying Video Subsystems

     Glossary

     Index



Acknowledgments



     The material in Chapters 6, 7, and 8 owes a great deal to the orig-
     inal efforts of several respected workers in the field of computer
     graphics. In each of these chapters I have included references to some
     of their best-known publications. If you are intrigued by the
     algorithms described in these chapters, by all means obtain the
     original publications and explore them yourself.

     This book could not have been written without the encouragement of my
     family, friends, and colleagues, who deserve great thanks for their
     patience and support. My gratitude also to Andy Fischer and to Charles
     Petzold, both of whom graciously reviewed portions of this book and
     offered accurate criticism and suggestions.

     And, of course, my special thanks to the enthusiastic people at
     Microsoft Press--Claudette Moore, Jeff Hinsch, and many others--who
     painstakingly transformed the raw material of this book into the
     finished product.



Introduction



     I clearly remember the day I first plugged a new IBM Enhanced Graphics
     Adapter (EGA) into an IBM PC. It was good to have IBM's new "enhanced"
     video hardware, with its better resolution and control over colors, as
     well as features not found in any of IBM's earlier PC video hardware.
     Now I was ready to write some really sharp graphics applications.

     Or so I thought. The problem was, I couldn't figure out how to program
     the contraption. I had no technical documentation at all. (It arrived
     in the mail six months and $125 later.) I tried disassembling the
     EGA's ROM BIOS, but studying 6000 uncommented machine instructions
     soon raised more questions than it answered. I desperately tried the
     shotgun approach--changing the contents of memory locations and
     machine registers just to see what would happen--but this was like
     chopping out random pieces of an automobile just to see what would
     stop working.

     What I lacked was the details--conceptual descriptions of the hardware
     design, tables describing the programming interface, and, above all,
     source code examples for some typical programming techniques. A few
     well-chosen source code examples would have saved many hours of
     experimentation and frustration when I was trying to understand how to
     program that video adapter.

     This book was inspired by the painful memory of that experience. It is
     filled with source code examples. Its text describes the source code,
     and vice versa. This book also has many tables and summary
     descriptions of the hardware programming interface. In short, this
     book is what I wish I'd had when I started to program PC video
     hardware.


What This Book Is About

     The first chapter of this book is a general overview of the video
     display environment. It describes the commonly used PC and PS/2 video
     hardware the rest of the book deals with. It also introduces you (if
     you aren't already on speaking terms) to the well-known ROM BIOS video
     support routines.

     The next 10 chapters contain the nuts and bolts of IBM video
     programming. The earlier chapters cover the fundamentals, including
     hardware architecture, video display modes, and the nature of the
     interface between your programs and the hardware. The later chapters
     build upon the fundamentals to demonstrate a number of techniques for
     producing text and graphics output.

     The last two chapters of this book take you to the low and high levels
     of video graphics programming. Chapter 12 is the hardware tinkerer's
     chapter--if you want to work with vertical interrupts or play with bit
     planes, this one's for you. Finally, Chapter 13 tells how to link
     your video hardware drivers to high-level programs and introduces you
     to several commercial video output packages.


What You Need to Use This Book

     This book is not really meant for beginners. That's not to say that a
     programmer who is just learning how to write working code will not
     benefit from this material. On the contrary, the many working examples
     of useful source code should be valuable to anyone who plans to do
     serious programming for PCs or PS/2s. Nevertheless, the broader your
     programming background, the more tools you will have for solving the
     diverse and exacting problems involved in video programming.

Languages

     I use assembly language and C for most of the programming examples in
     this book, although I intentionally avoid some of C's more cryptic
     syntactic constructs. If you are comfortable with assembly language
     and with a high-level language such as C, Pascal, FORTRAN, PL/1, or
     structured BASIC, you should have no problem reading the source code
     examples.

     Moreover, Chapter 13 discusses interfaces for several high-level
     languages using different memory models and subroutine-calling
     protocols. You can follow the guidelines there to convert any of the
     C-callable source code examples to the subroutine-calling protocol
     used by your favorite language translator.

     You might want to use some other programming tools if you plan to
     experiment with the source code examples that follow. For example, a
     good assembly-language debugger can be extremely helpful. You will
     probably need an object linker if you plan to call the assembly-
     language routines in this book from high-level-language programs.
     Also, as source files and object modules proliferate, you might find a
     UNIX-like make utility quite useful in keeping things straight.

Operating System

     Everything in this book is intended to run under MS-DOS, or PC-DOS,
     version 2.0 or later. However, there is nothing in any of the source
     code that verifies which operating system is in use, so be careful if
     you transport the code to earlier versions of MS-DOS or to another
     operating system.

Hardware

     Having a PC or PS/2 with a video display attached is essential. Video
     programming is like swimming: It's one thing to read about it, but
     it's quite another experience to try it yourself. In fact, if you plan
     to do a great deal of video programming, you should consider
     installing two different video subsystems and displays in your PC.
     With two separate sets of video hardware in the same computer, you can
     run a debugger on one screen while a test program produces output on
     the other screen. This dual-display hardware configuration is a real
     timesaver, particularly when you're developing video graphics routines
     such as those described in Chapters 5 through 9.

     Here is a list of the various computers and video adapters I used to
     develop the techniques discussed in this book:

     Computers
       IBM PC/XT
       IBM PC/AT
       IBM PS/2 Model 30
       IBM PS/2 Model 60

     Adapters
       IBM Monochrome Display Adapter
       IBM Color Graphics Adapter
       IBM Enhanced Graphics Adapter
       IBM PS/2 Display Adapter
       Hercules Graphics Card
       Hercules Graphics Card Plus
       Hercules Color Card
       Hercules InColor Card

     If you are using one of these computers or adapters, or a hardware-
     compatible clone, then you should be able to run the source code
     examples.

Manuals

     To program IBM PC video hardware effectively, you need to know what
     the hardware is designed to do and how software and the system BIOS
     are expected to interact with it. This basic information is found in
     IBM's Technical Reference manuals for the PC, PC/XT, PC/AT, and PS/2s
     and in its Options and Adapters Technical Reference manuals. Most
     second-source manufacturers of IBM PC video equipment also provide
     detailed technical information on their hardware.

     The material in this book is intended to complement the discussions in
     the manufacturers' technical documentation. I tried to follow the
     manufacturers' terminology and hardware descriptions wherever
     possible. However, the manufacturers' documentation goes somewhat awry
     at times. If you find a discrepancy between the official documentation
     and this book, you can (I hope) rely on this book to contain the right
     information.

     Still, in a book this size, I have certainly made some mistakes. I
     welcome your comments, criticisms, and suggestions.

     I have found that writing good video software is challenging, but the
     rewards are particularly satisfying. I hope to share some of the
     challenges--and some of the satisfaction--with you in this book.



                    1  IBM Video Hardware and Firmware


                      IBM PC and PS/2 Video Hardware
         IBM Monochrome Display Adapter and Color Graphics Adapter
           Hercules Graphics Card � Hercules Graphics Card Plus
           IBM Enhanced Graphics Adapter � Hercules InColor Card
             Multi-Color Graphics Array � Video Graphics Array

                  Introduction to the ROM BIOS Interface
                  Interrupt 10H � Video Display Data Area
            Accessing the Video BIOS from a High-Level Language



     Microcomputer video systems keep getting better. Since the
     introduction of the IBM PC in 1981, engineering technology has
     improved, and the market for more powerful video hardware has widened.
     Both IBM and its competitors have responded by developing increasingly
     sophisticated video adapters and displays, as well as the software to
     accompany them.

     This chapter provides an overview of the evolution of IBM PC and PS/2
     video hardware. This overview is by no means comprehensive, but it
     covers the most widely used video equipment that IBM and Hercules
     offer. The chapter concludes with an introduction to IBM's video BIOS,
     a set of drivers built into ROM in all IBM PCs and PS/2s, which
     provides a basic programming interface for video applications.


IBM PC and PS/2 Video Hardware


     A "plain vanilla" IBM PC/XT or PC/AT contains no built-in video
     hardware, so you must select and install the video hardware yourself.
     In a typical configuration, a video display (monitor) is attached with
     a 9-wire cable to a video adapter installed inside the PC. A typical
     video adapter is a printed circuit board with a 9-pin connector that
     attaches to the monitor's cable and a 2-by-31-connection card-edge tab
     that inserts into one of the slots on the PC's motherboard. Figure
     1-1 shows these connectors, as well as some of the integrated
     circuits common to many IBM video adapters. The circuitry in the video
     adapter generates the signals that control what is displayed on the
     monitor's screen.

     When you purchase an IBM PC, you must decide which video adapter and
     monitor to use. The most widely used video adapters with the most
     software written for them are IBM's Monochrome Display Adapter, Color
     Graphics Adapter, and Enhanced Graphics Adapter, and the monochrome
     Graphics Card made by Hercules.

     In contrast, all IBM PS/2 series computers are equipped with a built-
     in video subsystem, so purchasing a separate video adapter is
     unnecessary. The video subsystem in the PS/2 Models 25 and 30 is
     called the Multi-Color Graphics Array. In Models 50, 60, and 80, the
     integrated video subsystem is commonly known as the Video Graphics
     Array. The Video Graphics Array subsystem also is available as an
     adapter for the PC/XT, PC/AT, and PS/2 Model 30. This adapter has
     essentially the same hardware features as the integrated  PS/2
     subsystem.


IBM Monochrome Display Adapter and Color Graphics Adapter

     When the PC was introduced in 1981, IBM offered two video adapters:
     the Monochrome Display Adapter (MDA) and the Color Graphics Adapter
     (CGA). The MDA is designed for use with a monochrome monitor (the IBM
     Monochrome Display) that displays 80 columns and 25 rows of
     alphanumeric text. The CGA supports either an RGB display (a monitor
     with separate input signals for red, green, and blue) or a home
     television set (which uses a composite video signal). The CGA, of
     course, can display graphics information on a dot-by-dot basis as well
     as alphanumeric text.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 1-1 is found on page 3         �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

           Figure 1-1.  A typical IBM PC video adapter.


     Even though both the MDA and the CGA can display 25 rows of 80-column
     text, most people find the MDA's green monochrome display easier on
     the eyes. This is because the monochrome display used with an MDA has
     significantly higher resolution than that of any monitor you can use
     with the CGA. Its resolution is 720 dots wide and 350 dots high; the
     maximum resolution of a CGA-driven display is 640 dots wide and 200
     dots high.

     Both adapters display characters in a rectangular matrix of dots. A
     simple calculation shows that each character is 9 dots wide and 14
     dots high on a Monochrome Display but only 8-by-8 dots on a CGA
     display. The MDA's higher resolution produces more crisply defined
     characters that are easier to read. For this reason, most PC users who
     need to read text prefer an MDA to a CGA.

     On the other hand, many computer users need to display charts,
     diagrams, and other graphics information in addition to alphanumeric
     text. Also, displaying colors on the screen is essential to many
     computer applications. Because the MDA can display only monochrome
     text, PC users who need graphics output can compromise by using the
     CGA, with its dot-by-dot color graphics capability but less-readable
     text.

     Why not just attach the higher-resolution monochrome display to a
     Color Graphics Adapter and get the best of both worlds? Unfortunately,
     the video signals generated by an MDA are incompatible with those
     required to drive a CGA monitor, and vice versa. Mismatching the
     monitor and the adapter leads to a malfunctioning monitor instead of a
     higher-resolution display.

     If you need sharp, readable text as well as color graphics, and you
     can afford the extra equipment, you can install both an MDA and a CGA
     in the same PC. You can then use the monochrome display (attached to
     the MDA) for text processing and an RGB color display (driven by the
     CGA) for color graphics.


Hercules Graphics Card

     Hercules' solution to the problem of displaying readable text and dot-
     by-dot graphics on the same monitor was to add graphics capability to
     a monochrome display adapter. The monochrome Hercules Graphics Card
     (HGC), introduced in 1982, can display graphics and alphanumeric text
     on the same green monochrome display that is used with an IBM MDA. (In
     addition to its graphics capabilities, the HGC exactly duplicates the
     function of IBM's original MDA.) The ability to display a combination
     of readable text and monochrome graphics is sufficient for many
     applications, so many PC users find the HGC an economical option.
     Because it has received support from major software vendors, the HGC
     has become firmly established in the marketplace.


Hercules Graphics Card Plus

     The HGC+ was released in June 1986. The big difference in this upgrade
     of the original HGC is that it can display customized, RAM-based
     alphanumeric character sets, whereas the MDA and HGC can display only
     one, predefined, ROM-based alphanumeric character set. Because
     alphanumeric characters can be displayed much more rapidly than dot-
     by-dot graphics characters, using the HGC+ can double or triple the
     speed of some text-oriented applications.


IBM Enhanced Graphics Adapter

     A different response to the demand for better text and graphics
     resolution is IBM's Enhanced Graphics Adapter (EGA), released in early
     1985. The EGA can be configured to emulate either an MDA or a CGA;
     what makes the EGA "enhanced" is that it can also do things its
     predecessors cannot. Unlike the MDA, the EGA can produce dot-by-dot
     graphics on a monochrome display. Furthermore, the EGA improves on the
     CGA with the ability to generate 16-color alphanumeric or graphics
     images with 640-by-350 resolution.

     Although the resolution and color capabilities of the EGA are not that
     much greater than those of the CGA, both text and graphics appear much
     sharper on the EGA than on the CGA. The availability of low-priced EGA
     clones and of high-quality software applications that exploit the
     adapter's capabilities have made the EGA a de facto hardware standard
     in the marketplace.


Hercules InColor Card

     The Hercules InColor Card, introduced in April 1987, is essentially a
     16-color version of the HGC+. The InColor hardware fully emulates the
     HGC+, so programs that run properly on the HGC+ can run without change
     on the InColor Card. The InColor Card's resolution is the same as that
     of the HGC and HGC+: 720 horizontal by 348 vertical pixels. The
     adapter's color capabilities equal those of the EGA. It can display 16
     colors at once from a palette of 64 colors. The adapter must be used
     with an EGA-compatible color display that has 350-line vertical
     resolution.

       ���ͻ     Don't confuse the InColor Card with the Hercules Color
       � T �     Card, an augmented CGA clone designed for use in the same
       � I �     computer with an HGC or HGC+.
       � P �
       ���ͼ


Multi-Color Graphics Array

     The Multi-Color Graphics Array (MCGA) is the video subsystem
     integrated into the PS/2 Models 25 and 30. From a programmer's
     perspective, the MCGA resembles the CGA in many ways, yet the MCGA has
     much better resolution (a maximum of 640 horizontal by 480 vertical
     dots) and improved color-display capabilities.

     A significant difference between the MCGA and the above video adapters
     is that the MCGA generates analog RGB video signals, whereas the
     others produce digital RGB signals. The difference between digital and
     analog RGB is something like the difference between an on-off wall
     switch and a dimmer switch. With digital RGB signals, the video
     display must recognize only whether the signal for a particular color
     (red, green, or blue) is on or off. On the other hand, a video display
     that uses analog RGB signals translates the voltage of each signal
     into a wide range of corresponding color intensities. Only an analog
     video display can be used with the MCGA.

       ���ͻ     Some video monitors can be configured for either analog or
       � T �     digital video signals. If you use the right cable, these
       � I �     monitors can be connected to an MCGA if they are
       � P �     configured for analog video.
       ���ͼ

     The justification for using analog video is that it can display a
     wider range of colors. The MCGA has a video Digital to Analog
     Converter (DAC) that enables the subsystem to display as many as 256
     different colors at once from a palette of 262,144 (256 K or 2^18)
     colors. In addition to an analog color display, IBM supplies an analog
     monochrome display for use with the MCGA. With a monochrome monitor,
     the MCGA can display as many as 64 shades of gray.


Video Graphics Array

     The term Video Graphics Array (VGA) refers specifically to part of the
     circuitry of the video subsystem in PS/2 Models 50, 60, and 80. The
     VGA is actually a single chip that integrates the same set of
     functions performed by several chips on the EGA. Nevertheless, people
     generally use the abbreviation VGA to describe the entire video
     subsystem.

     The VGA's programming interface is similar to the EGA's, so many
     programs written for the EGA will run unchanged on the VGA. The VGA is
     capable of somewhat higher display resolution (as much as 720-by-400
     in text modes, or 640-by-480 in graphics modes). Like the MCGA,
     however, the VGA contains a video DAC that can generate 256 colors at
     a time from a possible 262,144. Because the VGA generates the same
     analog RGB signals as the MCGA, it must be used with the same analog
     monochrome or color monitors.


Introduction to the ROM BIOS Interface


     A set of BIOS (Basic Input/Output System) routines in ROM is built
     into every IBM PC and PS/2. The ROM BIOS routines provide an interface
     to standard hardware features, including the time-of-day clock, the
     keyboard, floppy and hard disks, and of course the video subsystem.
     The video BIOS routines comprise a set of simple tools for performing
     basic video programming tasks such as writing strings of characters to
     the screen, erasing the screen, changing colors, and so on.

     Although the ROM BIOS video routines are sometimes slow and relatively
     unsophisticated, programs that use them are portable among different
     video subsystems in IBM PCs and PS/2s. Furthermore, most manufacturers
     of IBM PC clones have duplicated the functions of IBM's BIOS in their
     machines. Thus, a program that uses BIOS routines to access the video
     hardware is likely to be more portable than one that does not.


Interrupt 10H

     The BIOS routines are written in assembly language, so accessing them
     is easiest when you program in assembly language. All BIOS video
     routines are accessed by executing 80x86 software interrupt 10H. (The
     term 80x86 refers to the microprocessors in the Intel 8086 family:
     8086, 8088, 80286, and 80386.) For this reason, the ROM BIOS video
     interface is widely known as the INT 10H interface. The ROM BIOS
     supports a number of video input/output functions, each accessed by
     executing interrupt 10H. The functions are numbered; before executing
     interrupt 10H, you place the number of the desired function in 80x86
     register AH.

     At the time the interrupt is executed, the remaining 80x86 registers
     usually contain parameters to be passed to the BIOS routines. If the
     INT 10H function returns data to your program, it does so by leaving
     the data in one or more of the 80x86 registers. This register-based
     parameter-passing protocol is intended for use in assembly-language
     programs.

     To see how the INT 10H interface is typically used, examine the
     assembly-language routine SetVmode() in Listing 1-1. This routine can
     be linked with a program written in Microsoft C. (The underscore
     preceding the procedure name, the near keyword in the PROC
     declaration, and the use of the stack to pass parameters all follow
     Microsoft C conventions.) The heart of the routine is its call to the
     ROM BIOS to configure the video hardware for a particular video mode.
     (The details of this operation are discussed in Chapter 2 and in
     Appendix A.)


���������������������������������������������������������������������������

     Listing 1-1.  SetVmode().

���������������������������������������������������������������������������


     The actual call to the video BIOS is simple. First, the desired
     function number is placed into register AH (XOR AH,AH). Then, after
     preserving the contents of register BP on the stack (PUSH BP), the
     routine invokes the ROM BIOS function by executing interrupt 10H
     (INT 10H).

     In Listing 1-2, a complementary routine called GetVmode() interrogates
     the BIOS for the number of the current video mode. The routine obtains
     this number by executing interrupt 10H function 0FH. The ROM BIOS
     function leaves the mode number in register AL. GetVmode() then
     returns the number to the calling program.


���������������������������������������������������������������������������

     Listing 1-2.  GetVmode().

���������������������������������������������������������������������������


Video Display Data Area

     The code that precedes the actual call to the ROM BIOS in Listing 1-1
     modifies one of several global variables that reflect the status of
     the PC's video subsystem. These variables are updated and referenced
     by all ROM BIOS video routines. They are collected in a block of RAM
     called, in IBM's technical documentation, the Video Display Data Area
     (or Video Control Data Area). The Video Display Data Area consists of
     two blocks of RAM. The first block is found between memory locations
     0040:0049 and 0040:0066, the second between 0040:0084 and 0040:008A.

     Some video BIOS routines also reference a 2-bit field in a global
     variable at 0040:0010 (called EQUIP_FLAG in IBM's technical
     documentation). Bits 4 and 5 of this variable indicate a default video
     mode to be used when the computer is first booted. The code in
     SetVmode() updates this bit field to conform with the video mode being
     selected. For example, if a Monochrome Display Adapter (MDA) is
     required for the desired video mode, the bit field in EQUIP_FLAG is
     updated accordingly. (Again, details on ROM BIOS video modes are found
     in Chapter 2 and in Appendix A.)

       ���ͻ     Throughout this book are references to the INT 10H
       � T �     interface, the BIOS's Video Display Data Area, and the
       � I �     symbolic names of specific locations in the Video Display
       � P �     Data Area that are of particular interest. If you aren't
       ���ͼ     already familiar with the available INT 10H functions and
                 the contents of the Video Display Data Area, a perusal of
                 Appendix A might be very helpful.


Accessing the Video BIOS from a High-Level Language

     You can make ROM BIOS routines accessible in high-level language
     programs with an assembly-language routine such as SetVmode() or
     GetVmode(). Listings 1-3 and 1-4 are short C programs that can be
     executed as MS-DOS commands. The program in Listing 1-3 calls
     SetVmode() to select a video mode. This program may be executed
     interactively or from a batch file. The program in Listing 1-4 calls
     GetVmode() and returns the video mode number in a way that can be used
     in a batch file (that is, with IF ERRORLEVEL == commands).


���������������������������������������������������������������������������

     Listing 1-3.  A C program based on SetVmode().

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 1-4.  A C program based on GetVmode().
���������������������������������������������������������������������������


     The overall process of generating an executable file for one of these
     programs consists of compiling the C code to produce an object module,
     assembling the assembly-language code to produce another object
     module, and linking the object modules to create the executable file.
     If the C source code in Listing 1-3 is contained in a file named SM.C
     and the assembly code in Listing 1-1 is saved in SETVMODE.ASM, you
     can build the executable file SM.EXE as follows:


msc       sm;          (compile the C code)
masm      setvmode;    (assemble the subroutine)
link      sm+setvmode; (link the object modules)


       ���ͻ     Some high-level language compilers can generate
       � T �     appropriate object code for loading the 80x86 registers,
       � I �     executing interrupt 10H, and copying the results from the
       � P �     registers to the calling program. If your compiler has
       ���ͼ     this capability, you might prefer to access the INT 10H
                 interface directly, instead of linking an assembly-
                 language subroutine to your high-level program. For
                 example, Listing 1-5 uses Microsoft C's int86() function
                 to implement GetVmode().


���������������������������������������������������������������������������

     Listing 1-5.  Microsoft C's int86() function.

���������������������������������������������������������������������������


     Many other INT 10H functions are available in the ROM BIOS. Your
     application program accesses them by loading the appropriate registers
     and executing interrupt 10H. Although the INT 10H support for video
     input/output admittedly is less than perfect, it is widely used in
     operating-system software (including MS-DOS) as well as in countless
     applications. If you want to write effective video and graphics
     programs, become familiar with the capabilities and the limitations of
     the INT 10H interface.



                        2  Programming the Hardware


           Functional Components of IBM PC and PS/2 Video Subsystems
                            Monitor � Video Buffer
                     Color and Character Display Hardware
                                CRT Controller

                           The Display Refresh Cycle
                      Horizontal Timing � Vertical Timing

                        Programming the CRT Controller
               MDA � CGA � Hercules Adapters � EGA � MCGA � VGA

                            Basic CRTC Computations
                Dot Clock � Horizontal Timing � Vertical Timing

                            The CRT Status Register

                                  Video Modes
                Resolution � Colors � Video Buffer Organization

                          Hardware Video Mode Control
                           MDA � CGA and MCGA � HGC
                      HGC+ and InColor Card � EGA and VGA
                              Video BIOS Support

                       Combinations of Video Subsystems
                    MDA � Hercules � CGA � EGA � MCGA � VGA



     This chapter describes IBM PC and PS/2 video hardware from a
     programmer's point of view. It covers the basics: which parts of the
     computer's video subsystem can be programmed, how a program interacts
     with the hardware, and how calculations for changing the video display
     format are performed. Many of the programming techniques in later
     chapters are based on the fundamental information discussed here.

     The purpose of this chapter is to demystify the hardware programming
     interface. Because most programmers rely on the video BIOS to perform
     most, if not all, hardware-level programming in their applications, an
     aura of mystery surrounds the way software interacts with video
     hardware. Of course, after you learn about it, you may wish it had
     remained a mystery--but the more you know, the more your programs will
     be able to do with the video hardware.


Functional Components of IBM PC and PS/2 Video Subsystems


     As you write programs that interact with IBM video hardware, it helps
     to visualize the relationships among the programmable components of
     IBM video subsystems (see Figure 2-1). You do not need a circuit
     designer's understanding of the hardware to write a good video
     interface. You do need to know where and how your program can interact
     with the hardware to produce video output efficiently.


     �������������Ŀ                   �������������Ŀ
     �             �  Attributes       �  Attribute  �
     �    Video    ��������������������   decoder   �
     �    Buffer   �                   �             �
     �             �                   ���������������
     �             �                          �Color, intensity, etc.
     ���������������                          
            �Character codes           �������������Ŀ
                                      �             �
     �������������Ŀ                   �    Video    �Video drive signals
     � Alphanumeric�                   �   signal    ���������������������
     �  character  ��������������������  generator  �(to video display)
     �  generator  �                   �             �
     ���������������                   ���������������
                                             
            �Horizontal & vertical timing   �  �Internal timings,
            ���������������������������������  �buffer addressing, etc.
     ������������Ŀ                   ������������Ŀ
     �     CRT     �                   �    Mode     �
     � Controller  �                   �   control   �
     ���������������                   ���������������

     Figure 2-1.  Programmable components (video buffer, attribute
     controller, and so on) of the IBM PC and PS/2 video subsystems. Some
     or all of these components are under software control in each of the
     video subsystems described in this book.


Monitor

     The most tangible part of a computer's video hardware is the monitor,
     or video display. However, there's nothing you can directly program in
     the monitor's hardware. It is the computer's video subsystem that
     contains programmable hardware. The signals generated by the video
     subsystem control what appears on the screen.

     The monitor differs from a home television receiver in that a group of
     separate timing and color signals drives it. In contrast, a home TV
     decodes a single "composite" signal that contains timing, color, and
     audio information. Although some IBM PC video adapters can generate
     such composite video output signals, as well as the direct drive
     signals that computer monitors use, most people avoid using a home
     television with their computers. Both text and colors appear sharper
     on a computer monitor than they do on a composite television screen.

     All the video monitors discussed in this book are raster-scan devices.
     The image on the screen of a monitor is made up of a group of closely
     spaced horizontal lines called the raster. An electron beam scans each
     successive line from left to right, starting at the upper left corner
     of the display. As the beam sweeps each line, the color and brightness
     of each of several hundred points (pixels) in the line are varied, and
     the entire raster appears as a coherent image.

     Conceptually, you can regard the electron beam as having "color" and
     "intensity," but in color video monitors the beam actually comprises
     three separate electron beams. Each beam controls the display of one
     of the three primary video colors (red, green, and blue) on the
     screen. Each pixel on a color display is physically represented by a
     small, closely spaced triad of red, green, and blue luminescent dots
     or stripes of phosphor. The three electron beams are masked in such a
     way that each illuminates dots of only one primary color. Thus, the
     relative intensity of the beams as they sweep over each triad
     determines the color and brightness of the pixels. Of course, unless
     you use a magnifying glass or look closely at the display, you do not
     perceive the red, green, and blue dots individually, but rather as
     blended colors.


Video Buffer

     The video buffer is a block of RAM in the video subsystem where
     displayable data is stored. This RAM lies within the address space of
     the computer's CPU, so a program may read from and write to the video
     buffer in the same way it accesses any other portion of RAM.

     The video subsystem's display circuitry updates, or refreshes, the
     screen by continually and repeatedly reading the data in the video
     buffer. Each bit or group of bits in the video buffer specifies the
     color and brightness of a particular location on the screen. The
     screen is refreshed between 50 and 70 times a second, depending on
     which video subsystem is in use. Obviously, when a program changes the
     displayed contents of the video buffer, the screen changes almost
     immediately.

     The actual amount of RAM available as a video buffer varies with the
     video subsystem. Most IBM video subsystems incorporate video buffers
     large enough to hold more than one screen of displayable data, so only
     part of the buffer is visible on the screen at any time. (Chapter 3
     discusses how to make full use of available video RAM.)


Color and Character Display Hardware

     All IBM video subsystems incorporate hardware that reads and decodes
     the data in the video buffer. For example, an alphanumeric character
     generator translates ASCII codes from the video buffer into the dot
     patterns that make up characters on the screen. An attribute decoder
     translates other data in the video buffer into the signals that
     produce colors, underlining, and so forth. Software can control these
     and other specialized components of the video subsystem; later
     chapters describe such programming in detail.


CRT Controller

     The CRT Controller (or CRTC for short) generates horizontal and
     vertical timing signals. It also increments a video buffer address
     counter at a rate that is synchronized with the timing signals. The
     video display circuitry reads data from the video buffer using the
     CRTC's address value, decodes the data, and sends the resulting color
     and brightness signals to the monitor along with the CRTC's timing
     signals. In this way the CRTC synchronizes the display of data from
     the video buffer with the timing signals that drive the video display.

     The CRTC performs several other miscellaneous functions. Among them
     are determining the size and displayed position of the hardware
     cursor, selecting the portion of the video buffer to be displayed,
     locating the hardware underline, and detecting light pen signals.
     (Chapter 3 contains examples of CRTC programming for some of these
     functions.)

     On the MDA, CGA, and Hercules cards, the CRTC is a single chip, the
     Motorola 6845. On the EGA, the CRTC is a custom LSI (large-scale
     integration) chip designed by IBM. On the MCGA, the CRTC is part of
     its Memory Controller Gate Array. The VGA's CRTC is one component of
     the single-chip Video Graphics Array. Regardless of the hardware
     implementation, the CRTC can be programmed to generate a variety of
     timing parameters in all these subsystems. Before delving into the
     techniques of CRTC programming, however, it is worthwhile to review
     how the CRTC's timing signals control the monitor's display of a
     raster-scan video image.


The Display Refresh Cycle


     The video image is refreshed in a cyclic manner between 50 and 70
     times a second, depending on the configuration of the video subsystem.
     During each refresh cycle, the electron beam sweeps across the screen
     in a zigzag fashion, starting at the left side of the topmost
     horizontal line in the raster (see Figure 2-2). After scanning a line
     from left to right, the beam is deflected down to the start of the
     next line until the entire raster is scanned. Then the beam returns to
     the upper left corner of the display, and the cycle repeats.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 2-2 is found on page 17        �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 2-2.  The path followed by the electron beam in a raster scan.


Horizontal Timing

     A number of carefully timed events occur as the beam moves across the
     display. At the beginning of each line, the electron beam is turned on
     in response to a Display Enable signal that the CRTC generates. As the
     beam sweeps left to right across the line, the video display circuitry
     uses the CRTC's address counter to read a sequence of bytes from the
     video buffer. The data is decoded and used to control the color and
     brightness signals sent to the monitor. As the beam sweeps across the
     screen, its color and brightness vary in response to these signals.

     Near the screen's right edge, the CRTC turns off the Display Enable
     signal and no further data is displayed from the video buffer. The
     CRTC then generates a horizontal sync signal, which causes the monitor
     to deflect the electron beam leftward and downward to the start of the
     next horizontal line in the raster. Then the CRTC turns the Display
     Enable signal back on to display the next line of data.

     The short period of time between the end of one line of video data and
     the beginning of the next is called the horizontal blanking interval.
     Because the horizontal retrace interval (the amount of time required
     to deflect the beam to the start of the next line) is shorter than the
     horizontal blanking interval, a certain amount of horizontal overscan
     is generated on both ends of each line (see Figure 2-3).


                ����������������������������������Ŀ
                �        � Vertical overscan        �
                �   ��������������������������Ŀ   �
                �   �                           �   �
                �   �                           �   �
                �   �                           �   �
      Horizontal�   �                           �   � Horizontal
      overscan  ���                           ��� overscan
                �   �                           �   �
                �   �                           �   �
                �   �                           �   �
                �   �                           �   �
                �   �                           �   �
                �   ����������������������������   �
                �        � Vertical overscan        �
                ������������������������������������

     Figure 2-3.  Overscan.


     During periods of horizontal overscan, the electron beam can be left
     on, displaying an overscan, or border, color. However, the primary
     reason horizontal overscan is designed into a video subsystem is to
     provide a margin of error in centering the raster, so that no data is
     lost at the edges of the screen.


Vertical Timing

     Once the electron beam has scanned all horizontal lines in the raster,
     the Display Enable signal is turned off. The CRTC then generates a
     vertical sync signal, which tells the monitor to deflect the electron
     beam from the bottom of the screen back to the upper left corner. The
     vertical retrace interval (during which the beam travels from the
     bottom to the top of the screen) is shorter than the vertical blanking
     interval (during which no data from the video buffer is displayed), so
     there are areas of vertical overscan at the top and bottom of the
     raster (see Figure 2-3). Like horizontal overscan, vertical overscan
     provides a border as well as a safety margin so that the raster can be
     centered on the screen.


Programming the CRT Controller


     The CRTC programming interface is well defined and easy to use. The
     same general programming approach applies to all IBM PC and PS/2 video
     subsystems.


MDA

     The Monochrome Display Adapter's CRTC, the Motorola 6845, has nineteen
     8-bit internal data registers. The contents of each register control
     various characteristics of the timing signals generated by the 6845
     (see Figure 2-4). One of these registers is an address register; its
     contents indicate which of the other 18 can be accessed. Most of the
     registers are write-only, but registers 0EH and 0FH, which control the
     position of the hardware cursor, may be read as well as written. On
     the MDA, the 6845's Address register is mapped to an I/O port at 3B4H,
     and the remaining 18 registers are all mapped to the next I/O port
     at 3B5H.

     To access the 6845's data registers, you first write the register
     number to the 6845's Address register (I/O port 3B4H). Then you access
     the specified data register with an I/O write or read at port 3B5H.
     For example, Listing 2-1 shows how to determine the current cursor
     location by reading the contents of registers 0EH and 0FH on the 6845.
     These two registers (Cursor Location High and Cursor Location Low)
     contain the high-order and low-order bytes of the cursor location
     relative to the start of the video buffer.


����������������������������������������������������������������������������ķ
Register  Name                          Read/Write Access
��������������������������������������������������������������������������
00H       Horizontal Total              Write only
01H       Horizontal Displayed          Write only
02H       Horizontal Sync Position      Write only
03H       Horizontal Sync Pulse Width   Write only
04H       Vertical Total                Write only
05H       Vertical Total Adjust         Write only
06H       Vertical Displayed            Write only
07H       Vertical Sync Position        Write only
08H       Interlace Mode                Write only
09H       Maximum Scan Line             Write only
0AH       Cursor Start                  Write only
Register  Name                          Read/Write Access
0AH       Cursor Start                  Write only
0BH       Cursor End                    Write only
0CH       Start Address High            Write only
0DH       Start Address Low             Write only
0EH       Cursor Location High          Read/Write
0FH       Cursor Location Low           Read/Write
10H       Light Pen High                Read only
11H       Light Pen Low                 Read only

     Figure 2-4.  Motorola 6845 CRTC data registers (for the MDA, CGA, and
     Hercules video adapters).


���������������������������������������������������������������������������

     Listing 2-1.  Reading the 6845 Cursor Location registers.

���������������������������������������������������������������������������


     With the MDA, there is rarely any reason to change the values in any
     of the 6845 registers except 0AH and 0BH (Cursor Start and Cursor End)
     and 0EH and 0FH (Cursor Location High and Low). Registers 00H through
     09H control the horizontal and vertical timing signals, which should
     not be changed. Registers 0CH and 0DH (Start Address High and Start
     Address Low), which indicate what part of the MDA's video buffer is
     displayed, should always be set to 0.


CGA

     The Color Graphics Adapter's CRTC is a Motorola 6845, as is the MDA's.
     The same programming technique used to access the CRTC on the MDA also
     works on the CGA. On the CGA, however, the CRTC Address register is
     mapped to I/O port 3D4H and the data registers are accessed at 3D5H.
     If you write a program that can run on either an MDA or a CGA, you can
     take advantage of the fact that the video BIOS routines in both the PC
     and PS/2 families maintain the value of the CRTC's Address register
     I/O port in a variable. Many of the programming examples in this book
     reference this variable, ADDR_6845, which is located at 0040:0063 in
     the BIOS Video Display Data Area.


Hercules Adapters

     Like the MDA and CGA, the Hercules Graphics Card, Graphics Card Plus,
     and InColor Card all use a Motorola 6845 as a CRTC. The CRTC registers
     are mapped at I/O ports 3B4H and 3B5H on all Hercules adapters.
     Although it is a color adapter, the InColor Card uses the MDA's I/O
     port and video buffer addresses in order to preserve compatibility
     with the MDA and with Hercules monochrome adapters.

       ���ͻ     On all Hercules video adapters (as well as the EGA, MCGA,
       � T �     and VGA), you can set both the address and data registers
       � I �     of the CRTC with one 16-bit port write (OUT DX,AX) instead
       � P �     of two 8-bit port writes  (OUT DX,AL). For example, the
       ���ͼ     two sequences of code that follow do the same thing to the
                 CRTC.

mov     dx,3B4h         ; CRTC address register
mov     al,0Ch          ; CRTC register number
out     dx,al           ; select this register
inc     dx              ; DX := 3B5h (CRTC data register)
mov     al,8            ; data
out     dx,al           ; store data in register
dec     dx

     and

mov     dx,3B4h         ; CRTC address register
mov     ax,080Ch        ; AL := reg number, AH := data
out     dx,ax           ; store data in register


EGA

     The Enhanced Graphics Adapter's CRTC is a proprietary LSI chip with a
     set of registers different from those in the 6845 (see Figure 2-5).
     The programming interface is similar to the 6845's, but the register
     assignments and formats are different enough that programs that write
     directly to CRTC registers on the MDA or CGA will probably crash on an
     EGA.

     The EGA's CRTC supports a wider set of control functions than does the
     6845. For example, the CRTC can cause a hardware interrupt at the
     start of a vertical blanking interval. The CRTC also supports the
     simultaneous display of two noncontiguous portions of the video
     buffer. (Chapter 12 describes these CRTC capabilities.)

     A curious feature of the EGA's CRTC is its Overflow register (07H).
     Because the EGA can display a raster of more than 256 lines, the CRTC
     registers that contain a number of scan lines must be 9 bits wide
     instead of 8. The high-order bit in each of these registers is stored
     in the Overflow register.


����������������������������������������������������������������������������ķ
Register  Name                          EGA Read/Write Access
��������������������������������������������������������������������������
00H       Horizontal Total              Write only
01H       Horizontal Display Enable End Write only
02H       Start Horizontal Blanking     Write only
03H       End Horizontal Blanking       Write only
04H       Start Horizontal Retrace      Write only
Register  Name                          EGA Read/Write Access
04H       Start Horizontal Retrace      Write only
05H       End Horizontal Retrace        Write only
06H       Vertical Total                Write only
07H       Overflow                      Write only
08H       Preset Row Scan               Write only
09H       Maximum Scan Line Address     Write only
0AH       Cursor Start                  Write only
0BH       Cursor End                    Write only
0CH       Start Address High            Read/Write
0DH       Start Address Low             Read/Write
0EH       Cursor Location High          Read/Write
0FH       Cursor Location Low           Read/Write
10H       Vertical Retrace Start        Write only
10H       Light Pen High                Read only
11H       Vertical Retrace End          Write only
11H       Light Pen Low                 Read only
12H       Vertical Display Enable End   Write only
13H       Offset (Logical Line Width)   Write only
14H       Underline Location            Write only
15H       Start Vertical Blanking       Write only
Register  Name                          EGA Read/Write Access
15H       Start Vertical Blanking       Write only
16H       End Vertical Blanking         Write only
17H       Mode Control                  Write only
18H       Line Compare                  Write only

     Figure 2-5.  EGA and VGA CRT Controller data
     registers.


MCGA

     In the MCGA, the functions of a CRTC are integrated into a circuit
     component called the Memory Controller Gate Array. The first 16 Memory
     Controller registers are analogous to those in the 6845 (see Figure 2-
     6). As on the CGA, all MCGA Memory Controller registers, including
     the CRTC registers, are indexed through an address register at I/O
     port 3D4H. The data registers themselves may be accessed at port
     3D5H.

     Several features of the MCGA's CRTC distinguish it from the CGA's
     6845. All of the Memory Controller registers can be read as well as
     written. Moreover, registers 00H through 07H may be designated read-
     only so that horizontal and vertical timing parameters are not
     inadvertently disrupted. Setting bit 7 of the Memory Controller Mode
     Control register (10H) to 1 protects registers 00H through 07H.

     Another feature of the MCGA CRTC is that the hardware can compute the
     horizontal timing parameters for each of the available video modes.
     When bit 3 of the Mode Control register is set to 1, and when the
     values in registers 00H through 03H represent appropriate horizontal
     timing values for 40-by-25 alphanumeric mode (video BIOS mode 0), the
     Memory Controller generates proper horizontal timing signals in all
     available video modes.

     If you compare the MCGA CRTC and the Motorola 6845 register by
     register, you will note several discrepancies in the interpretation of
     the values stored in some CRTC registers. In particular, the values
     expected in registers 09H, 0AH, and 0BH are specified in units of two
     scan lines on the MCGA, instead of one scan line on the 6845. Because
     the default alphanumeric character matrix on the MCGA is 16 scan lines
     high, this feature provides a certain amount of low-level
     compatibility, letting you use the same values for these registers as
     you would on a CGA.


����������������������������������������������������������������������������ķ
Register  Name                               Read/Write Access
��������������������������������������������������������������������������
00H       Horizontal Total                   Read/Write
01H       Horizontal Displayed               Read/Write
02H       Start Horizontal Sync              Read/Write
03H       Sync Pulse Width                   Read/Write
04H       Vertical Total                     Read/Write
05H       Vertical Total Adjust              Read/Write
06H       Vertical Displayed                 Read/Write
07H       Start Vertical Sync                Read/Write
08H       (reserved)
09H       Scan Lines per Character           Read/Write
0AH       Cursor Start                       Read/Write
0BH       Cursor End                         Read/Write
0CH       Start Address High                 Read/Write
0DH       Start Address Low                  Read/Write
Register  Name                               Read/Write Access
0DH       Start Address Low                  Read/Write
0EH       Cursor Location High               Read/Write
0FH       Cursor Location Low                Read/Write
10H       Mode Control                       Read/Write
11H       Interrupt Control                  Read/Write
12H       Character Generator, Sync Polarity Read/Write
13H       Character Generator Pointer        Read/Write
14H       Character Generator Count          Read/Write
20-3FH    (reserved)

     Figure 2-6.  MCGA Memory Controller data registers. Registers 00H
     through 0FH are comparable to those in the CGA's CRT Controller.


VGA

     Functionally, the VGA's CRTC registers (see Figure 2-5) comprise a
     superset of those in the EGA's CRTC. The VGA's CRTC register set is
     addressable at the same I/O ports as the EGA's. A few more bit fields
     have been added to the register set, primarily so that the CRTC can
     handle 400-line and 480-line rasters. However, unlike the EGA's CRTC,
     the VGA's CRTC does not support the use of a light pen.

     More important, however, all the EGA's CRTC register specifications
     have been carried over to the VGA. Thus, programs that write to the
     EGA's CRTC registers can be run unchanged on VGA-based hardware.

     As on the MCGA, the VGA's CRTC data registers can all be read as well
     as written. Also, the VGA horizontal and vertical timing registers
     (CRTC registers 00H through 07H) can be write-protected by setting bit
     7 of the Vertical Retrace End register (11H) to 1.

       ���ͻ     As on Hercules adapters, you can program the CRTC on the
       � T �     EGA, MCGA, and VGA using a 16-bit port write (OUT DX,AX).
       � I �     Moreover, you will find by experimenting that 16-bit port
       � P �     writes work on many non-IBM video adapters. But stay away
       ���ͼ     from this technique on MDAs, CGAs, and clones if
                 portability is important.


Basic CRTC Computations


     To use the CRTC effectively, you must be able to perform the basic
     computations necessary to specify the CRTC's timings correctly. These
     computations are based on three constraints: the bandwidth of the
     video signal sent to the monitor and the monitor's horizontal and
     vertical synchronization rates.


Dot Clock

     IBM PC video subsystems display pixels at a rate determined by the
     hardware. This rate is variously known as the video bandwidth, the dot
     rate, or the pixel rate; the oscillator that generates this rate is
     called the dot clock. The MDA, CGA, and Hercules adapter use only one
     dot clock; on the EGA and VGA, more than one dot clock is available
     (see Figure 2-7). The higher the dot clock frequency, the better the
     displayed pixel resolution.

     Given the dot rate, the CRTC must be programmed so that the horizontal
     and vertical scan frequencies sent to the video display are limited to
     frequencies the display can handle. Older displays, such as the IBM
     Monochrome Display, are designed to handle only one horizontal and one
     vertical scan rate. Newer displays, such as the NEC MultiSync, can
     synchronize with a range of horizontal and/or vertical scan rates.


����������������������������������������������������������������������������ķ
                       Video Bandwidth     Horizontal Scan   Vertical Scan
IBM Subsystem          (Dot Rate) in MHz   Rate in KHz       Rate in Hz
���������������������������������������������������������������������������
MDA, HGC
720x350 mono           16.257              18.43             50

CGA
640x200 color          14.318              15.75             60

EGA
640x350 color          16.257              21.85             60
640x200 color          14.318              15.75             60
720x350 mono           16.257              18.43             50

                       Video Bandwidth     Horizontal Scan   Vertical Scan
IBM Subsystem          (Dot Rate) in MHz   Rate in KHz       Rate in Hz

InColor
720x350 color          19.000              21.80             60

MCGA
640x400 mono/color     25.175              31.50             70
640x480 mono/color     25.175              31.50             60

VGA
640x400 mono/color     25.175              31.50             70
720x400 mono/color     28.322              31.50             70
640x480 mono/color     25.175              31.50             60
640x350 mono/color     25.175              31.50             70

     Figure 2-7.  Basic timings for IBM video subsystems.


Horizontal Timing

     Consider how you would calculate the typical CRTC register values
     shown in Figure 2-8 for an MDA with an IBM Monochrome Display. The
     MDA's video bandwidth (dot rate) is 16.257 MHz; that is, 16,257,000
     dots per second. The monochrome display's horizontal scan rate is
     18.432 KHz (18,432 lines per second). Dividing the dot rate by the
     horizontal scan rate gives 882 dots per line. Each character displayed
     by the MDA is 9 dots wide, so the total number of characters in each
     line is 882 / 9, or 98.

     This value is used to program the CRTC's Horizontal Total register.
     For the MDA's CRTC, a Motorola 6845, the value you store in the
     Horizontal Total register must be 1 less than the computed total, or
     97 (61H).


����������������������������������������������������������������������������ķ
Register  Name                      Parameter       Description
��������������������������������������������������������������������������
00H       Horizontal Total          97 (61H)        (total characters per
                                                    scan line) - 1

Register  Name                      Parameter       Description

01H       Horizontal Displayed      80 (50H)        Characters displayed in
                                                    each scan line

02H       Horizontal Sync Position  82 (52H)        Position in scan line
                                                    where horizontal
                                                    retrace starts

03H       Horizontal Sync Width     15 (0FH)        Duration of horizontal
                                                    retrace interval
                                                    (character clocks)

04H       Vertical Total            25 (19H)        Total character rows in
                                                    one frame

05H       Vertical Total Adjust     2               Remaining scan lines in
                                                    one frame

06H       Vertical Displayed        25 (19H)        Character rows dis-
                                                    played in each frame
Register  Name                      Parameter       Description
                                                    played in each frame

07H       Vertical Sync Position    25 (19H)        Position in frame where
                                                    vertical retrace
                                                    starts

08H       Interlace Mode            2               Always set to 2

09H       Maximum Scan Line         13 (0DH)        (height of one charac-
                                                    ter in scan lines) - 1

     Figure 2-8.  Typical CRTC parameters for the Monochrome Display
     Adapter.


     In terms of CRTC timings, the Horizontal Total value describes the
     amount of time, in "character clocks," required to complete one
     horizontal scan. During this period, 80 characters are actually
     displayed. (This is the value used for the Horizontal Displayed
     register.) The other 18 character clocks are spent in horizontal
     overscan and in horizontal retrace.

     The duration of the horizontal retrace interval is about 10 to 15
     percent of the Horizontal Total value. The exact value depends on the
     video subsystem. On the MDA, the horizontal retrace interval is set at
     15 character clocks by storing this value in the CRTC Horizontal Sync
     Width register. This leaves 3 character clocks of horizontal overscan.
     The horizontal retrace signal is programmed to start 2 character
     clocks after the rightmost displayed character by storing the value 82
     (52H) in the CRTC Horizontal Sync Position register. Thus, there are 2
     character clocks of right horizontal overscan and 1 character clock of
     left overscan.

       ���ͻ     Changing the value in the Horizontal Sync Position
       � T �     register changes the size of the right and left overscan
       � I �     areas and thus the horizontal position of the displayed
       � P �     raster. For example, to shift the displayed raster to the
       ���ͼ     left, increase the size of the right overscan interval by
                 increasing the value in the CRTC Horizontal Sync Position
                 register.


Vertical Timing

     Similar considerations apply in programming the CRTC to generate
     appropriate vertical timings. The nominal horizontal scan rate in the
     MDA's monochrome display is 18.432 KHz (18,432 lines per second) with
     a vertical scan rate of 50 Hz (50 frames per second), so the number of
     lines in one frame is 18,432 / 50, or 368. Since each character
     displayed is 14 lines high, 25 rows of characters account for 350
     lines. The MDA's CRTC always uses 16 lines for vertical retrace; this
     leaves 368 - (350 + 16), or 2 lines of vertical overscan.

     The CRTC programming follows these calculations. The height of each
     displayed character is specified by the value in the CRTC Maximum Scan
     Line register. Since characters are 14 scan lines high, the maximum
     scan line value is 13 (0DH). Taken together, the values for Vertical
     Total (25 character rows) and Vertical Total Adjust (2 scan lines)
     indicate the total number of scan lines in one frame. The number of
     character rows displayed (25) is indicated in the Vertical Displayed
     register. The position in the frame where vertical retrace starts (25)
     is specified by the value in the Vertical Sync Position register.

     The CRTCs on the MCGA, EGA, and VGA are more complex than the Motorola
     6845 CRTC on the MDA and CGA. Nevertheless, the registers that control
     horizontal and vertical timings in the newer video subsystems are
     similar in nomenclature and functionality to the 6845's registers. The
     computations for the MCGA, EGA, and VGA CRTCs are derived from the dot
     rate, the character size, and the horizontal and vertical capabilities
     of the video display, just as they are for the MDA and CGA.


The CRT Status Register


     All IBM video subsystems have a read-only CRT Status register. This
     register is located at I/O port 3BAH on the MDA and Hercules adapters
     and at 3DAH on the CGA and MCGA; on the EGA and VGA, this register is
     at 3BAH in monochrome configurations and at 3DAH in color
     configurations. Generally, two of the eight bits in this register
     reflect the current status of the horizontal and vertical timing
     signals generated by the CRTC. These status bits can be used to
     synchronize video buffer updates with the screen refresh cycle to
     minimize interference with the displayed image. (Chapter 3 contains
     examples of this type of programming.)

     Unfortunately, the exact interpretation of the status bits in the CRT
     Status register varies among the different IBM video subsystems (see
     Figure 2-9). Therefore, programs should be designed to determine
     which hardware they are running on (Appendix C) before they attempt
     to use the status information in this register.

     Listing 2-2 shows how the status bits in the CRTC Status register are
     used to synchronize program operation with the video refresh cycle.
     This subroutine can be used on the CGA to time the horizontal blanking
     interval. The subroutine uses bit 3 of the CRT Status register, which
     indicates when the CRTC's vertical sync signal is active, to
     synchronize with the start of a refresh cycle. The loops at L01 and
     L02 show how this is done.

     The loops at L03 and L04 then synchronize with the Display Enable
     signal, using bit 0 of the CRT Status value. When the Display Enable
     signal goes off, the loop at L05 decrements the value in CX during the
     horizontal blanking interval, that is, while the Display Enable signal
     is off. The number of iterations counted in CX can then be used as a
     timeout value to determine when the last horizontal line in the frame
     has been scanned. (See Chapter 3.)


���������������������������������������������������������������������������

     Listing 2-2.  Timing the horizontal blanking interval on the CGA

���������������������������������������������������������������������������


Video Modes


     Despite the timing constraints imposed by the dot clock and the rated
     horizontal and vertical scan rates of available monitors, all IBM
     video subsystems except the MDA can be programmed with a variety of
     different CRTC parameters. This makes a number of video modes
     available. Each video mode is characterized by its resolution (the
     number of characters or pixels displayed horizontally and vertically),
     by the number of different colors that can be displayed
     simultaneously, and by the format of the displayable data in the video
     buffer.


Resolution

     The horizontal and vertical resolution in a video mode is a function
     of the dot rate as well as the monitor's horizontal and vertical scan
     rates. The number of pixels displayed in each frame corresponds to the
     dot rate divided by the vertical scan rate. The actual horizontal and
     vertical resolution then depends on the horizontal scan rate.


�����������������������������������������������������������������������������
                    Register    Bit 7               Bit 3               Bit 2
�����������������������������������������������������������������������������
MDA                 3BA                             Video drive

HGC, HGC+, InColor  3BA         0 = vertical sync   Video drive

                    Register    Bit 7               Bit 3               Bit 2

CGA                 3DA                             1 = vertical sync   1 = l
                                                                            s
EGA                 3BA or 3DA                      1 = vertical sync   1 = l
                                                                            s
VGA                 3BA or 3DA                      1 = vertical sync

MCGA                3DA                             1 = vertical sync1

     Figure 2-9.  CRTC Status register bit assignments


Colors

     The number and variety of colors that can be displayed in a video mode
     depend on the design of the video subsystem's attribute decoding and
     video signal generator components. The attribute decoder uses data
     stored in the video buffer to control the color and brightness signals
     produced by the video signal generator. Establishing a particular
     video mode always involves programming a video subsystem's attribute
     decoder in addition to updating its CRTC parameters.


Video Buffer Organization

     The format of the data in video RAM also characterizes a video mode.
     In all PC and PS/2 subsystems, video modes can be classified as
     alphanumeric or graphics modes, depending on the video buffer data
     format. In alphanumeric modes, the data in the video buffer is
     formatted as a sequence of ASCII code and attribute byte pairs; the
     alphanumeric character generator translates the ASCII codes into
     displayed characters while the attribute bytes specify the colors used
     to display them (see Chapter 3). In graphics modes, the video buffer
     is organized as a sequence of bit fields; the bits in each field
     designate the color of a particular pixel on the screen.


Hardware Video Mode Control


     Establishing a video mode on an IBM PC or PS/2 video subsystem
     generally requires specific mode control programming apart from
     specifying CRTC parameters. For example, the alphanumeric character
     generator must be enabled in alphanumeric modes and disabled in
     graphics modes. Also, the subsystem's internal character clock, which
     determines the number of pixels generated for each alphanumeric
     character code read from the video buffer, may run at different rates
     in different video modes. These and other internal functions are
     controlled by loading one or more specialized mode control registers
     with values appropriate for each video mode.


MDA

     The MDA's Mode Control register is a write-only register mapped to
     port 3B8H (see Figure 2-10). Only three of the eight bits in this
     register have meaning. Bit 0 is set to 1 at powerup and must always
     remain set to 1. Bit 3, when set to 1, enables video refresh; clearing
     this bit blanks the screen. Bit 5 is the Enable Blink bit; it controls
     whether characters can blink. On the MDA, most programs leave bit 3
     set at all times. Chapter 3 explains how to use bit 5 (the Enable
     Blink bit).


����������������������������������������������������������������������������ķ
Bit         Settings
��������������������������������������������������������������������������
0           1 = adapter enabled (should always = 1)
1           (unused, should always = 0)
2           (unused, should always = 0)
3           1 = video enabled
            0 = video disabled (screen blank)
4           (unused, should always = 0)
5           1 = blinking attribute enabled
            0 = blinking attribute disabled
6           (unused, should always = 0)
7           (unused, should always = 0)

     Figure 2-10.  Bit settings for the MDA Mode Control register (3B8H).


CGA and MCGA

     The Mode Control register on the CGA and MCGA is found at 3D8H (see
     Figure 2-11a). The five low-order bits control internal timings
     appropriate for the video modes they select, while bit 5 is an Enable
     Blink bit just as it is on the MDA. The useful bit patterns for the
     CGA's Mode Control register are listed in Figure 2-11b. These values
     correspond to the available BIOS video modes on the CGA.

     The Mode Control registers on the CGA and the MCGA have two
     differences. One is that the MCGA Mode Control register may be read as
     well as written; the CGA register is write-only. The other difference
     relates to the function of bit 2. On the CGA, setting bit 2 to 1
     disables the color burst component of the composite video output
     signal. This can improve the quality of the display if you are using a
     composite green or amber monitor with a CGA. On the MCGA, which does
     not support a composite monitor, the function of bit 2 of the Mode
     Control register is to select between two sources for the foreground
     color in 2-color graphics modes.


����������������������������������������������������������������������������ķ
Bit         Settings
Bit         Settings
��������������������������������������������������������������������������
0           1 = 80-character alphanumeric modes
            0 = 40-character alphanumeric modes
1           1 = 320-wide graphics mode
            0 = (all other modes)
2           1 = color burst disabled (CGA only)
            1 = foreground color from video DAC register 7 (MCGA only)
            0 = color burst enabled (CGA only)
            0 = foreground color from the video DAC register specified
                in bits 0-3 of the Palette register (3D9H) (MCGA only)
3           1 = video enabled
            0 = video disabled (screen blank)
4           1 = 640-wide graphics modes
            0 = (all other modes)
5           1 = blinking attribute enabled
            0 = blinking attribute disabled
6           (unused, should always = 0)
7           (unused, should always = 0)

     Figure 2-11a.  Bit settings for the CGA and MCGA Mode Control register
     (3D8H).


����������������������������������������������������������������������������ķ
BIOS Mode                                Value for Mode
Number      Description                  Control Register
��������������������������������������������������������������������������
0           40x25 alpha                  00101100b (2CH)
              (color burst disabled)
1           40x25 alpha                  00101000b (28H)
2           80x25 alpha                  00101101b (2DH)
              (color burst disabled)
3           80x25 alpha                  00101001b (29H)
4           320x200 graphics             00101010b (2AH)
5           320x200 graphics             00101110b (2EH)
              (color burst disabled)
6           640x200 graphics             00011100b (1CH)
7           80x25 alpha                  00101001b (29H)
              (MDA only)
11H         640x480 graphics             00011000b (18H)
              (MCGA only)
BIOS Mode                                Value for Mode
Number      Description                  Control Register
              (MCGA only)

     Figure 2-11b.  MDA, CGA, and MCGA Mode Control register options.


     The MCGA has two additional mode control registers, which are not
     implemented on the CGA. The MCGA Memory Controller Mode Control
     register (10H) at port 3D4H/3D5H selects 640-by-480 2-color and 320-
     by-200 256-color graphics modes (see Figure 2-12). An Extended Mode
     Control register is mapped to I/O port 3DDH. This register is used
     only during machine coldstart; it has no practical use in applications
     programs.


����������������������������������������������������������������������������ķ
Bit         Settings
��������������������������������������������������������������������������
0           1 = select 320x200 256-color mode
            0 = (all other modes)
Bit         Settings
            0 = (all other modes)
1           1 = select 640x480 2-color mode
            0 = (all other modes)
2           (reserved)
3           1 = horizontal timing parameters computed for video mode
            0 = horizontal timing parameters as specified in registers
                00-03H
4           1 = enable dot clock (should always be 1)
5           (reserved)
6           Inverse of bit 8 of Vertical Displayed register (06H)
7           1 = write-protect registers 00-07H
            0 = allow updating of registers 00-07H

     Figure 2-12.  Bit settings for the MCGA Memory Controller Mode Control
     register.


HGC

     The Hercules Graphics Card has two control registers whose contents
     affect the video mode configuration. The Mode Control register at 3B8H
     is functionally compatible with the MDA's Mode Control register, but
     it maps additional mode configuration functions to bits 1 and 7 (see
     Figure 2-13). Bit 1, when set to 1, establishes internal timings for
     a 720-by-348 graphics mode. Setting bit 7 to 1 while the adapter is in
     graphics mode displays the second half of the adapter's 64 KB video
     buffer at B800:0000. These bits have no function, however, unless the
     appropriate bits in the adapter's Configuration Switch register are
     set properly.

     The Configuration Switch register (3BFH) determines the function of
     the Mode Control register at 3B8H (see Figure 2-14). When bit 0 of
     the Configuration Switch register is 0, the HGC cannot be placed in
     its graphics mode, so bit 1 of the Mode Control register must also be
     0. Bit 1 of the Configuration Switch register controls video buffer
     addressing when the adapter is used in combination with a CGA or
     compatible (see below).


����������������������������������������������������������������������������ķ
Bit         Settings
Bit         Settings
��������������������������������������������������������������������������
0           (unused)
1           1 = 720x348 graphics mode
            0 = 80x25 alphanumeric mode
2           (unused, should always = 0)
3           1 = video enabled
            0 = video disabled (screen blank)
4           (unused, should always = 0)
5           1 = blinking attribute enabled
            0 = blinking attribute disabled
6           (unused, should always = 0)
7           1 = graphics mode buffer displayed from B800:0000 (video
                page 1)
            0 = graphics mode buffer displayed from B000:0000 (video
                page 0)

     Figure 2-13.  Bit settings for the Hercules Mode Control register
     (3B8H). This register is the same on the HGC, HGC+, and InColor Card.


����������������������������������������������������������������������������ķ
Bit         Settings
��������������������������������������������������������������������������
0           1 = allows graphics mode
            0 = prevents graphics mode
1           1 = enables upper 32 KB of graphics mode video buffer at
                B800:0000
            0 = disables upper 32 KB of graphics mode buffer
2-7         (unused)

     Figure 2-14.  Bit settings for the Hercules Configuration Switch
     register (3BFH). This register is the same on the HGC, HGC+, and
     InColor Card.


HGC+ and InColor Card

     The HGC+ and InColor Card implement an extended mode control register
     (called the xMode register) in addition to the Mode Control and
     Configuration Switch registers found on the HGC. The xMode register is
     a write-only register addressable as register 14H at port 3B4H/3B5H.
     (The register is addressed exactly as if it were a CRTC register.) The
     xMode register controls the alphanumeric character generator; Chapter
     10 explains this in detail.


EGA and VGA

     When you establish a video mode on the EGA and the VGA, you can
     control the internal timing and addressing of several different
     components of the video subsystem. These include the Sequencer, the
     Graphics Controller, and the Attribute Controller, each of which has
     several control registers. There is also a Miscellaneous Output
     register, which controls I/O port and video buffer addressing and
     selects the dot clock frequency.

       ���ͻ     All Sequencer, Graphics Controller, and Attribute
       � T �     Controller registers on the EGA are write-only registers,
       � I �     but on the VGA they can be read as well as written.
       � P �
       ���ͼ


     Sequencer
     The Sequencer generates internal timings for video RAM addressing. It
     has five programmable data registers (see Figure 2-15) mapped to
     ports 3C4H and 3C5H in a manner analogous to CRTC register mapping.
     The Sequencer's Address register is located at 3C4H; its five data
     registers are selected by storing an index value between 0 and 4 in
     the Address register and then accessing the corresponding data
     register at 3C5H.


����������������������������������������������������������������������������ķ
Register    Name
��������������������������������������������������������������������������
0           Reset
1           Clocking Mode
2           Map Mask
3           Character Map Select
4           Memory Mode

     Figure 2-15.  EGA and VGA Sequencer registers.


     Graphics Controller
     The Graphics Controller mediates data flow between the video buffer
     and the CPU, as well as from the video buffer to the Attribute
     Controller. The Graphics Controller has nine data registers, plus an
     Address register (see Figure 2-16). The Address register maps to
     port 3CEH, and the data registers map to port 3CFH.


����������������������������������������������������������������������������ķ
Register    Name
��������������������������������������������������������������������������
0           Set/Reset
1           Enable Set/Reset
2           Color Compare
3           Data Rotate/Function Select
4           Read Map Select
5           Graphics Mode
6           Miscellaneous
7           Color Don't Care
Register    Name
7           Color Don't Care
8           Bit Mask

     Figure 2-16.  EGA and VGA Graphics Controller registers.


     Attribute Controller
     The Attribute Controller supports a 16-color palette on the EGA and
     VGA. It also controls the color displayed during overscan intervals.
     The Attribute Controller's Address register and 21 data registers all
     map to I/O port 3C0H (see Figure 2-17). A value written to port 3C0H
     will be stored in either the Address register or a data register,
     depending on the state of a flip-flop internal to the Attribute
     Controller.


����������������������������������������������������������������������������ķ
Register(s) Function
���������������������������������������������������������������������������
0-0FH       Palette
Register(s) Function
0-0FH       Palette
10H         Attribute Mode Control
11H         Overscan Color
12H         Color Plane Enable
13H         Horizontal Pixel Panning
14H         Color Select (VGA only)

     Figure 2-17.  EGA and VGA Attribute Controller registers.


     To set the flip-flop, perform an I/O read (IN AL,DX) of the CRT Status
     register (port 3BAH in monochrome modes, 3DAH in color modes). Listing
     2-3 illustrates how this is done in updating an Attribute Controller
     register. On the VGA, Attribute Controller data registers may be read
     as well as written. Do this by writing the register number to port
     3C0H and then reading the value from port 3C1H.


���������������������������������������������������������������������������

     Listing 2-3.  Updating the EGA or VGA Attribute Controller

���������������������������������������������������������������������������


       ���ͻ     You can use 16-bit port writes (OUT DX,AX) to store data
       � T �     in EGA and VGA Sequencer and Graphics Controller
       � I �     registers. On the EGA, you can use the same technique to
       � P �     program the Attribute Controller, which recognizes I/O
       ���ͼ     port writes at 3C1H as well as 3C0H. However, the VGA
                 Attribute Controller does not emulate the EGA in this
                 regard, so this technique should be used carefully when
                 VGA compatibility is important.


Video BIOS Support

     The video BIOS supports a number of different video modes on IBM PC
     and PS/2 video subsystems (see Figure 2-18). The video BIOS routines,
     which can be called with INT 10H, let you establish a video mode
     simply by specifying its number.

     Not all of the BIOS video modes are available on all IBM PC video
     subsystems. Furthermore, the video BIOS does not support video mode
     configurations on non-IBM hardware unless it exactly emulates the
     corresponding IBM hardware.

     For example, all Hercules video adapters emulate IBM's MDA exactly.
     Thus, the video BIOS can be used to select the monochrome alphanumeric
     mode (BIOS mode 7) on all Hercules products. However, the Hercules
     hardware also supports a 720-by-348 graphics mode which is not
     recognized by IBM's video BIOS. Consequently, to set up the Hercules
     graphics mode, a program must configure the hardware directly (see
     Listing 2-4.)


�����������������������������������������������������������������������������
Mode
Number                                            Mode      Buffer
(hex)  Resolution                        Colors   Type      Segment   MDA  CG
�����������������������������������������������������������������������������
0      40x25 chars (320x200 pixels)1,2  16       Alpha     B800           x
Mode
Number                                            Mode      Buffer
(hex)  Resolution                        Colors   Type      Segment   MDA  CG
0      40x25 chars (320x200 pixels)1,2  16       Alpha     B800           x
0      40x25 chars (320x350 pixels)2    16       Alpha     B800
0      40x25 chars (320x400 pixels)      16       Alpha     B800
0      40x25 chars (360x400 pixels)2    16       Alpha     B800
1      40x25 chars (320x200 pixels)2    16       Alpha     B800           x
1      40x25 chars (320x350 pixels)2    16       Alpha     B800
1      40x25 chars (320x400 pixels)      16       Alpha     B800
1      40x25 chars (360x400 pixels)2    16       Alpha     B800
2      80x25 chars (640x200 pixels)1,2  16       Alpha     B800           x
2      80x25 chars (640x350 pixels)2    16       Alpha     B800
2      80x25 chars (640x400 pixels)      16       Alpha     B800
2      80x25 chars (720x400 pixels)2    16       Alpha     B800
3      80x25 chars (640x200 pixels)2    16       Alpha     B800           x
3      80x25 chars (640x350 pixels)2    16       Alpha     B800
3      80x25 chars (640x400 pixels)      16       Alpha     B800
3      80x25 chars (720x400 pixels)2    16       Alpha     B800
4      320x200 pixels                     4       Graphics  B800           x
5      320x200 pixels3                   4       Graphics  B800           x
Mode
Number                                            Mode      Buffer
(hex)  Resolution                        Colors   Type      Segment   MDA  CG
5      320x200 pixels3                   4       Graphics  B800           x
6      640x200 pixels                     2       Graphics  B800           x
7      80x25 chars (720x350 pixels)2     2       Alpha     B000      x
7      80x25 chars (720x400 pixels)2     2       Alpha     B000
8      (PCjr only)
9      (PCjr only)
0A     (PCjr only)
0B     (used by EGA video BIOS)
0C     (used by EGA video BIOS)
0D     320x200 pixels                    16       Graphics  A000           x
0E     640x200 pixels                    16       Graphics  A000           x
0F     640x350 pixels                     2       Graphics  A000           x
10     640x350 pixels4                   4       Graphics  A000           x
10     640x350 pixels                    16       Graphics  A000           x
11     640x480 pixels                     2       Graphics  A000
12     640x480 pixels                    16       Graphics  A000
13     320x200 pixels                   256       Graphics  A000

     Figure 2-18.  ROM BIOS video modes


���������������������������������������������������������������������������

     Listing 2-4.  Configuring a Hercules adapter for 720-by-348 graphics
     mode

���������������������������������������������������������������������������


Combinations of Video Subsystems


     IBM designed the original MDA and CGA such that both adapters can be
     used in the same PC. This is possible because the CRTC registers and
     other control and status registers are assigned to a different range
     of I/O ports on the MDA than on the CGA. The MDA's port addresses
     range from 3B0H through 3BFH, while the CGA's range from 3D0H through
     3DFH. Also, the video buffers on the MDA and the CGA occupy different
     portions of the 80x86 address space: The MDA's 4 KB video buffer is at
     B000:0000, while the CGA's 16 KB buffer starts at B800:0000.

     This separation was carried forward in the design of the EGA. The
     EGA's I/O port and video buffer addressing are programmable. When the
     EGA is attached to a monochrome monitor, the MDA-compatible addresses
     are used. When the EGA is used with a color monitor, the CGA-
     compatible addresses are used. Thus, an EGA can coexist with either an
     MDA or a CGA.

     Figure 2-19 shows which PC and PS/2 video subsystems can coexist in
     the same computer. The table reflects the dichotomy between MDA-
     compatible and CGA-compatible I/O port and video buffer addressing. As
     a rule of thumb, you can usually combine one MDA-compatible adapter
     and one CGA-compatible adapter in the same system.

     NOTE: The Hercules InColor Card should be regarded as an MDA-
     compatible adapter, even though it is ostensibly a color card. In
     fact, if you use the InColor Card in a PS/2 Model 30 with a monochrome
     monitor attached to the Model 30's MCGA, you end up with the strange
     combination of an MDA-compatible color subsystem and a CGA-compatible
     monochrome subsystem in the same computer.

     The BIOS video mode routines generally support dual-display
     configurations. The video BIOS routines use bits 4 and 5 of the
     variable EQUIP_FLAG at 0040:0010 in the BIOS video data area to choose
     between two video subsystems. If there are addressing conflicts
     between two subsystems, the BIOS in the MCGA and VGA provides a
     "display switch" interface that lets you independently disable and
     enable each subsystem (see Appendix A).


����������������������������������������������������������������������������ķ
              MDA   CGA   EGA   MCGA   VGA Adapter   HGC   HGC+   InColor
��������������������������������������������������������������������������
MDA                 x     x     x      x
CGA           x           x                          x     x      x
EGA           x     x                                x     x      x
MCGA          x                        x             x     x      x
VGA Adapter   x                 x                    x     x      x
HGC                 x     x     x      x
HGC+                x     x     x      x
InColor             x     x     x      x
              MDA   CGA   EGA   MCGA   VGA Adapter   HGC   HGC+   InColor
InColor             x     x     x      x

     Figure 2-19.  Allowable combinations of IBM PC and PS/2 video
     subsystems.


     With some combinations of video adapters, the address space the two
     subsystems' video buffers occupy may overlap even if their I/O port
     address assignments do not. In this situation you must selectively
     exclude part or all of one subsystem's video buffer from the CPU
     memory map so that the CPU can access the other subsystem's buffer
     without addressing conflicts. The technique for doing this varies with
     the hardware.


MDA

     The MDA's video buffer is mapped to the addresses between B000:0000
     and B000:FFFF. The same buffer is also mapped to the 4 KB blocks of
     RAM starting at segments B100H, B200H, and so on through B700H,
     although there is no real reason for software to use these alternate
     address maps. The MDA's video buffer address mapping cannot be
     disabled.


Hercules

     On the HGC, the HGC+, and the InColor Card, the video buffer occupies
     the 64 KB of RAM starting at B000:0000. The second 32 KB of the video
     buffer overlaps the address space of a CGA's video buffer (starting at
     B800:0000). For this reason these Hercules adapters are designed so
     that the second 32 KB can be selectively excluded from the CPU memory
     map. The extent of the video buffer address space depends upon the
     value you store in the Configuration Switch register (3BFH). When bit
     1 of this register is 0 (the power-on default), video RAM occupies
     addresses from B000:0000 through B000:7FFF, which excludes the second
     32 KB portion from the CPU memory map and allows the card to be used
     with a CGA. To make the second half of the video buffer addressable,
     set bit 1 to 1.


CGA

     The CGA's video buffer maps to the addresses between B800:0000 and
     B800:3FFF. The same buffer is also mapped between BC00:0000 and
     BC00:3FFF, although few programs use this alternate address map. As
     with the MDA, the CGA's video buffer mapping cannot be altered.

     This is not the case, however, for all CGA clones. The Hercules Color
     Card (not to be confused with the InColor Card) is a CGA work-alike
     whose video buffer can be excluded from the CPU's address space. This
     is achieved by setting bit 1 of the card's Configuration Switch
     register (3BFH) to 1. This register maps to the same I/O port as the
     equivalent register on an HGC, HGC+, or InColor Card, but the polarity
     of the control bit is opposite that on the other Hercules cards. Thus,
     by toggling this bit, software can address the video buffers on both a
     Hercules Color Card and another Hercules adapter without addressing
     conflicts.


EGA

     The EGA's video buffer can be mapped to any of four locations,
     depending on the values of bits 2 and 3 in the Graphics Controller
     Miscellaneous register (see Figure 2-20). The default values for
     these bits depend on the video mode. When the video BIOS sets up a
     video mode, it sets these bits to appropriate values.


����������������������������������������������������������������������������ķ
Bit 3       Bit 2       Video Buffer Address Range
��������������������������������������������������������������������������
0           0           A000:0000-B000:FFFF
0           1           A000:0000-A000:FFFF
1           0           B000:0000-B000:7FFF
1           1           B800:0000-B800:7FFF

     Figure 2-20.  Control of EGA and VGA video buffer addressing with the
     Graphics Controller Miscellaneous register.


     The EGA also provides another level of control over the video buffer
     address map. When set to 0, bit 1 of the EGA's Miscellaneous Output
     register (3C2H) excludes the entire video buffer from the CPU memory
     address space.


MCGA

     The MCGA's 64 KB video buffer occupies the addresses between A000:0000
     and A000:FFFF, but the second 32 KB of the buffer, starting at
     A000:8000 (A800:0000), also maps to the CGA video buffer address range
     (B800:0000 through B800:7FFF). CPU addressing of the MCGA's video
     buffer and I/O ports can be disabled by setting bit 2 of the system
     board control port at 65H to 0. Listing 2-5 shows how INT 10H function
     12H can be called to set or reset this bit.


���������������������������������������������������������������������������

     Listing 2-5.  Enable or disable video I/O port and buffer addressing
     on an MCGA or VGA.

���������������������������������������������������������������������������


VGA

     Control over the VGA's video buffer address map is the same as on the
     EGA. However, there are two different methods of disabling CPU
     addressing of the video subsystem, depending on whether you are using
     an integrated VGA (in a PS/2 Model 50, 60, or 80) or the VGA Adapter.
     In the integrated subsystem, the Video Subsystem Enable Register
     (3C3H) controls both video buffer addressing and I/O port addressing;
     setting bit 0 of this register to 0 disables addressing, and setting
     bit 0 to 1 enables addressing.

     On the VGA Adapter, the Video Subsystem Enable register does not
     exist. Instead, bit 3 of the control register at I/O port 46E8H
     enables and disables addressing: Writing a default value of 0EH to
     this port enables addressing; writing a value of 6 disables
     addressing.

     In all VGA subsystems, however, INT 10H function 12H provides the same
     interface as it does on the MCGA (see Listing 2-5). Because of the
     hardware differences between the MCGA, the integrated VGA and the VGA
     Adapter, it is easier to use INT 10H function 12H to enable or disable
     addressing in the PS/2 video subsystems (see Listing 2-5).



                         3  Alphanumeric Modes


                        Using Alphanumeric Modes
                    BIOS and Operating-System Support
                          Speed � Compatibility

                   Representation of Alphanumeric Data

                               Attributes
                          MDA � HGC � CGA � EGA
                        InColor Card � MCGA � VGA

                           Gray-Scale Summing

                              Border Color
                            CGA � EGA and VGA

                            Avoiding CGA Snow
                          Blanking the Display
                  Using the Vertical Blanking Interval
                 Using the Horizontal Blanking Interval

                       Using All the Video Buffer
                             CGA Video Pages
                     EGA, MCGA, and VGA Video Pages

                             Cursor Control
                     Cursor Size on the MDA and CGA
                   Cursor Location on the MDA and CGA
            MCGA Cursor Control � EGA and VGA Cursor Control
                        ROM BIOS Cursor Emulation
                           An Invisible Cursor



     All IBM PC and PS/2 video subsystems except the MDA can be programmed
     to display characters in either alphanumeric or graphics modes. This
     chapter discusses what you need to know to use alphanumeric modes--the
     advantages and disadvantages of programming in alphanumeric modes; the
     basics of colors, blinking, and other character display attributes;
     and special techniques that exploit the capabilities of the hardware
     to improve the on-screen appearance and performance of your programs.


Using Alphanumeric Modes


     The video BIOS on all IBM PCs and PS/2s always selects an alphanumeric
     video display mode when you boot the computer. In the IBM PC family,
     switches on the motherboard, the video adapter, or both determine
     whether a 40-column or 80-column mode is selected and whether a color
     or monochrome display is used. In the PS/2 series, the initial video
     mode is always an 80-column alphanumeric mode. Furthermore, the video
     mode set by the ROM BIOS is the one the operating system initially
     uses. Until you run a program that changes the video mode, all video
     output appears in the default mode--which is alphanumeric.

     For this reason, the simplest way to write a program is to assume that
     it runs in an alphanumeric mode and to program the video interface
     accordingly. This assumption minimizes the coding required to send
     output to the screen. Not only are alphanumeric video output routines
     simpler than equivalent routines for graphics modes, but in most cases
     the ROM BIOS or the operating system provides character output
     routines that can be used in any alphanumeric mode.


BIOS and Operating-System Support

     In the IBM PC, operating-system output routines are usually based on
     the set of primitive routines in the ROM BIOS that are called with
     software interrupt 10H. You can send characters to the video display
     either by using operating-system calls or by calling the INT 10H
     routines directly. In either case, use of these routines obviates the
     need for writing your own character output routines.

     An additional advantage to using BIOS or operating-system character
     output functions is that programs using only such functions are more
     likely to run on different video hardware. For example, a program
     using only MS-DOS function calls for video output will run in almost
     any MS-DOS environment, regardless of the video hardware, including
     (but not limited to) the entire IBM PC and PS/2 family.

     Of course, routing video output through an operating system is
     relatively slow compared with writing directly to the hardware. The
     use of operating-system character output routines introduces a certain
     amount of unavoidable overhead,particularly when such features as
     input/output redirection and multiprocessing are supported. Never-
     theless, this overhead may be acceptable in many applications. You
     should always consider whether the extra programming and decreased
     portability required to improve video output performance are
     worthwhile in your application.


Speed

     This is not to say that alphanumeric video output is inherently slow.
     When compared with character output in graphics modes, alphanumeric
     output is significantly faster, simply because much less data must be
     stored in the video buffer to display characters. In alphanumeric
     modes, each character is represented by a single 16-bit word; the
     video hardware takes care of displaying the pixels that make up the
     character. In graphics modes, every pixel in every character is
     represented explicitly in a bit field in the video buffer. For this
     reason, graphics-mode output is much more costly than equivalent
     character output in alphanumeric modes, both in terms of display
     memory used and processing required.

     For example, in a 16-color graphics mode, each character drawn on the
     screen in an 8-by-8 dot matrix is represented by 32 bytes of data in
     the video buffer (8 * 8 * 4 bits per pixel). The memory overhead
     increases rapidly, in direct relationship to increasing resolution and
     the addition of more colors, as does the amount of time the CPU spends
     in manipulating data in the video buffer. On newer video adapters,
     dedicated graphics coprocessors such as the Intel 82786 or the TI
     34010 may assume much of the computational burden of graphics-mode
     text display, thereby improving the speed of graphics-mode text
     output. Without a coprocessor, however, output in graphics modes is
     much slower than in alphanumeric modes.


Compatibility

     Writing a program that is compatible with different IBM video
     subsystems is easier if you use only alphanumeric video display modes.
     The reason is simple: All commonly used IBM video subsystems support
     an 80-column by 25-row alphanumeric mode with the same video buffer
     format. If you design your video interface with an 80-by-25
     alphanumeric display in mind, your program will run on a majority of
     PCs and compatibles with little or no modification.

     Unfortunately, high compatibility is generally achieved only by
     sacrificing speed. Fast video output routines usually take advantage
     of hardware idiosyncrasies, so they are less likely to be portable to
     different video hardware than routines that rely on slower but more
     universal BIOS or operating-system calls. This trade-off will be
     implicit in almost every video output routine you write.


Representation of Alphanumeric Data


     All IBM PC and PS/2 video subsystems use the same format for storing
     alphanumeric data in the video buffer. Each character is represented
     by a simple 2-byte data structure (see Figure 3-1). Characters are
     stored in the buffer in a linear sequence that maps across and down
     the screen (see Figure 3-2).


        Low-order byte      High-order byte
     ���������������������������������������Ŀ
     �  ASCII character  �     Attribute     �
     �        code       �                   �
     ����������������������������������������

     Figure 3-1.  Alphanumeric character and attribute mapping in a 16-bit
     word.


                                                      Display
               Video buffer                          ��������������
     0000H �������������������Ŀ                     ����������
           �                   ���� Character row O ����������
     00A0H �������������������Ĵ                     ����������
           �                   ���� Character row 1 ����������
     0140H �������������������Ĵ                     ����������
           �                   ���� Character row 2 ����������
     01E0H ���������������������                     ����������
           �                   �                     ����������
            � � � � � � � � � �                      ����������
           �                   �                     ����������
            � � � � � � � � � �                      ����������
           �                   �                     ����������
           � � � � � � � � � � �                     �

     Figure 3-2.  Video buffer map in 80-by-25 alphanumeric modes.


     A hardware character generator converts each character code into the
     proper dot pattern on the display. At the same time, attribute decoder
     circuitry generates the appropriate attribute--color, intensity
     (brightness), blinking, and so on--for each character. Since each
     character code in the video buffer is accompanied by an attribute
     byte, you can independently control the displayed attributes of each
     character on the screen.

     The hardware character generator displays each alphanumeric character
     within a rectangular matrix of pixels. Within this character matrix,
     the character itself is composed of a set of foreground pixels. The
     colors of the character's foreground and background pixels are
     specified by the low and high nibbles of the corresponding attribute
     byte.

     To display a character, you store its ASCII code and attribute in the
     proper location in the video buffer. Because of the linear mapping
     scheme, you can easily calculate the buffer address of a particular
     screen location. The general formula is

     offset = ((row * width) + column) * 2

     In this formula, width is the number of characters in each row. The
     factor of 2 is included because each character requires 2 bytes (one
     16-bit word) of storage in the video buffer. The values for row and
     column are zero-based, starting in the upper left corner of the
     screen. (The character in the upper left corner is located at row 0,
     column 0.)

     If you examine the contents of the video buffer, you can see how this
     data corresponds to characters on the screen (see Figure 3-3). Note
     how each character code is followed by its attribute byte. (All of the
     attribute bytes in the portion of the video buffer shown in Figure 3-3
     have the value 07H.)


           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
B000:0000  43 07 68 07 61 07 72 07 61 07 63 07 74 07 65 07 C.h.a.r.a.c.t.e.
B000:0010  72 07 20 07 72 07 6F 07 77 07 20 07 30 07 30 07 r. .r.o.w. .0.0.
B000:0020  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0030  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0040  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0050  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0060  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0070  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0080  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0090  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:00A0  43 07 68 07 61 07 72 07 61 07 63 07 74 07 65 07 C.h.a.r.a.c.t.e.
B000:00B0  72 07 20 07 72 07 6F 07 77 07 20 07 30 07 31 07 r. .r.o.w. .0.1.
B000:00C0  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:00D0  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:00E0  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:00F0  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0100  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0110  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0120  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................
B000:0130  00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 ................

     Figure 3-3.  Hexadecimal dump of an alphanumeric video buffer.


Attributes


     Although all IBM PC and PS/2 video subsystems use the same pattern
     of alternating character codes and attribute bytes to represent
     alphanumeric data, the way the attribute byte is interpreted varies.
     In general, the attribute byte is formatted as two 4-bit nibbles.
     The low-order nibble (bits 0 through 3) determines the character's
     foreground attribute; that is, the color and intensity of the
     character itself. The high-order nibble (bits 4 through 7) indicates
     the character's background attribute, although bit 7 may also control
     blinking in some situations.

     The 4-bit foreground and background attributes are ultimately decoded
     into a set of signals that drive the video monitor. In the simplest
     case, on the CGA, the four bits correspond directly to the three color
     signals and the intensity signal. The decoding scheme on other video
     subsystems can be complex, as on the EGA, MCGA, VGA, and InColor Card,
     or comparatively simple, as on the MDA.


MDA

     Although you may specify any of 16 (2^4) attributes for both
     foreground and background attributes, the MDA only recognizes certain
     combinations (see Figure 3-4). Nevertheless, you can generate a
     useful variety of character attributes by creatively combining
     intensity, blinking, and underlining. You can also exchange the usual
     foreground and background attributes to obtain "reverse video"--black
     characters on a normal-intensity background.


                           Not Underlined
���������������������������������������������������������������������������
                              Foreground
               Black     Dim1    Normal Intensity    High Intensity
��������������������������������������������������������������������������
Background
Black          00        2        07                  0F
Dim1          2        88        87                  8F
Normal         70        78        2                  2
High           F0        F8        2                  2

                             Underlined
���������������������������������������������������������������������������
                             Foreground
               Normal Intensity       High Intensity
���������������������������������������������������������������������������
Background
Black          01                     09
Dim1          81                     89

     Figure 3-4.  MDA foreground-background attribute combinations (values
     in hex). Attribute values not in this table always map to one of the
     combinations shown.


     On the MDA, as well as on all other IBM video hardware, bit 7 of each
     character's attribute byte can serve two purposes. By default, this
     bit controls whether a character blinks when displayed; setting the
     bit to 1 causes the associated character to blink. Bit 7 controls
     blinking because bit 5 (the Enable Blink bit) of the MDA's CRT Mode
     Control register (3B8H) is set to 1 by the video BIOS when the
     computer is powered up.

     If the Enable Blink bit is 0, however, bit 7 of the attribute byte no
     longer controls blinking (see Listing 3-1). Instead, bit 7 is
     interpreted as an intensity bit for the background attribute. When bit
     7 is set in a character's attribute byte, the character's background
     attribute is intensified; that is, normal green becomes intense green
     and black becomes dim green. Thus, to obtain all possible combinations
     of monochrome attributes listed in Figure 3-4, you must zero the
     Enable Blink bit.


���������������������������������������������������������������������������

     Listing 3-1.  Resetting the Enable Blink bit on the MDA or CGA.

���������������������������������������������������������������������������


     The value of the Mode Control register's Enable Blink bit affects the
     interpretation of bit 7 of all attribute bytes, so you can't display
     both blinking characters and characters with intensified background at
     the same time. You must decide which attribute is more useful in your
     program and set the Enable Blink bit accordingly.

     All IBM PC and PS/2 video subsystems, including the MDA, blink
     alphanumeric characters by substituting the background attribute for
     the foreground attribute about twice a second. The effect is that each
     blinking character alternates with a blank character.

     If you fill the display with blinking characters, the overall effect
     can be disconcerting, because the screen is blanked and restored twice
     each second. But if your purpose is to attract attention to the
     display, using the blink attribute can be very effective.

       ���ͻ     If you use the underline attribute (foreground attribute 1
       � T �     or 9) on a Compaq portable, you won't see underlined
       � I �     characters. This is because the Compaq portable decodes
       � P �     attribute values into 16 progressively brighter shades
       ���ͼ     of green; the underline attribute values of 1 and 9
                 therefore appear as shades of green.

       ���ͻ     Surprisingly, a few IBM MDAs generate color as well as
       � T �     monochrome output. Of course, the MDA's green monochrome
       � I �     display uses only two signals to control attributes (video
       � P �     on/off and intensity on/off); it ignores any color video
       ���ͼ     signals. However, a color display that can use the MDA's
                 16.257 MHz horizontal sync and 50 Hz vertical sync signals
                 will display eight colors (with and without intensity)
                 when attached to some (but not all) MDAs. Unfortunately,
                 you can never be certain which MDA will turn out to be a
                 color adapter in disguise.


HGC

     The HGC and HGC+ exactly emulate the MDA's monochrome alphanumeric
     mode. Programs written for the MDA run unchanged on either of these
     adapters.


CGA

     The CGA uses the same foreground-background attribute scheme as does
     the MDA. However, the CGA's attribute decoder circuitry recognizes all
     16 possible combinations of the four bits in each nibble of the
     attribute byte. For each character on the screen, you can
     independently specify any of 16 colors for foreground and background.

     The available colors are simple combinations of the primary colors
     red, green, and blue. Each bit in each nibble of the attribute byte
     corresponds to a signal that the CGA supplies to the video monitor
     (see Figure 3-5). The low-order three bits of each nibble correspond
     to the red (R), green (G), and blue (B) signals. The eight possible
     combinations produce a gamut of red, green, blue, and their
     intermediate colors (see Figure 3-6).


     Bit   3     2     1     0
        �����������������������Ŀ
        �  I  �  R  �  G  �  B  �
        ����������������������
           �     �     �     �
           �     �     �     �
           �     �     �     ��������������Pin 5
           �     �     ��������������������Pin 4
           �     ��������������������������Pin 3
           ��������������������������������Pin 6

     Figure 3-5.  CGA attributes and monitor color drive signals. Pin
     numbers refer to the CGA's 9-pin connector.


����������������������������������������������������������������������������ķ
Color               Binary (IRGB)  Hexadecimal
��������������������������������������������������������������������������
Black               0000           00
Blue                0001           01
Green               0010           02
Cyan                0011           03
Red                 0100           04
Violet              0101           05
Yellow (brown)      0110           06
White               0111           07
Black (gray)        1000           08
Intense blue        1001           09
Intense green       1010           0A
Intense cyan        1011           0B
Intense red         1100           0C
Intense violet      1101           0D
Intense yellow      1110           0E
Intense white       1111           0F
Color               Binary (IRGB)  Hexadecimal
Intense white       1111           0F

     Figure 3-6.  CGA display attributes.


     Setting bit 3 of the attribute byte (the intensity bit in the
     foreground nibble) displays the color designated in the R, G, and B
     bits (bits 0 through 2) with higher intensity. However, as on the MDA,
     the high-order bit (bit 7) of each attribute byte controls either
     background intensity or blinking. Again, the attribute displayed
     depends upon the state of a bit in a control register.

     Bit 5 of the CGA's Mode Control register (I/O port 3D8H) is an Enable
     Blink bit analogous to bit 5 of the MDA's CRT Control register. When
     you set the Enable Blink bit to 0, bit 7 of a character's attribute
     byte signifies that the background color specified in bits 4 through
     6 should be intensified. When you set the Enable Blink bit to 1, only
     nonintensified background colors are displayed, but characters whose
     attribute bytes have bit 7 set to 1 will blink.

     The Enable Blink bit is set to 1 whenever you call the ROM BIOS to
     select an alphanumeric video mode. By default, therefore, bit 7 of
     each character's attribute byte controls blinking rather than
     background intensity. You must reset the Enable Blink bit to display
     characters with intensified background colors.

     Many CGA-compatible displays squeeze a bit more out of the 16
     available colors (8 nonintensified, 8 intensified) by displaying low-
     intensity yellow as brown and high-intensity black as gray.
     Unfortunately, a program cannot determine whether a particular display
     can do this. Be careful about displaying, for example, gray characters
     on a black background with a CGA, because such color combinations are
     invisible on some color displays.


EGA

     In 16-color alphanumeric modes, the EGA uses the same attribute byte
     format as the CGA. However, the 4-bit foreground and background values
     do not correspond directly to the colors displayed. Instead, each 4-
     bit value is masked with the four low-order bits of the Attribute
     Controller's Color Plane Enable register (12H); the resulting 4-bit
     value designates one of the EGA's 16 palette registers (see Figure 3-
     7). Each bit of the 6-bit color value contained in the designated
     palette register corresponds to one of the six RGB signals that drive
     the monitor (see Figure 3-8).

     An EGA-compatible color monitor is driven by six color signals--three
     primary (higher intensity) and three secondary (lower intensity). All
     64 combinations of these six signals appear as different colors and/or
     intensities. With a 200-line color monitor--or in 200-line modes on an
     EGA-compatible monitor--bits 0, 1, and 2 control the color signals,
     while bit 4 controls the intensity signal.


     �����������������Ŀ                   ������������������Ŀ
     �                 �    Logical AND    �   Color Plane    �
     � 4-bit attribute �������������������Ĵ Enable register  �
     �                 �         �         �                  �
     �������������������         �         ��������������������
                                 �
                     ����������������������Ŀ
                     �Palette register 0-0FH �
                     �������������������������
                                 �
                                 
               6-bit digital output to video display
                (2-bits each for red, green, blue)

     Figure 3-7.  Attributes and colors on the EGA.


     200-line monitors (CGA-compatible):

     Bit   7     6     5     4     3     2     1     0
        �����������������������������������������������Ŀ
        �     �     �     �  I  �     �  R  �  G  �  B  � Palette register
        ������������������������������������������
                             �           �     �     �������������Pin 5
                             �           �     �������������������Pin 4
                             �           �������������������������Pin 3
                             �������������������������������������Pin 6

     350-line color monitors (EGA-compatible):

     Bit   7     6     5     4     3     2     1     0
        �����������������������������������������������Ŀ
        �     �     �  r  �  g  �  b  �  R  �  G  �  B  � Palette register
        ������������������������������������������
                       �     �     �     �     �     �������������Pin 5
                       �     �     �     �     �������������������Pin 4
                       �     �     �     �������������������������Pin 3
                       �     �     �������������������������������Pin 7
                       �     �������������������������������������Pin 6
                       �������������������������������������������Pin 2

     350-line monochrome monitors (MDA-compatible)

     Bit   7     6     5     4     3    2     1     0
        �����������������������������������������������Ŀ
        �     �     �     �  I  �  V  �     �     �     � Palette register
        ������������������������������������������
                             �     ������������������������������Pin 7
                             ������������������������������������Pin 6

     R,G,B = primary red, green, blue (higher intensity)
     r,g,b = secondary red, green, blue (lower intensity)
     I = intensity
     V = monochrome video

     Figure 3-8.  EGA palette register values and corresponding monitor
     color drive signals. Pin numbers refer to the EGA's 9-pin connector.


     The EGA's method of generating colors indirectly through palette
     registers is more complex than the CGA's direct scheme, but the EGA is
     more flexible. You can select the foreground and background colors for
     each character individually, yet you can produce global color changes
     by updating the value in a particular palette register.

     The high-order bit of each character's attribute byte can control
     either blinking or background intensity, just as on the MDA and the
     CGA. Bit 3 of the EGA's Attribute Controller Mode Control register
     (register 10H at I/O port 3C0H) is the Enable Blink bit. Setting it to
     1 enables blinking, so only the low-order 3 bits of the background
     nibble (bits 4 through 6 of the attribute byte) designate palette
     registers. Thus, when blinking is enabled, you can reference only
     the first eight palette registers to select the background color for
     a character. Setting the Enable Blink bit to 0 disables blinking,
     making all 16 palette registers available for background colors
     (see Listing 3-2).


���������������������������������������������������������������������������

     Listing 3-2.  Setting and resetting the Enable Blink bit on the MCGA,
     EGA, or VGA.

���������������������������������������������������������������������������


     When you select an alphanumeric video mode using the EGA BIOS, the
     palette registers are loaded with default values that correspond to
     the colors available on the CGA. The color values in the second eight
     palette registers are intensified versions of those in the first
     eight. Thus, if you simply treat bit 7 of the attribute byte as a
     "background intensity or blink" bit, your program will run on both an
     EGA and a CGA.

     You can update the contents of any palette register either directly or
     with INT 10H function 10H (see Listing 3-3). Using the BIOS routine
     is more convenient and avoids the need to write hardware-dependent
     code. Moreover, the BIOS routine can also load all 16 palette
     registers at once, given a table of color values (see Appendix A).
     Nevertheless, you may still need to program the palette registers
     directly to produce very rapid color changes such as might be required
     in some types of animation.


���������������������������������������������������������������������������

     Listing 3-3.  Palette register programming on the EGA or VGA.

���������������������������������������������������������������������������


     In monochrome alphanumeric mode, the EGA emulates the MDA monochrome
     display attributes. The video BIOS initializes the palette registers
     with values that correspond to MDA attributes (see Figure 3-9). Bit 3
     determines whether pixels are on or off, and bit 4 (if set in addition
     to bit 3) causes a higher-intensity display. The underline attribute
     is generated whenever a character's foreground attribute is 1 or 9,
     regardless of the value in the corresponding palette register.


����������������������������������������������������������������������������ķ
Value       Attribute
��������������������������������������������������������������������������
0           Black
8           Normal intensity
10H         Dim
18H         High intensity

     Figure 3-9.  Monochrome alphanumeric attribute values for the EGA
     palette registers.


       ���ͻ     The EGA also generates an underline attribute in 16-color
       � T �     alphanumeric modes when the foreground attribute is 1 or 9
       � I �     and the background attribute is 0 or 8. However, you do
       � P �     not normally see an underline in 16-color modes because
       ���ͼ     the video BIOS default value for the CRTC Underline
                 Location register (14H) is 1FH. This value is greater than
                 the number of scan lines normally displayed for
                 alphanumeric characters, so the underline does not appear.

                 You can generate underlined characters in 16-color modes
                 by storing a displayable value in the Underline Location
                 register. Of course, only characters with attributes of 1,
                 9, 81H, or 89H will appear underlined, but you can change
                 the values in the corresponding palette registers to
                 produce underlined characters of any desired color.


InColor Card

     The InColor Card can decode alphanumeric attributes in several
     different ways. The card has a set of 16 palette registers whose
     function is analogous to the EGA's Attribute Controller palette
     registers, but the InColor Card can be configured by your program to
     bypass the palette registers and decode each character's 4-bit
     foreground and background attributes in an MDA- or CGA-compatible
     manner. Bits 4 and 5 of the Exception register (17H) control how the
     InColor Card interprets alphanumeric attributes (see Figure 3-10).
     Bit 5 determines whether the InColor Card displays monochrome
     attributes (as on the MDA) or color attributes (as on the CGA or EGA).
     Bit 4 enables attribute mapping through the palette registers.

     When the InColor Card is powered up, Exception register bit 5 has the
     value 1 and bit 4 has the value 0. Thus, by default, the card
     interprets attributes as an MDA would. However, if you set both bits 5
     and 4 to 0 (see Listing 3-4), alphanumeric attributes specify the
     same set of 16 colors as on a CGA (refer to Figure 3-6).


���������������������������������������������������������������������������

     Listing 3-4.  InColor Exception register programming.

���������������������������������������������������������������������������


����������������������������������������������������������������������������ķ
Bit 5     Bit 4     Attribute Emulation
��������������������������������������������������������������������������
0         0         CGA
0         1         EGA
1         0         MDA
1         1         MDA mapped through palette registers.

     Figure 3-10.  Exception register control of attributes on the Hercules
     InColor Card.


     Setting bit 4 to 1 causes attributes to map to the card's palette
     registers, regardless of the value of bit 5. Thus, if bit 4 is 1 and
     bit 5 is 0, the InColor Card interprets attributes as does the EGA. If
     bit 4 is 1 and bit 5 is 1, however, the card maps each character's
     foreground and background attributes only to the palette registers
     that correspond to valid monochrome attribute values. In this case,
     the "black," "dim," "normal intensity," and "high intensity"
     attributes select palette registers 00H, 08H, 07H, and 0FH
     respectively.

     Bit 5 of the CRT Mode Control register at 3B8H is the Enable Blink
     bit. This bit controls background intensity regardless of the values
     of Exception register bits 4 and 5. However, characters are blinked
     only when Exception register bit 5 is 1 (MDA-compatible attributes);
     characters do not blink when bit 5 of the Exception register is 0
     (CGA-compatible attributes), regardless of the Enable Blink bit's
     setting.

     No video BIOS support is provided for the InColor Card's palette
     registers. Your program must therefore update the palette by directly
     storing values in the palette registers. Listing 3-5 is an example of
     how you might do this. The initial I/O read (IN AL,DX) of the palette
     register (1CH) resets an internal index which points to the first of
     the 16 internal palette registers. Each subsequent I/O write
     (OUT DX,AL) updates one internal palette register and increments the
     internal index to point to the next palette register, so all 16
     registers can be loaded by executing a simple loop.

       ���ͻ     Because monochrome attributes can be mapped through
       � T �     palette registers, you can assign as many as four
       � I �     different colors to monochrome programs that run on the
       � P �     InColor Card. Do this by setting Exception register bits 4
       ���ͼ     and 5 to 1 and updating palette registers 00H, 08H, 07H,
                 and 0FH with the desired colors.


���������������������������������������������������������������������������

     Listing 3-5.  InColorpalette register programming.

���������������������������������������������������������������������������


     On the InColor Card, the colors of both the cursor and the underscore
     are independent of the foreground colors of the characters in the
     video buffer. The cursor color is specified in bits 0 through 3 of the
     Exception register, and the underscore color value is specified in
     bits 4 through 7 of the Underscore register (CRTC register 15H). When
     the InColor Card is displaying MDA attributes (that is, when bit 5 of
     the Exception register is set to 1), you can specify only the three
     low-order bits of the cursor and underscore colors; the high-order bit
     of these color values is derived from the foreground attribute of the
     character where the cursor or underscore is displayed.

     When palette mapping is enabled (Exception register bit 4 is set to
     1), both the cursor and underscore color values select palette
     registers. When palette mapping is disabled, the cursor and underscore
     color values are displayed using the usual CGA colors. Also, if you
     specify a value of 0 for either the underscore color or the cursor
     color, the InColor Card uses the value 7 instead.


MCGA

     The components of the PS/2 Model 30's video subsystem that transform
     attribute data into color video signals are the Video Formatter and
     the video Digital-to- Analog Converter (DAC). The Video Formatter gate
     array decodes attributes and generates an 8-bit digital output which
     is passed to the video DAC; from this, the DAC generates analog red,
     green, and blue signals for the video display. The DAC converts the 8-
     bit output from the Video Formatter to the three analog color signals
     by using the 8 bits to select one of the DAC's 256 color registers.
     Each DAC color register is 18 bits wide, comprising three 6-bit values
     for red, green, and blue (see Figure 3-12). The DAC converts each
     6-bit value into an analog signal with the highest value (3FH)
     corresponding to the highest-intensity signal.

     In alphanumeric modes, the four low-order bits of the Video
     Formatter's 8-bit digital output are derived from attribute bytes,
     while the four high-order bits are always 0 (see Figure 3-11). Thus,
     only the first 16 of the video DAC's color registers are used in MCGA
     alphanumeric modes. The remaining 240 registers can be accessed only
     in 320-by-200 256-color graphics mode (see Chapter 4). When an MCGA
     is attached to a color display, the video BIOS initializes the first
     16 video DAC color registers with the same colors found on the CGA.

       ���ͻ     The value in the video DAC Mask register (I/O port 3C6H)
       � T �     masks the 8-bit value passed to the video DAC. The Mask
       � I �     register value is set to 0FFH by the video BIOS
       � P �     initialization routines so that all 256 video DAC color
       ���ͼ     registers can be accessed. IBM technical documentation
                 recommends that this value not be modified.


     ����������������������Ŀ                  ��������������������������Ŀ
     �   4-bit attribute    �   Logical AND    �  Video DAC Mask register �
     �                      ������������������Ĵ                          �
     ������������������������         �        ����������������������������
                                      
                        ��������������������������Ŀ
                        �        Video DAC         �
                        �   color register 0-0FH   �
                        ����������������������������
                                      �
                                      
                    18-bit analog output to video display
                     (6 bits each for red, green, blue)

     Figure 3-11.  Attributes and colors on the MCGA. (The value in the
     video DAC Mask register should normally be 0FFH.)


           6 bits           6 bits          6 bits
     ��������������������������������������������������Ŀ
     �      Red       �      Green     �      Blue      �
     ��������������������������������������������������
              �                �                �����������Pin 3
              �                ����������������������������Pin 2
              ���������������������������������������������Pin 1

     Figure 3-12.  Video DAC color register values and monitor color drive
     signals. Pin numbers refer to the MCGA's 15-pin connector.


     Unlike the EGA, an MCGA with a monochrome display does not emulate the
     MDA's attributes. Instead, the 16 default video DAC color register
     values consist of four groups of four shades of gray. Each group is
     displayed with higher intensity than the preceding group. Within each
     group, the intensity increases from lower to higher attribute values.
     Thus, attribute values 0 through 3 make up a range of four shades of
     gray, values 4 through 7 a second range of somewhat higher intensity,
     and values 8 through 0BH and 0CH through 0FH a third and fourth range
     of still higher intensity.

       ���ͻ     Instead of this default MCGA monochrome gray-scale
       � T �     configuration, you might prefer to use gray-scale values
       � I �     that increase uniformly with increasing attribute values.
       � P �     The code in Listing 3-6 loads the video DAC registers
       ���ͼ     with appropriate values for this gray-scale gamut.


���������������������������������������������������������������������������

     Listing 3-6.  Loading an alternative MCGA monochrome gray-scale
     palette.

���������������������������������������������������������������������������


VGA

     In general, the VGA exactly emulates EGA alphanumeric attribute
     decoding. However, the VGA has both a video DAC and a set of 16
     Attribute Controller palette registers. Each palette register value
     selects one of 256 video DAC color registers. The value in the
     selected video DAC color register determines the color displayed.

     Depending on the value of bit 7 in the Attribute Controller's Mode
     Control register, you can use the palette register value to select a
     video DAC color register in one of two ways. When bit 7 is set to 0,
     the Attribute Controller combines the 6-bit palette-register value
     with bits 2 and 3 of its Color Select register (14H) to produce an 8-
     bit value that selects a video DAC color register (see Figure 3-13).
     Alternatively, when bit 7 is set to 1, only the four low-order bits of
     each palette register are meaningful. The Attribute Controller derives
     the other four bits of the 8-bit value from bits 0 through 3 of the
     Color Select register (see Figure 3-14).

     In the first case (when bit 7 of the Mode Control register is set to
     0), the 6-bit palette registers are used to select one of four groups
     of 64 video DAC color registers, and bits 2 and 3 of the Color Select
     register determine which group of color registers is used. In the
     second case (when bit 7 of the Mode Control register is set to 1),
     each palette register value selects one of 16 groups of 16 video DAC
     color registers, and bits 0 through 3 of the Color Select register
     specify one of the 16 groups of DAC color registers.


     �����������������Ŀ              �����������������Ŀ
     � 4-bit attribute � Logical AND  �   Color Plane   �
     �                 ��������������Ĵ Enable register �
     �������������������       �      �������������������
                               
                   ����������������������Ŀ  ����������������������Ŀ
                   �Palette register 0-0FH�  �Color Select register �
                   �                      �  �     (bits 2-3)       �
                   ������������������������  ������������������������
                               �Bits 0-5 of              � Bits 6-7 of
                               �color register           � color register
                               �number                   � number
                               ���������������������������
                                            �
                                            �     �����������������������Ŀ
                                            �     �    Video DAC Mask     �
                                            �     �       register        �
                                            �     �������������������������
                                            �   Logical AND    �
                                            ��������������������
                                                   �
                                                   
                                        ����������������������Ŀ
                                        �      Video DAC       �
                                        �color register 0-0FFH �
                                        ������������������������
                                                   �
                                                   
                                 18-bit analog output to video display
                                   (6 bits each for red, green, blue)

     Figure 3-13.  Attributes and colors on the VGA (when bit 7 of the
     Attribute Controller's Mode Control register is set to 0).


     This added level of indirection, afforded by the combined use of
     palette registers and video DAC color registers, makes switching
     between palettes easy, since you can select any of 16 different 16-
     color palettes just by changing the value in the Attribute
     Controller's Color Select register. If you store 16 palettes of
     gradually increasing intensity in the DAC color registers, you can
     accentuate characters on the screen by cyclically increasing and
     decreasing their intensity. This effect is more subtle than simply
     blinking the characters on and off, particularly when applied to a
     large area of the display.


     �����������������Ŀ              �����������������Ŀ
     � 4-bit attribute � Logical AND  �   Color Plane   �
     �                 ��������������Ĵ Enable register �
     �������������������       �      �������������������
                               
                   ����������������������Ŀ  ����������������������Ŀ
                   �Palette register 0-0FH�  �Color Select register �
                   �                      �  �     (bits 0-3)       �
                   ������������������������  ������������������������
                               �Bits 0-3 of              � Bits 4-7 of
                               �color register           � color register
                               �number                   � number
                               ���������������������������
                                            �
                                            �     �����������������������Ŀ
                                            �     �    Video DAC Mask     �
                                            �     �       register        �
                                            �     �������������������������
                                            �   Logical AND    �
                                            ��������������������
                                                   �
                                                   
                                        ����������������������Ŀ
                                        �      Video DAC       �
                                        �color register 0-0FFH �
                                        ������������������������
                                                   �
                                                   
                                 18-bit analog output to video display
                                   (6 bits each for red, green, blue)

     Figure 3-14.  Attributes and colors on the VGA (when bit 7 of the
     Attribute Controller's Mode Control register is set to 1).


       ���ͻ     When the VGA emulates 80-by-25 16-color alphanumeric mode
       � T �     on a monochrome display, the palette consists of the same
       � I �     four groups of four gray-scaled values as does the
       � P �     corresponding palette on the MCGA. As on the MCGA, you can
       ���ͼ     create a gray-scale palette with gradually increasing
                 intensities. Listing 3-7 illustrates how you might do
                 this. Note how the appropriate video DAC registers are
                 selected by examining the values in the Attribute
                 Controller's palette registers.


���������������������������������������������������������������������������

     Listing 3-7.  Loading an alternative VGA monochrome gray-scale
     palette.

���������������������������������������������������������������������������


     The VGA emulates the MDA's monochrome alphanumeric mode (video BIOS
     mode 7) on either a color or a monochrome display. The Attribute
     Controller palette register values and the control of blinking and
     underlining are the same as on the EGA. In this mode, the video DAC
     registers corresponding to the palette values 00H, 07H, 08H, and 18H
     are initialized with the appropriate gray-scale values. The palette
     and video DAC register values are the same in this mode regardless of
     whether a color or monochrome display is attached.


Gray-Scale Summing


     Both the MCGA and the VGA BIOS contain logic which can transform the
     red-green-blue values in the video DAC registers into corresponding
     gray-scale values. This transformation is performed by taking a
     weighted average of the red, green, and blue components. To compute
     the gray-scaled equivalent value, the BIOS sums 30 percent of the red
     value, 59 percent of the green, and 11 percent of the blue. (These
     percentages approximate the displayed intensities of pure red, green,
     and blue.) For example, the default color for video DAC Color Register
     02H (cyan) is made up of three 6-bit components. The value of the red
     component is 0, the green component 2AH, and the blue component 2AH.
     The gray-scale value is therefore 1DH, the sum of

     (.30 x 0) + (.59 x 2AH) + (.11 x 2AH)

     INT 10H function 10H includes a subfunction (AL = 1BH) that reads a
     set of video DAC color registers and updates them with equivalent
     gray-scale values. Appendix A contains an example of the use of this
     video BIOS function.

     On both the MCGA and the VGA, INT 10H function 0 uses gray-scale
     summing by default when a monochrome display is attached. With a color
     display, gray-scale summing is disabled by default. You can
     selectively enable or disable default gray-scale summing by executing
     INT 10H function 12H with BL = 33H.


Border Color


     On the CGA, EGA, MCGA, and VGA, you can specify a color to be
     displayed during the vertical and horizontal overscan intervals. This
     overscan or border color is not represented by any data in the video
     buffer. Instead, a special control register contains the value of the
     color displayed.


CGA

     On the CGA, you select the border color with the four low-order bits
     of the Color Select register at I/O port 3D9H (see Listing 3-8). The
     color values parallel those available for character attributes: bits
     0, 1, and 2 select the blue, green, and red primaries, and bit 3 is
     interpreted as an intensity bit.


���������������������������������������������������������������������������

     Listing 3-8.  Setting a border color.

���������������������������������������������������������������������������


     Using INT 10H function 0BH to update the border color is probably more
     convenient than programming the Color Select register directly. The
     code is more portable, and the BIOS routine saves the most recently
     written value of the Color Select register in its Video Display Data
     Area in the byte at 0040:0066 (CRT_PALETTE). If you do write directly
     to the Color Select register, you should update CRT_PALETTE as in the
     example in Listing 3-8.

       ���ͻ     The MCGA does not generate a colored border, regardless of
       � T �     the value in its Color Select register.
       � I �
       � P �
       ���ͼ


EGA and VGA

     On the EGA and VGA, the overscan color is specified by the contents of
     register 11H of the Attribute Controller (I/O port 3C0H). You could
     write directly to the  I/O port, but doing the job with an INT 10H
     call is usually easier. You can use the EGA BIOS to update the
     overscan color in two ways. You can use function 0BH of INT 10H or you
     can include the border color as the 17th and last entry in the table
     of palette register colors you pass to INT 10H function 10H (see
     Appendix A).

       ���ͻ     On the VGA with a monochrome display, the only useful
       � T �     border attributes are 0 (black), 8 (normal), and 18H
       � I �     (intense).
       � P �
       ���ͼ

     The 350-line video modes on the EGA have relatively short vertical and
     horizontal overscan intervals. The displayed border may be only 1 or 2
     mm wide, or it may bleed across the screen during the horizontal
     retrace interval. For this reason you should avoid setting the border
     color in any 350-line mode on the EGA.

       ���ͻ     You can increase the EGA's horizontal and vertical
       � T �     overscan intervals in 350-line modes by modifying the CRTC
       � I �     horizontal and vertical timing parameters. A reasonable
       � P �     border, about as wide as that displayed with the VGA, can
       ���ͼ     be achieved by adding one or two characters to the
                 Horizontal Total value and eight or ten scan lines to the
                 Vertical Total value. The corresponding timing values for
                 the Horizontal and Vertical Retrace and Blanking registers
                 must be adjusted accordingly (see  Figure 3-15).

                 The problem with reprogramming the CRTC in this way is
                 that the horizontal and vertical frequencies that drive
                 the video display are somewhat lower than nominal. For
                 example, with the CRTC values shown in Figure 3-15,
                 the horizontal scan rate becomes 16.257 MHz / (94
                 chars/line x 8/char), or 21.62 KHz, which is about 1
                 percent lower than the nominal horizontal scan frequency
                 of 21.85 KHz. Similarly, the vertical scan rate becomes
                 21.62 KHz / 374 lines, or 58 Hz, almost 4 percent lower
                 than the usual 60 Hz frame rate. Still, these scan rates
                 are usually within the tolerances of an EGA-compatible
                 video display.


               80-by-25 16-Color Alphanumeric Mode:
���������������������������������������������������������������������������
CRTC register  Function                      Setting (default)
���������������������������������������������������������������������������
0              Horizontal Total              5CH (5BH)
2              Horizontal Blanking Start     54H (53H)
3              Horizontal Blanking End       3CH (37H)
4              Horizontal Retrace Start      52H (51H)
5              Horizontal Retrace End        5CH (5BH)
6              Vertical Total                76H (6CH)
10H            Vertical Retrace Start        64H (5EH)
11H            Vertical Retrace End          25H (2BH)
15H            Vertical Blank Start          64H (5EH)
16H            Vertical Blank End            11H (0AH)


               640-by-350 16-Color Graphics Mode:
���������������������������������������������������������������������������
CRTC register  Function                      Setting (default)
���������������������������������������������������������������������������
0              Horizontal Total              5CH (5BH)
2              Horizontal Blanking Start     53H (53H)
3              Horizontal Blanking End       3CH (37H)
4              Horizontal Retrace Start      53H (52H)
5              Horizontal Retrace End        00H (00H)
6              Vertical Total                76H (6CH)
10H            Vertical Retrace Start        64H (5EH)
11H            Vertical Retrace End          25H (2BH)
15H            Vertical Blank Start          64H (5EH)
16H            Vertical Blank End            11H (0AH)

     Figure 3-15.  CRTC parameters for increased border width in 350-line
     EGA video modes. (Default register values are listed in parentheses.)


Avoiding CGA Snow


     On the CGA, alphanumeric video display modes present a particular
     programming challenge whenever you are concerned about the speed of
     video display output. You must program carefully in alphanumeric modes
     to prevent interference with the display when you read or write data
     in the CGA's video buffer.

     Directly accessing the contents of the CGA's video buffer from
     your program has its pros and cons. On the positive side, it enables
     your program to completely control the buffer's contents, and thus
     what is displayed. The negative side is that when both the CPU and
     the display-refresh circuitry access the buffer at the same time,
     interference, or "snow," can appear on the display. The snow can be
     barely noticeable or greatly distracting, depending on the amount of
     data transferred to or from the video buffer.

     In general, to avoid snow you must limit CPU accesses to the video
     buffer to intervals when data is not being fetched from the buffer to
     refresh the screen. In practice, this means that your program must
     transfer data to and from the video buffer only when the electron
     beam in the video display is moving through an overscan or retrace
     interval.

     This synchronization can be achieved in several ways, but,
     unfortunately, all of them introduce a certain amount of hardware
     dependency into your program. As a general rule, the more hardware-
     dependent tricks you play, the faster your program runs on a CGA but
     the less likely it is to run on another video adapter.


Blanking the Display

     One technique for preventing display interference on the CGA is to
     turn off the electron beam whenever you access the display buffer. You
     then leave the beam off while data is transferred to or from the video
     buffer. This method is used in the ROM BIOS routines which scroll the
     display.

     The best time to blank the display is when it's blank anyway, at the
     start of a vertical blanking interval. If you do not take care to turn
     the electron beam off during the vertical blanking interval, you will
     instead blank the screen while it is being refreshed. This can produce
     an annoying flicker or interference stripes.

     The technique is straightforward (see Listing 3-9). The trick is to
     synchronize buffer access with the start of a vertical blanking
     interval. Do this by detecting an interval when vertical blanking is
     not occurring. Then wait for the next subsequent vertical blanking
     interval to begin.


���������������������������������������������������������������������������

     Listing 3-9.  Display alphanumeric text on the CGA by blanking the
     display.

���������������������������������������������������������������������������


     The procedure for detecting the start of a vertical blanking interval
     requires you to first determine a timeout value for the horizontal
     retrace interval (see Listing 2-2). This value is then used to wait
     for the last horizontal scan in the current frame. When the last
     horizontal blanking interval times out, the vertical blanking interval
     has begun.

     At this point, your program should explicitly disable the electron
     beam by resetting bit 3 of the CGA's Mode Control register (port
     3D8H). When this bit is zeroed, the electron beam is disabled and the
     display remains dark. While the display is dark, you can move data to
     or from the video buffer without causing snow. When the data transfer
     is complete, restore the display by setting bit 3 of the Mode Control
     register to 1.

     It is not necessarily desirable to wait for another vertical blanking
     interval before reenabling the electron beam. If the period during
     which you transferred data left the screen dark long enough to cause
     noticeable flicker, waiting until the next vertical retrace will only
     prolong the duration of the flicker. If you reenable the display
     somewhere in the middle of a refresh cycle, the flicker will be worse
     in the top part of the screen but better in the bottom part. Neither
     situation is ideal; it's up to you to decide which alternative is
     preferable.

     The amount of time it takes to access the video buffer determines how
     long your program must keep the screen dark. Obviously, the longer the
     screen is dark, the more flicker you perceive. If your program is
     executed on one of the slower members of the IBM PC family (PC or
     PC/XT), the flicker effect can become annoying.

     Consider what might happen whenever you scroll an entire 80-by-25
     screen up one line. Within the video buffer, 4000 bytes of data must
     be moved. On a vintage IBM PC, with its 4.77 MHz 8088, this data
     transfer takes about 21 milliseconds. Since each video frame lasts
     about 16.7 milliseconds (1/60 second), the screen remains dark for
     about 1-1/3 frames. The resulting flicker is very noticeable,
     particularly if the background color is not black. On the other hand,
     on a PC with a faster CPU, the data transfer takes less time, so the
     flicker is less apparent.


Using the Vertical Blanking Interval

     A technique that avoids the flicker problem is to access the video
     buffer only for the duration of the vertical blanking interval.
     However, this slows data transfer, because you can move only a limited
     number of bytes of data during a single vertical blanking interval.

     The limitations here are the duration of the vertical blanking
     interval (about 4 milliseconds) and the rate at which the CPU can move
     data in the video buffer. A 4.77 MHz 8088 in a PC or PC/XT can move
     about 450 words (900 bytes) of data before the vertical blanking
     interval ends and snow becomes visible. Obviously, a PC with a higher
     clock speed or with an 80286 or 80386 can move more data during a
     single vertical blanking interval.


Using the Horizontal Blanking Interval

     If your video output routine synchronizes with the start of horizontal
     blanking intervals, you have about 7 microseconds in which to access
     the video buffer at the end of each raster scan line without causing
     snow (see Listing 3-10). Although 7 microseconds may not seem like
     much time, it is long enough to move 2 bytes into or out of the video
     buffer without causing display interference. Since each frame contains
     200 horizontal blanking intervals, you can significantly increase
     performance by taking advantage of them.


���������������������������������������������������������������������������

     Listing 3-10.  Display alphanumeric text on the CGA during horizontal
     and vertical blanking intervals.

���������������������������������������������������������������������������


     Because the horizontal blanking interval is so short, synchronization
     is critical. The technique is parallel to that used for synchronizing
     with the vertical retrace interval. In this case, you determine the
     status of the Display Enable signal by testing bit 0 of the CRT Status
     register (3DAH). When this bit has a value of 1, the Display Enable
     signal is off and a horizontal blanking interval is in progress.

     Keep in mind two considerations if you take the trouble to use the
     horizontal blanking intervals. First, you might as well use the
     vertical blanking intervals as well, since they're there. Second, you
     should use MOVS or STOS instructions to do the actual data transfers.
     The slower MOV mem/reg instruction can take longer than the horizontal
     blanking interval lasts, so snow isn't eliminated.

     The IBM ROM BIOS routines that write to the video buffer during
     horizontal retrace use the sequence

        mov    ax,bx
        stosw

     to move a character and attribute into the buffer without snow.
     Nevertheless, if you use the same two instructions in a RAM-based
     program, you see snow on a CGA running on a 4.77 MHz PC. The reason is
     that, at the point where these instructions are executed, the 4-byte
     instruction prefetch queue in the 8088 has room for only two more
     bytes. This means that the STOSW opcode cannot be prefetched. Instead,
     the 8088 must fetch the opcode from memory before it can  be executed.

     That last memory access to fetch the STOSW instruction makes the
     difference. Because accesses to ROM are faster than accesses to RAM,
     the instruction fetch is slightly faster out of ROM, so no snow is
     visible because the STOSW can run before the horizontal blanking
     interval ends. The routine in Listing 3-10 sidesteps the problem by
     using XCHG AX,BX (a 1-byte opcode) instead of MOV AX,BX (a 2-byte
     opcode). This avoids the extra instruction fetch, so the code executes
     fast enough to prevent display interference.

     Note how the interrupts are disabled in the loop that waits for the
     start of the horizontal blanking interval. Had an interrupt occurred
     between the JNZ L06 and the following XCHG AX,BX instructions, the
     horizontal blanking interval would have ended long before control
     returned from the interrupt handler. Disabling interrupts while each
     word is transferred into the video buffer avoids this possible loss of
     synchronization.

       ���ͻ     The routine in Listing 3-10 never explicitly detects the
       � T �     end of the vertical blanking interval, nor does it count
       � I �     the 200 horizontal scans in each display refresh cycle.
       � P �     Instead, the number of bytes that can be transferred
       ���ͼ     during each vertical blanking interval (VBcount) is
                 determined empirically for a "worst case" situation (for
                 example, for a 4.77 MHz IBM PC).

                 The most important reason for this imprecision about the
                 number of bytes to transfer during vertical blanking
                 intervals is that interrupts can occur anywhere in a video
                 output routine except where they are explicitly disabled.
                 For example, clock-tick interrupts and keyboard interrupts
                 can occur at any time. Because you can't simply disable
                 all interrupts for the duration, you must design video
                 output routines to accommodate the unpredictable time
                 spent in interrupt handlers.

     The problem of snow is avoided in the hardware design of every other
     IBM PC and PS/2 video subsystem, including the MDA, EGA, MCGA, and VGA
     (and even the PCjr). Also, many second-source manufacturers of CGA-
     compatible adapters design their hardware to eliminate the problem.
     This means that retrace synchronization loops may not be needed in
     many applications.

     If you run a program either on a CGA (with snow) or on a CGA-
     compatible (without snow), the program should try to determine what
     type of hardware it is running on (see Appendix C). If the program is
     running on a machine without snow, it can skip over any vertical and
     horizontal synchronization loops. The slight extra overhead of
     detecting the presence of a CGA is repaid in greatly improved
     performance on video subsystems that have no snow problem.


Using All the Video Buffer


     In alphanumeric video modes, the CGA, EGA, MCGA, and VGA have much
     more RAM available in their video buffers than is required to display
     one screen of text. In other words, you can display only a portion of
     the data in the video buffer at a time. In effect, what you see on the
     screen is a "window" on the video buffer.

     For example, in 80-by-25-character alphanumeric modes, only 4000 bytes
     (80 x 25 x 2 bytes per character) are displayed at any one time.
     However, the CGA has 16 KB of video RAM, so you can actually store
     four 80-by-25 screens of data in the buffer. You can then program the
     CGA's CRT Controller to display any 2000 consecutive characters (4000
     bytes) in the buffer.


CGA Video Pages

     To program the CGA to display different portions of the buffer, you
     update two CRT Controller registers. When you call the ROM BIOS to
     select a video display mode, the BIOS initializes the CRTC to display
     the first 4000 bytes of the video buffer. It does this by storing 0,
     the offset of the first character to be displayed, in the CRTC Start
     Address registers (0CH and 0DH).

     You can display any arbitrary portion of the CGA's video buffer by
     storing a video buffer offset in words (not bytes) in the CRTC Start
     Address registers. The high-order byte of the offset belongs in
     register 0CH, the low-order byte in register 0DH. For example, loading
     the Start Address registers with the word offset of the second row
     (50H) causes the display to begin there (see Listing 3-11).

     Loading the Start Address registers is a much faster operation than
     transferring characters into the video buffer. Thus, you might regard
     the 16 KB video buffer as a 102-line "virtual" screen of which only 25
     lines can be displayed at a time. When the video buffer is filled with
     text, you can rapidly display any 25 consecutive lines simply by
     changing the value in the CRTC Start Address registers.


���������������������������������������������������������������������������

     Listing 3-11.  Setting the CRTC Start Address registers.

���������������������������������������������������������������������������


     Whenever you update the Start Address registers, also update the BIOS
     Video Display Data Area word at 0040:004E (CRT_START). This helps to
     maintain functionality across video BIOS calls and with MS-DOS.

     Instead of deciding for yourself which portions of the video buffer to
     display, you might find it more convenient to adopt the conceptual
     model of the ROM BIOS, which supports four 80-by-25 (or eight 40-by-
     25) virtual "pages" in the CGA's video buffer. To simplify addressing,
     each page starts on a 1 KB (1024-byte) boundary. The four 80-by-25
     pages thus start at B800:0000, B800:1000, B800:2000, and B800:3000.
     You can selectively display any video page by calling INT 10H function
     05H (see Listing 3-12).


���������������������������������������������������������������������������

     Listing 3-12.  Video page selection using the ROM BIOS.

���������������������������������������������������������������������������


       ���ͻ     A technique that can improve CGA performance is to display
       � T �     one video page while you fill another (nondisplayed) video
       � I �     page with data. Then you display the newly filled video
       � P �     page and make the previous page available for more data
       ���ͼ     transfers. Design your user interface so that while the
                 user reads the display, a nondisplayed video page is
                 filled with the next screen of information. Careful use of
                 the video pages can make screen updates appear
                 "instantaneous."

                 You must still avoid display interference by using one of
                 the techniques for synchronizing the update with vertical
                 or horizontal blanking intervals, even if you write to a
                 nondisplayed portion of the buffer.


EGA, MCGA, and VGA Video Pages

     With the EGA, MCGA, and VGA, the techniques for using video RAM are
     similar to those used on the CGA. The Start Address registers in the
     CRT Controller are mapped to the same I/O port addresses as they are
     on the CGA's CRTC. Furthermore, the video BIOS supports video pages
     with the same interface used for the CGA. This simplifies writing a
     program to run on all of these video subsystems.

       ���ͻ     One handy feature of the CRTC on the EGA, the MCGA, the
       � T �     VGA, and some but not all CGA look-alikes is that the
       � I �     Start Address registers can be read as well as written.
       � P �     This feature can be useful in programming these registers
       ���ͼ     directly, because you can determine their contents at any
                 time simply by inspecting them.


Cursor Control


     The CRT Controller also controls the size and screen location of the
     hardware cursor in alphanumeric modes. You specify the cursor's size
     by loading a CRTC register with values that indicate its top and
     bottom lines. The top line is 0; the value for the bottom line depends
     on the size of the displayed character matrix--7 for an 8-by-8 matrix
     and 0DH for a 9-by-14 matrix. The cursor's location is specified with
     a word offset into the video buffer, exactly as you specify the CRT
     Controller's start address.


Cursor Size on the MDA and CGA

     CRTC registers 0AH and 0BH control the cursor size on all IBM PC and
     PS/2 video subsystems. On the MDA and the CGA, the low-order five bits
     of register 0AH (Cursor Start) indicate the top line of the displayed
     cursor. The low-order five bits of register 0BH (Cursor End) specify
     the bottom line.

     Changing the size of the hardware cursor is a matter of programming
     these two registers. For example, to display a "block" cursor, which
     is a rectangle filling an entire character space, set the Cursor Start
     register to 0 and the Cursor End register to one less than the height
     of the character matrix. To display the ROM BIOS's default cursor, set
     the Cursor Start and Cursor End registers to the values for the last
     two lines of the character matrix, as is done in Listing 3-13.

     In most applications, however, you can use INT 10H function 1 (Set
     Cursor Type) to change the cursor's size. Using this function ensures
     compatibility with the video BIOS on all IBM PC and PS/2 video
     subsystems. Although performing the software interrupt and executing
     the BIOS routine is slower than programming the CRTC directly, in
     general you modify the cursor size so infrequently that you'll never
     notice the slight slowing of your program.

     Also, the BIOS routine maintains the current cursor size in two bytes
     in the Video Display Data Area at 0040:0060 (CURSOR_MODE). On the MDA
     and CGA, the CRTC's Cursor Start and Cursor End registers are read-
     only registers, so you might as well use the BIOS to keep track of the
     current state of the cursor. The byte at 0040:0060 represents the
     value in 6845 register 0AH (Cursor Start), and the following byte, at
     0040:0061, represents register 0BH (Cursor End). If you do bypass the
     BIOS routine and program the 6845 directly, keep the values in
     CURSOR_MODE up to date.


���������������������������������������������������������������������������

     Listing 3-13.  Setting the cursor size.

���������������������������������������������������������������������������


Cursor Location on the MDA and CGA

     To control the cursor's location, load a buffer offset into the CRTC's
     Cursor Location High (0EH) and Cursor Location Low (0FH) registers
     (see Listing 3-14). The Cursor Location offset is relative to the
     start of the video buffer. If you have changed the CRTC Start Address
     registers, you must adjust for the new Start Address offset in
     calculating the Cursor Location offset.


���������������������������������������������������������������������������

     Listing 3-14.  Setting the cursor location.

���������������������������������������������������������������������������


MCGA Cursor Control

     The MCGA's CRTC doubles the values you store in the Cursor Start and
     Cursor End registers and doubles the number of scan lines in the
     displayed cursor. Thus, the size of the MCGA's alphanumeric cursor is
     a multiple of two scan lines.

     This doubling of the Cursor Start and Cursor End values allows you to
     specify default alphanumeric cursor sizes with the same values you
     would use on a CGA. For example, in the MCGA's default alphanumeric
     modes, the character matrix is 16 lines high. If you set Cursor Start
     to 6 and Cursor End to 7, as you would in a CGA alphanumeric mode, you
     see the MCGA's cursor at the bottom of the character matrix in lines
     0CH through 0FH. In this way the MCGA's Cursor Start and End registers
     emulate the CGA's despite the MCGA's taller character matrix.

     However, there are several differences in the way the MCGA interprets
     the Cursor Start and Cursor End values (see Figure 3-16). On the
     MCGA, only the four low-order bits of the Cursor Start and Cursor End
     values are significant. Furthermore, since the character matrix can be
     at most 16 scan lines high, Cursor Start and Cursor End values are
     usually limited to the range 0 through 7. Values greater than 7 can
     produce a cursor that wraps around to the top of the character matrix
     (see Figure 3-16e).


EGA and VGA Cursor Control

     On the EGA and the VGA, the Cursor Start, Cursor End, Cursor Location
     High, and Cursor Location Low registers are mapped to the same CRTC
     register numbers as on the MDA and CGA. This can lead to trouble if
     you're concerned about portability and need to write to the CRTC
     registers directly. This is because the EGA and VGA Cursor Start and
     Cursor End registers do not function exactly as do those on the MDA,
     CGA, or MCGA.

     On the EGA, the value you specify for the Cursor End register must be
     1 greater than the bottom line of the cursor (see Figure 3-17). The
     EGA's CRT Controller displays the alphanumeric cursor from the
     character scan line specified in the Cursor Start register to the line
     specified by the Cursor End register minus 1.

     If the Cursor End value is less than the Cursor Start value, the
     cursor wraps around the character matrix. If the low-order four bits
     of the Cursor Start and Cursor End values are equal, the cursor
     appears only on the single line specified in the Cursor Start
     register. Finally, the Cursor End value must be less than the number
     of scan lines in the character matrix. Otherwise, the CRT Controller
     displays a full-height cursor regardless of the Cursor Start
     register's value.


     Cursor Start = 2                   Cursor Start = 2
     Cursor End = 2                     Cursor End = 4
                      ���������������Ŀ                  ���������������Ŀ
      Scan line 0-1   � � � � � � � � �  Scan line 0-1   � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 2-3   � � � � � � � � �  Scan line 2-3   � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 4-5   �ۺۺۺۺۺۺۺ۳  Scan line 4-5   �ۺۺۺۺۺۺۺ۳
                      ���������������͵                  ���������������͵
      Scan line 6-7   � � � � � � � � �  Scan line 6-7   �ۺۺۺۺۺۺۺ۳
                      ���������������͵                  ���������������͵
      Scan line 8-9   � � � � � � � � �  Scan line 8-9   �ۺۺۺۺۺۺۺ۳
                      ���������������͵                  ���������������͵
      Scan line 10-11 � � � � � � � � �  Scan line 10-11 � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 12-13 � � � � � � � � �  Scan line 12-13 � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 14-15 � � � � � � � � �  Scan line 14-15 � � � � � � � � �
                      �����������������                  �����������������
     a.                                 b.


     Cursor Start = 3                   Cursor Start = 4
     Cursor End = 7                     Cursor End = 2
                      ���������������Ŀ                  ���������������Ŀ
      Scan line 0-1   � � � � � � � � �  Scan line 0-1   � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 2-3   � � � � � � � � �  Scan line 2-3   � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 4-5   � � � � � � � � �  Scan line 4-5   � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 6-7   �ۺۺۺۺۺۺۺ۳  Scan line 6-7   � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 8-9   �ۺۺۺۺۺۺۺ۳  Scan line 8-9   � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 10-11 �ۺۺۺۺۺۺۺ۳  Scan line 10-11 � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 12-13 �ۺۺۺۺۺۺۺ۳  Scan line 12-13 � � � � � � � � �
                      ���������������͵                  ���������������͵
      Scan line 14-15 �ۺۺۺۺۺۺۺ۳  Scan line 14-15 � � � � � � � � �
                      �����������������                  �����������������
     c.                                 d.


     Cursor Start = 3
     Cursor End = 8
                      ���������������Ŀ
      Scan line 0-1   �ۺۺۺۺۺۺۺ۳
                      ���������������͵
      Scan line 2-3   � � � � � � � � �
                      ���������������͵
      Scan line 4-5   � � � � � � � � �
                      ���������������͵
      Scan line 6-7   �ۺۺۺۺۺۺۺ۳
                      ���������������͵
      Scan line 8-9   �ۺۺۺۺۺۺۺ۳
                      ���������������͵
      Scan line 10-11 �ۺۺۺۺۺۺۺ۳
                      ���������������͵
      Scan line 12-13 �ۺۺۺۺۺۺۺ۳
                      ���������������͵
      Scan line 14-15 �ۺۺۺۺۺۺۺ۳
                      �����������������
     e.

     Figure 3-16.  Sample MCGA alphanumeric cursor settings for an 8-by-16
     character matrix.


     The VGA's Cursor Start and Cursor End values (see Figure 3-18) work
     slightly differently than do the EGA's. The VGA's Cursor End value
     indicates the last line of the displayed cursor (not the last line
     plus 1), and the displayed cursor does not wrap around to the top of
     the character matrix if the Cursor End value is less than the Cursor
     Start value. (Compare Figures 3-17 and 3-18.)


     Cursor Start = 4                     Cursor Start = 4
     Cursor End = 4                       Cursor End = 8
                  ���������������Ŀ                    ���������������Ŀ
      Scan line 0 � � � � � � � � �        Scan line 0 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 1 � � � � � � � � �        Scan line 1 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 2 � � � � � � � � �        Scan line 2 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 3 � � � � � � � � �        Scan line 3 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 4 �ۺۺۺۺۺۺۺ۳        Scan line 4 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 5 � � � � � � � � �        Scan line 5 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 6 � � � � � � � � �        Scan line 6 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 7 � � � � � � � � �        Scan line 7 �ۺۺۺۺۺۺۺ۳
                  �����������������                    �����������������
     a.                                   b.


     Cursor Start = 4                     Cursor Start = 4
     Cursor End = 7                       Cursor End = 2
                  ���������������Ŀ                    ���������������Ŀ
      Scan line 0 � � � � � � � � �        Scan line 0 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 1 � � � � � � � � �        Scan line 1 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 2 � � � � � � � � �        Scan line 2 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 3 � � � � � � � � �        Scan line 3 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 4 �ۺۺۺۺۺۺۺ۳        Scan line 4 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 5 �ۺۺۺۺۺۺۺ۳        Scan line 5 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 6 �ۺۺۺۺۺۺۺ۳        Scan line 6 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 7 � � � � � � � � �        Scan line 7 �ۺۺۺۺۺۺۺ۳
                  �����������������                    �����������������
     c.                                   d.


     Cursor Start = 4
     Cursor End = 0
                  ���������������Ŀ
      Scan line 0 � � � � � � � � �
                  ���������������͵
      Scan line 1 � � � � � � � � �
                  ���������������͵
      Scan line 2 � � � � � � � � �
                  ���������������͵
      Scan line 3 � � � � � � � � �
                  ���������������͵
      Scan line 4 �ۺۺۺۺۺۺۺ۳
                  ���������������͵
      Scan line 5 �ۺۺۺۺۺۺۺ۳
                  ���������������͵
      Scan line 6 �ۺۺۺۺۺۺۺ۳
                  ���������������͵
      Scan line 7 �ۺۺۺۺۺۺۺ۳
                  �����������������
     e.

     Figure 3-17.  Sample EGA alphanumeric cursor settings for an 8-by-8
     character matrix.


       ���ͻ     Bits 5 and 6 of the Cursor End register (0BH) on the EGA
       � T �     and VGA control the rightward skew of the cursor. If bits
       � I �     5 and 6 are not 0, the cursor appears one, two, or three
       � P �     characters to the right of the location that the Cursor
       ���ͼ     Location registers specify. For most applications, the
                 cursor skew should be 0.


     Cursor Start = 4                     Cursor Start = 4
     Cursor End = 4                       Cursor End = 8
                  ���������������Ŀ                    ���������������Ŀ
      Scan line 0 � � � � � � � � �        Scan line 0 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 1 � � � � � � � � �        Scan line 1 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 2 � � � � � � � � �        Scan line 2 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 3 � � � � � � � � �        Scan line 3 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 4 �ۺۺۺۺۺۺۺ۳        Scan line 4 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 5 � � � � � � � � �        Scan line 5 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 6 � � � � � � � � �        Scan line 6 �ۺۺۺۺۺۺۺ۳
                  ���������������͵                    ���������������͵
      Scan line 7 � � � � � � � � �        Scan line 7 �ۺۺۺۺۺۺۺ۳
                  �����������������                    �����������������
     a.                                   b.


     Cursor Start = 4                     Cursor Start = 4
     Cursor End = 7                       Cursor End = 2
                  ���������������Ŀ                    ���������������Ŀ
      Scan line 0 � � � � � � � � �        Scan line 0 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 1 � � � � � � � � �        Scan line 1 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 2 � � � � � � � � �        Scan line 2 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 3 � � � � � � � � �        Scan line 3 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 4 �ۺۺۺۺۺۺۺ۳        Scan line 4 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 5 �ۺۺۺۺۺۺۺ۳        Scan line 5 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 6 �ۺۺۺۺۺۺۺ۳        Scan line 6 � � � � � � � � �
                  ���������������͵                    ���������������͵
      Scan line 7 �ۺۺۺۺۺۺۺ۳        Scan line 7 � � � � � � � � �
                  �����������������                    �����������������
      c.                                  d.

     Figure 3-18.  Sample VGA alphanumeric cursor settings for an 8-by-8
     character matrix.


ROM BIOS Cursor Emulation

     The ROM BIOS routine for INT 10H function 01H uses the values in 80x86
     registers CH and CL to program the CRTC Cursor Start and Cursor End
     registers (see Listing 3-13). On an MDA or CGA, these values are
     simply copied into the CRTC registers. On an EGA or VGA, however, the
     BIOS can scale these values relative to an 8-line character matrix and
     program the CRTC with the scaled results. This scaling is called
     "cursor emulation" in IBM's technical manuals.

     When ROM BIOS cursor emulation is in effect, the values you specify to
     INT 10H function 01H represent the position of the start and end of
     the displayed cursor relative to an 8-line character matrix. When the
     actual character matrix is larger than 8 lines, the BIOS routine
     adjusts the Cursor Start and Cursor End values to maintain the
     cursor's relative location in the matrix.

     Consider what happens, for example, when you call INT 10H function 01H
     with CH = 6 and CL = 7. If the character matrix is 8 lines high, the
     cursor appears on the bottom two lines. (This is the usual cursor in
     200-line video modes.) If the character matrix is 14 lines high,
     however, the BIOS routine adjusts the Cursor Start and Cursor End
     values so that the cursor appears near the bottom of the matrix; that
     is, on lines 0BH and 0CH. Thus, cursor emulation allows programs that
     change the cursor size with INT 10H function 01H to run unchanged
     regardless of the size of the character matrix.

     The BIOS carries out cursor emulation in INT 10H functions whenever
     bit 0 of the Video Display Data Area INFO byte (0040:0087) is set to
     0. (This is the power-on default for both the EGA and the VGA.) You
     can disable cursor emulation by setting this bit to 1 before calling
     INT 10H function 01H. On the EGA, you must set and reset the bit
     directly, but on the VGA, you should use INT 10H function 12H to set
     the bit's value.

       ���ͻ     On the EGA, cursor emulation is implemented by adding 5 to
       � T �     any Cursor Start or Cursor End value greater than 4. This
       � I �     works well when the character matrix is the default 14
       � P �     lines high. For character matrices of other heights,
       ���ͼ     however, this simple algorithm breaks down and computes
                 the Cursor Start and Cursor End values incorrectly. You
                 should therefore disable cursor emulation when you program
                 the EGA's character generator to change the size of its
                 character matrix (see Chapter 10).

                 On the VGA, the cursor-emulation computation takes into
                 account the height of the character matrix, so the
                 emulated cursor is displayed correctly regardless of
                 character matrix dimensions.


An Invisible Cursor

     You can make the cursor "invisible" by programming the CRT Controller
     to display it at an offscreen location. Do this by setting the Cursor
     High and Cursor Low registers to a non-displayed buffer offset.
     Another way to make the cursor vanish is to load the Cursor Start and
     Cursor End registers with values below the displayed character matrix.
     On the MDA, CGA, and VGA, load the Cursor Start register with the
     value 20H to make the cursor disappear. On the EGA, set Cursor Start
     to a value greater than or equal to the number of lines in the
     character matrix and set Cursor End to 0 (see Listing 3-15).


���������������������������������������������������������������������������

     Listing 3-15.  An invisible alphanumeric cursor for IBM video
     subsystems.

���������������������������������������������������������������������������



                         4  Graphics Modes


                        Using Graphics Modes

                    Mapping Pixels to the Screen
                           CGA � HGC � EGA
                Hercules InColor Card � MCGA and VGA

                          Pixel Coordinates
                      Pixel Coordinate Scaling
                            Aspect Ratio

                      Pixel Display Attributes
                           CGA � HGC � EGA
                 Hercules InColor Card � MCGA � VGA



     This chapter covers the basics of graphics-mode programming on the
     CGA, EGA, MCGA, VGA, and Hercules cards. First the chapter describes
     how pixels are represented in the video buffer and how they are mapped
     to the screen. Then it focuses on pixel display attributes; that is,
     on how to determine a pixel's color, intensity, and blinking.


Using Graphics Modes


     In graphics modes, your program can manipulate the color of every
     pixel on the display. For this reason, graphics modes are sometimes
     called All Points Addressable (APA) modes. Because you have control
     over each pixel in the displayed image, you can construct complex
     geometric images, fill arbitrary areas of the screen with solid colors
     or blends of colors, and display animated images that move smoothly
     across the screen.

     Most programmers, however, use graphics modes only when pixel-by-pixel
     control over the screen is essential to an application. The reason:
     The price you pay for total control over the screen is increased
     source code complexity and decreased performance. A simple comparison
     of the amount of data required to display a full screen of information
     in alphanumeric and in graphics modes shows why.

     For example, to display 25 rows of 16-color, 80-column text in
     alphanumeric mode on an EGA, you need to store 4000 bytes (80 x 25 x
     2) in the video buffer. With a 350-line monitor, the text is displayed
     with 640-by-350-pixel resolution. Obtaining the same resolution in a
     16-color graphics mode requires 112,000 bytes (640 x 350 x 4 bits per
     pixel / 8 bits per byte). Obviously, a program that must manipulate
     112,000 bytes of data is more complex and slower than a program that
     manipulates only 4000 bytes.

     Of course, the performance penalty for using graphics-mode video
     output is less apparent when you use a faster computer, such as an
     80286-based or 80386-based machine whose CPU runs at a high clock
     speed. Still, before you leap into graphics-mode programming, you
     should carefully consider the alternatives. Alphanumeric modes are
     sufficient for displaying text and simple block graphics and, hence,
     for the majority of real-world applications.

       ���ͻ     An alternative in some applications is to use a video
       � T �     subsystem that has an alphanumeric character generator
       � I �     capable of displaying RAM-based character sets. (The EGA,
       � P �     MCGA, VGA, HGC+, and InColor Card all have this
       ���ͼ     capability.) With these subsystems, you can design
                 "characters" that are actually subunits of a larger
                 graphics image and then assemble the subunits into a
                 complete image in an alphanumeric mode. (Chapter 10
                 explains the technique in detail.)


Mapping Pixels to the Screen


     PC and PS/2 video subsystems store pixel data as groups of bits that
     represent pixel values. The color of each pixel on the display is
     determined, directly or indirectly, by its pixel value. Furthermore,
     no pixel value is ever represented by more than eight bits, so one or
     more pixels are mapped into every byte in the video buffer.

     The format of the pixel map or bit map in the video buffer depends on
     the number of bits required to represent each pixel, as well as on the
     architecture of the video RAM. Obviously, the number of colors that a
     given graphics mode can display at one time is determined by the
     number of bits used to represent each pixel.

     When pixel values are smaller than eight bits, pixels are mapped in
     bit fields from left to right across each byte. The leftmost pixel
     represented in a given byte is always found in that byte's high-order
     bit(s). This is true on all PC and PS/2 video subsystems.


Color Graphics Adapter

     On the CGA, each pixel is represented either by two bits, as in 320-
     by-200 4-color mode (see Figure 4-1a) or by one bit, as in 640-by-200
     2-color mode (see Figure 4-1b). Because two bits are used to
     represent pixels in 320-by-200 mode, a pixel can have any of four
     different pixel values, so this mode can display four different colors
     at a time. Only one bit is used to represent pixel values in 640-by-
     200 mode, so that mode can display only two colors at a time.


                  Bit fields in one byte
     �����������������������������������������������Ŀ
     �           �           �           �           �
     �  0     0  �  0     1  �  1     0  �  1     1  �
     �           �           �           �           �
     ����������������������������������������������
           �           �           �           �
         ��Ŀ       ��Ŀ       ��Ŀ       ��Ŀ
         �   �       �����       �����       ���۳ Pixels on screen
         �����       �����       �����       �����
     a.

                  Bit fields in one byte
     �����������������������������������������������Ŀ
     �           �           �           �           �
     �  0     0  �  0     1  �  1     0  �  1     1  �
     �           �           �           �           �
     ������������������������������������������
        �     �     �     �     �     �     �     �
      ��Ŀ ��Ŀ ��Ŀ ��Ŀ ��Ŀ ��Ŀ ��Ŀ ��Ŀ
      �   � �   � �   � ����� ����� �   � ����� ����� Pixels on screen
      ����� ����� ����� ����� ����� ����� ����� �����
     b.

     Figure 4-1.  Pixel mapping in CGA graphics modes.


     The pixel data is mapped in two interleaved halves of the CGA's 16 KB
     video buffer. Data for the 100 even-numbered scan lines starts at
     B800:0000, and data for the odd-numbered scan lines starts at
     B800:2000 (see Figure 4-2). If the scan lines are numbered
     consecutively from 0, the half of the video buffer in which the nth
     scan line is represented can be determined by calculating n MOD 2.

       ���ͻ     This two-way buffer interleave lets the CGA's CRT
       � T �     Controller display 200 lines of graphics data without
       � I �     overflowing the 7-bit CRTC vertical timing registers. In
       � P �     CGA graphics modes, the CRTC is set up to display 100 rows
       ���ͼ     of "characters," each two scan lines high. The top (even)
                 line of each character is derived from the first half of
                 the video buffer, and the bottom (odd) line is read from
                 the second half of the buffer.


     B800:0000 �������������������Ŀ                             ����������
               �                   ���������������� Scan line 0 ����������
          0050 �������V�����������Ĵ                             �����D����
               �      i            ������Ŀ  ������ Scan line 1 �����i����
          00A0 �������d�����������Ĵ      �  �                   �����s����
               �      e            ����Ŀ ��������� Scan line 2 �����p����
          00F0 �������o�������������    �    �                   �����l����
               �                   �    �    �  ��� Scan line 3 �����a����
                � � � B � � � � � �     �    �  �                �����y����
               �      u            �    ����������� Scan line 4 ����������
                � � � f � � � � � �          �  �                ����������
               �      f            �         �  � � Scan line 5 ����������
     B800:2000 �������e�����������Ŀ         �  � �
               �      r            �����������  � �
          2050 �������������������Ĵ            � �
               �                   �������������� �
          20A0 �������������������Ĵ              �
               �                   ����������������
          20F0 ���������������������
               �                   �
                � � � � � � � � � �
               �                   �
                � � � � � � � � � �
               �                   �
                � � � � � � � � � �

     Figure 4-2.  Video buffer interleave in CGA graphics modes.


Hercules Graphics Card

     In 720-by-348 graphics mode on the HGC and HGC+, pixel representation
     is similar to that in the CGA's 640-by-200 2-color graphics mode. One
     bit represents each pixel, so only two "colors" (pixel on or pixel
     off) are available.

     However, the HGC's 348 90-byte lines of pixel data are interleaved
     using four separate areas of the video buffer (see Figure 4-3), each
     containing 87 (348 / 4) lines of data. With this buffer organization,
     the area in the buffer in which the nth scan line is represented can
     be determined by n MOD 4.

     On Hercules video adapters, the four-way interleave allows the CRTC to
     be programmed to display 87 rows of characters which are four scan
     lines high. (See Listing 2-4 in Chapter 2.) Each of the four scan
     lines in a "character" is read from the corresponding location in one
     of the four interleaved portions of the video buffer.


     B000:0000 �������������������Ŀ                            ����������
               �                   ��������������� Scan line 0 ����������
          005A ���������V���������Ĵ                            �����D����
               �        i          �Ŀ  ���������� Scan line 1 �����i����
          00B4 ���������d����������� �  �                       �����s����
               �        e          � �  �    ����� Scan line 2 �����p����
                � � � � o � � � � �  �  �    �                  �����l����
               �                   � �  � �������� Scan line 3 �����a����
                � � � � B � � � � �  �  � �  �                  �����y����
               �        u          � ������������� Scan line 4 ����������
     B000:2000 ���������f���������Ŀ    � �  �                  ����������
               �        f          ������ �������� Scan line 5 ����������
          205A ���������e���������Ĵ      �� �                  ����������
               �        r          ��������� �  �� Scan line 6 ����������
          20B4 ���������������������      �  �  �               ����������
               �                   �   ����������� Scan line 7 ����������
                � � � � � � � � � �    �  �  �  �
               �                   �   �  �  �  �
                � � � � � � � � � �    �  �  �  �
               �                   �   �  �  �  �
     B000:4000 �������������������Ŀ   �  �  �  �
               �                   �����������  �
          405A �������������������Ĵ   �  �     �
               �                   ��������������
          40B4 ���������������������   �  �
               �                   �   �  �
                � � � � � � � � � �    �  �
               �                   �   �  �
                � � � � � � � � � �    �  �
               �                   �   �  �
     B000:6000 �������������������Ŀ   �  �
               �                   ��������
          605A �������������������Ĵ   �
               �                   �����
          60B4 ���������������������
               �                   �
                � � � � � � � � � �
               �                   �
                � � � � � � � � � �

     Figure 4-3.  Video buffer interleave in Hercules graphics mode.


Enhanced Graphics Adapter

     When the EGA is configured to emulate a CGA graphics mode, pixels are
     mapped in the video buffer just as they would be on the CGA. However,
     in the EGA's native graphics modes (200-line 16-color modes and all
     350-line modes), pixels are always mapped eight to a byte.

     This mapping is dictated by the architecture of the EGA's video
     buffer. The 256 KB video buffer consists of four 64 KB maps, or
     parallel banks of RAM. The maps are parallel in the sense that they
     occupy the same range of addresses in the CPU's address space; the
     EGA's Sequencer and Graphics Controller allow the maps to be accessed
     either individually or in parallel (more about this in Chapter 5).

     A pixel's value is determined by the values of the corresponding bits
     at the same byte offset and bit offset in each map (see Figure 4-4).
     For this reason, in graphics modes, the four maps are called bit
     planes. You might imagine each pixel's value as the result of
     concatenating one bit from the same location in each bit plane.

       ���ͻ     The relationship of memory maps to bit planes is altered
       � T �     in 350-line graphics modes on an IBM EGA equipped with
       � I �     only 64 KB of video RAM. (To bring IBM's original EGA up
       � P �     to 256 KB, you must install a piggyback board, called the
       ���ͼ     Graphics Memory Expansion Card.) When you use INT 10H
                 function 00H to select 640-by-350 graphics modes (mode
                 0FH or 10H) on an EGA with a 64 KB video buffer, video
                 buffer address decoding is altered so that even-numbered
                 addresses in the buffer reference the even-numbered maps
                 and odd-numbered addresses refer to odd-numbered maps
                 (see Figure 4-5).

                 In this way the four video buffer maps are chained
                 together, with maps 0 and 1 forming bit plane 0 and maps
                 2 and 3 forming bit plane 2. Routines that access pixels
                 in the video buffer must accommodate this relationship
                 between the bit planes and buffer addresses.


                   �����������������������������������������������Ŀ
Bit          Map 3 �  1  �  1  �  0  �  0  �  0  �  0  �  1  �  1  �
fields in          ������������������������������������������
correspond-         ���   ���   ���   ���   ���   ���   ���   ���
ing byte         ���������������������������������������Ŀ
in each    Map 2 �  0  �  1  �  0  �  1  �  0  �  0  �  1  �  0  �
map              ������������������������������������������
                  ���   ���   ���   ���   ���   ���   ���   ���
               ���������������������������������������Ŀ
         Map 1 �  1  �  0  �  1  �  0  �  1  �  0  �  0  �  1  �
               ������������������������������������������     Color
                ���   ���   ���   ���   ���   ���   ���   ���        Plane
             ���������������������������������������Ŀ      Enable
       Map 0 �  1  �  0  �  1  �  1  �  0  �  1  �  1  �  0  �    register
             ����������������������������������������������������Ŀ
                                                      �   0111   �
Pixel          1011  1100  0011  0101  0010  0001  1101  1010 ������������
values          �     �     �     �     �     �     �     �   ������
                                                   
AND with       0011  0100  0011  0101  0010  0001  0101  0010
Color Plane     �     �     �     �     �     �     �     �
Enable                                             
               �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ
Pixels on      ���   �۳   ���   �۳   ���   ���   �۳   ���
screen         ���   ���   ���   ���   ���   ���   ���   ���

     Figure 4-4.  Pixel mapping in native EGA graphics modes.


Bit fields
in corres-     �����������������������������������������������Ŀ
ponding   Map 2�  0  �  1  �  0  �  1  �  0  �  0  �  1  �  0  �
byte in        ������������������������������������������      Color
each map        ���   ���   ���   ���   ���   ���   ���   ���         Plane
             ���������������������������������������Ŀ       Enable
        Map 0�  1  �  0  �  1  �  1  �  0  �  1  �  1  �  0  �     register
             �����������������������������������������������������Ŀ
                                                      �    0101   �
Pixel          01    10    01    11    00    01    11    00   �������������
values          �     �     �     �     �     �     �     �   ������
                                                   
AND with bits  01    10    01    11    00    01    11    00   Note: Bits
2 and 0 of      �     �     �     �     �     �     �     �   2 and 0 mask
Color Plane     �     �     �     �     �     �     �     �   pixel values.
Enable          �     �     �     �     �     �     �     �   Bits 3 and 1
                                                      should be 0.
Pixels on      �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ
screen         ���   ���   ���   �۳   � �   ���   �۳   � �
               ���   ���   ���   ���   ���   ���   ���   ���
a.


Bit fields
in corres-     �����������������������������������������������Ŀ
ponding   Map 3�  1  �  1  �  0  �  0  �  0  �  0  �  1  �  1  �
byte in        ������������������������������������������Color
each map        ���   ���   ���   ���   ���   ���   ���   ���   Plane
             ���������������������������������������Ŀ  Enable
        Map 1�  1  �  0  �  1  �  0  �  1  �  1  �  0  �  1  �  register
             �����������������������������������������������������Ŀ
                                                      �   0101    �
Pixel          11    10    01    00    01    01    10    11   �������������
values          �     �     �     �     �     �     �     �   ������
                                                   
AND with bits  11    10    01    00    01    01    10    11   Note: Bits
2 and 0 of      �     �     �     �     �     �     �     �   2 and 0 mask
Color Plane     �     �     �     �     �     �     �     �   pixel values.
Enable          �     �     �     �     �     �     �     �   Bits 3 and 1
                                                      should be 0.
Pixels on      �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ   �Ŀ
screen         �۳   ���   ���   � �   ���   ���   ���   �۳
               ���   ���   ���   ���   ���   ���   ���   ���
b.

     Figure 4-5.  Video buffer maps in 350-line graphics modes (EGA with 64
     KB video RAM). Pixel values at even addresses are stored in maps 0 and
     2 (Figure 4-5a); pixels at odd addresses are stored in maps 1 and 3
     (Figure 4-5b).


     In native EGA graphics modes, there is no line-by-line interleaving of
     the pixel data in the video buffer, as in CGA and HGC graphics modes.
     Instead, rows of pixels are mapped linearly, just as rows of
     characters are mapped linearly in alphanumeric video modes.


Hercules InColor Card

     In its 720-by-348 graphics mode, the InColor Card's video buffer has
     four parallel maps organized as four parallel bit planes. As on the
     EGA, a pixel's value is determined by concatenating the corresponding
     bits in each of the bit planes. However, video buffer addressing is
     not linear, as it is on the EGA.

     Pixels are stored in the InColor Card's video buffer using the same
     four-way interleave that the HGC and HGC+ use. In the buffer, 348
     lines of 90 bytes (720 pixels) are mapped in a four-way interleave
     starting at B000:0000. The buffer also contains two video pages (as on
     the monochrome HGC), at B000:0000 and B000:8000. This aspect of the
     InColor Card's design preserves its symmetry with Hercules monochrome
     graphics cards but differentiates it from the EGA.


MCGA and VGA

     The PS/2 video subsystems support three graphics modes not found on
     earlier PC video adapters. The 640-by-480 2-color mode (MCGA and VGA)
     and 640-by-480 16-color mode (VGA only) resemble the native EGA
     graphics modes: Both use a linear bit map starting at A000:0000. A
     similar linear pixel map also is used in 320-by-200 (MCGA and VGA)
     256-color mode, with one important difference: Each byte in the video
     buffer represents one pixel (see Figure 4-6). Since there are eight
     bits to a byte, each pixel can have any of 256 (2^8) different colors.


     ����������������������Ŀ                  ��������������������������Ŀ
     �   8-bit pixel value  �   Logical AND    �      Video DAC Mask      �
     �                      ������������������Ĵ                          �
     ������������������������         �        ����������������������������
                                      
                        ��������������������������Ŀ
                        �        Video DAC         �
                        �   color register 0-0FFH  �
                        ����������������������������
                                      �
                                      
                    18-bit analog output to video display
                     (6 bits each for red, green, blue)

     Figure 4-6.  Color selection in MCGA and VGA 320-by-200 256-color
     mode.


       ���ͻ     On the VGA, 640-by-480 2-color mode is nearly identical to
       � T �     640-by-480 16-color mode. All four bit planes remain
       � I �     active in the 2-color mode even though one bit plane is
       � P �     sufficient to store a full screen of pixels. The only
       ���ͼ     difference between the two modes is that the video BIOS
                 makes only two palette colors available in the 2-color
                 mode, whereas it sets up 16 palette colors in the 16-color
                 mode.


Pixel Coordinates


     In graphics modes, the video buffer can be thought of as a flat, two-
     dimensional array of pixels with its origin at the upper left corner.
     What is visible on the screen is a subset of the pixels represented in
     the buffer. On the CGA, the video buffer can contain only one
     screenful of pixels, so the first byte in the buffer represents the
     pixels in the screen's upper left corner. On the EGA, MCGA, and VGA,
     however, the video buffer can store several screenfuls of pixels. You
     can thus select which portion of the video buffer appears on the
     screen.

     Every pixel on the screen can be identified by a unique pair of (x,y)
     coordinates relative to the screen's upper left corner. Each (x,y)
     pair also corresponds to a particular byte offset in the video buffer
     and a bit offset in that byte. Thus, given a pixel's (x,y) coordinates
     on the screen, you can compute where in the video buffer the pixel is
     represented.

     Converting from pixel coordinates to the corresponding byte and bit
     offsets is one of the most frequent operations in IBM video graphics
     programming. The program examples in Listings 4-1 through 4-5
     demonstrate how to do this efficiently and in a uniform manner.


���������������������������������������������������������������������������

     Listing 4-1.  Computing a pixel's address in 320-by-200 4-color

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 4-2.  Computing a pixel's address in 640-by-200 2-color

���������������������������������������������������������������������������


     Transforming pixel coordinates to a buffer offset involves simple
     logic. Begin by calculating the offset of the start of pixel row y.
     (For CGA and Hercules graphics modes, this calculation accounts for
     the interleaving of the video buffer.) To this value, add the byte
     offset of the xth pixel in the row. Finally, add the byte offset of
     the start of the displayed portion of the video buffer to obtain the
     final byte offset of the pixel.

     PixelByteOffset = RowOffset(y) + ByteOffset(x) + OriginOffset

     The bit offset of the pixel within the byte that contains its value
     depends only on the number of pixels represented in each byte of the
     video buffer. You could express the relationship this way:

     PixelBitOffset = PixelsPerByte - (x MOD PixelsPerByte) - 1

     However, it is more practical to represent a pixel's bit offset as a
     bit mask rather than as an ordinal bit number. This can be done easily
     with a table lookup (for example, an assembler XLAT instruction) or
     with a logical shift instruction. (This is why Listings 4-1 through
     4-4 return the bit offset as a number of bits to shift.)


���������������������������������������������������������������������������

     Listing 4-3.  Computing a pixel's address in Hercules graphics mode.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 4-4.  Computing a pixel's address in CGA and VGA graphics
     modes.

���������������������������������������������������������������������������


     Here is a high-level example of a pixel coordinate transformation for
     the CGA's 320-by-200 4-color graphics mode. As Figure 4-1a shows,
     each byte in the video buffer contains four pixels. At four pixels per
     byte, 80 bytes of data represent one row of 320 pixels. The origin of
     the screen--that is, the byte offset of the displayed portion of the
     buffer--is 0, since the CGA video buffer contains only one screenful
     of pixels.

     int PixelsPerByte = 4;
     int BytesPerRow = 80;
     int OriginOffset = 0;
     static int Masks[] = { 0xC0, 0x30, 0x0C, 0x03 };

     unsigned int x,y;
     unsigned int ByteOffset,BitMask;

     /* buffer interleave (0 or 0x2000) */
     ByteOffset = (y & 1) << 13;

     /* offset of start of row */
     ByteOffset += BytesPerRow * (y/2);

     /* byte offset in screen */
     ByteOffset += (x / PixelsPerByte) % BytesPerRow;

     /* byte offset in video buffer */
     ByteOffset += OriginOffset;

     BitMask = Masks[x % PixelsPerByte];

     The same routine in assembly language is much more efficient, because
     all arithmetic can be done in registers and register halves (refer to
     Listing 4-1). Also, if you know that the number of bytes per row of
     pixels is a constant, you  can further increase performance by
     performing multiplication and division as a sequence of bit shifts.

     For example, in Listing 4-5, the y-coordinate is multiplied by 320
     through a series of logical shift operations instead of a single MUL
     instruction. The resulting routine runs about 40 percent faster on the
     8086-based PS/2 Model 30 and about 10 percent faster on the 80286-
     based PS/2 Model 60. This optimization complicates the assembly code
     somewhat, but the speed gained is worth the effort--low-level routines
     such as those in Listings 4-1 through 4-5 may execute many thousands
     of times in a graphics-oriented application.


���������������������������������������������������������������������������

     Listing 4-5.  Computing a pixel's address in 320-by-200 256-color
     mode.

���������������������������������������������������������������������������


Pixel Coordinate Scaling

     One characteristic of most IBM graphics modes is that horizontal pixel
     resolution differs from vertical pixel resolution. For example, in a
     640-by-200 mode, a typical 200-line color monitor displays about 70
     pixels per horizontal inch, but only about 30 pixels per vertical
     inch.

     This discrepancy complicates the mapping of pixels in the display
     buffer to screen locations, as is shown in Figure 4-7. For example,
     in a 640-by-200 mode, a line drawn between the pixel at (0,0) in the
     screen's upper left corner and the pixel at (100,100) has a
     mathematical slope of 1, so you would expect it to be displayed at a
     45-degree angle from the display's top and left edges. However, the
     displayed line (line a, Figure 4-7) is "compressed" in the horizontal
     direction.

     Displaying a line at a 45-degree angle requires scaling the pixel
     coordinates to account for the discrepancy in vertical and horizontal
     resolution. In a 640-by-200 mode, the horizontal scaling factor is
     about 2.4 (horizontal resolution / vertical resolution). In the
     example, you would scale the x-coordinates of the endpoints to 0 (0 *
     2.4) and 240 (100 * 2.4). The scaled line (line b, Figure 4-7), with
     endpoints at (0,0) and (240,100), appears at a 45-degree angle on the
     screen.

     You must scale the (x,y) coordinates of all pixels in all geometric
     figures in all graphics modes--unless, of course, the scaling factor
     happens to be 1. Otherwise, squares appear as rectangles and circles
     as ellipses. Furthermore, you must adjust the scaling factor for the
     horizontal and vertical resolutions of each graphics mode. Figure 4-8
     is a table of the horizontal-to-vertical scaling ratios for graphics
     modes on IBM video subsystems with typical monitors.


     ����������������������������������Ŀ
     � (O,O)                            �
     �   ��������������������������Ŀ   �
     �   ���                        �   �
     �   �����������Ŀ              �   �
     �   ���Ŀ       �b.            �   �
     �   � a.�       �(240,100)     �   �
     �   �(100,100)                 �   �
     �   �                          �   �
     �   �                          �   �
     �   ����������������������������   �
     �                                  �
     ������������������������������������

     Figure 4-7.  Pixel coordinate scaling in 640-by-200 graphics.


����������������������������������������������������������������������������ķ
BIOS Mode   Mode                     Scaling Factor
Number      Description              (horizontal/vertical)
��������������������������������������������������������������������������
4,5         320-by-200 4-color       1.20
6           640-by-200 2-color       2.40
BIOS Mode   Mode                     Scaling Factor
Number      Description              (horizontal/vertical)
6           640-by-200 2-color       2.40
0DH         320-by-200 16-color      1.20
0EH         640-by-200 16-color      2.40
0FH         640-by-350 monochrome    1.26 (monochrome monitor)
10H         640-by-350 16-color      1.37
11H         640-by-480 2-color       1.00
12H         640-by-480 16-color      1.00
13H         320-by-200 256-color     2.40
            720-by-348 (Hercules)    1.43 (monochrome monitor)

     Figure 4-8.  Pixel scaling values for PC and PS/2 graphics modes. An
     aspect ratio of 1.33 (4:3) for color monitors, 1.45 for monochrome
     monitors, is assumed.


Aspect Ratio

     A related programming concern is the screen's aspect ratio--the ratio
     of a screen's width to its height. The color monitors commonly used
     with IBM video subsystems have aspect ratios of about 1.33 (4:3); for
     the typical green monochrome monitor, the aspect ratio is about 1.45.
     Because the screen is rectangular instead of square, the maximum
     potential width of a screen image exceeds its maximum potential
     height. This limitation must always be considered in scaling pixel
     coordinates.

       ���ͻ     One attractive feature of the MCGA, the VGA, and other
       � T �     video subsystems that offer 640-by-480 resolution is that
       � I �     horizontal resolution and vertical resolution are the same
       � P �     on a display with an aspect ratio of 4:3. You can think of
       ���ͼ     the pixels in this situation as being "square." With
                 "square" pixels, mapping the video buffer to the screen is
                 simpler because the pixel coordinate scaling factor is 1.


Pixel Display Attributes


     In general, pixel values determine video attributes--in other words,
     the bits that represent a pixel in the video buffer determine how the
     pixel looks on the screen. The way that pixel values are decoded in
     graphics modes is similar to the way that alphanumeric attributes are
     decoded. But in graphics modes, pixel values may range from one
     through eight bits, while alphanumeric attributes are four bits wide.


Color Graphics Adapter

     In 640-by-200 2-color mode, one bit represents each pixel. If the bit
     is 0, the pixel is displayed as black. If the bit is 1, the pixel is
     displayed with the color specified in bits 0 through 3 of the CGA's
     Color Select register (port 3D9H). This is the same register that
     specifies the overscan color in alphanumeric modes. If you change
     video modes by directly programming the CGA's CRTC and Mode Control
     registers, you should avoid spurious border colors or pixel colors by
     programming the Color Select register as well.

     You can use INT 10H function 0BH to select the displayed color of
     nonzero pixels in 640-by-200 2-color mode (see Listing 4-6). This BIOS
     function stores a color value in the Color Select register and updates
     the variable CRT_PALETTE in the Video Display Data Area at 0040:0066.
     If you bypass the video BIOS and program the Color Select register
     directly, you should also update CRT_PALETTE.


���������������������������������������������������������������������������

     Listing 4-6.  Foreground color in CGA 640-by-200 2-color graphics.

���������������������������������������������������������������������������


     In 320-by-200 4-color modes, two bits represent each pixel, so pixel
     values can range from 0 through 3. Pixels with the value 0 are
     displayed with the color value stored in the Color Select register at
     port 3D9H. A quirk of the CGA is that the Color Select register value
     determines both the overscan (border) color and the color for pixel
     value 0. This means you cannot specify a border color independently of
     the background color on the CGA in this video mode.

     The colors displayed for pixels with nonzero values are taken from one
     of three hardware palettes (see Figure 4-9). The palette is selected
     by the values of bit 5 of the Color Select register (port 3D9H) and of
     bit 2 of the Mode Control register at port 3D8H (Listing 4-7). If bit
     2 of the Mode Control register is 1, the palette comprises cyan, red,
     and white. If this bit is 0, bit 5 of the Color Select register
     selects either green, red, and yellow (if bit 2 in the Color Select
     register is 0), or cyan, violet, and white (if bit 2 in the Color
     Select register is 1). In effect, setting bit 2 in the Color Select
     register adds blue to the palette; that is, green plus blue produces
     cyan, red plus blue produces violet, and yellow plus blue produces
     white.

       ���ͻ     Setting bit 2 of the CGA's Mode Control register to 1
       � T �     disables the color burst component of the adapter's
       � I �     composite video output signal. If you use a black-and-
       � P �     white display, appropriate shades of gray are generated
       ���ͼ     for the four possible pixel values when bit 2 is set to 1.


���������������������������������������������������������������������������
Bit 2 of Mode Control register = 0
Pixel Value                    Color Displayed
���������������������������������������������������������������������������
Bit 5 of Color Select register = 0
1                              Green
2                              Red
3                              Yellow

Bit 5 of Color Select register = 1
1                              Cyan
2                              Violet
3                              White
���������������������������������������������������������������������������
Bit 2 of Mode Control register = 1
Pixel Value                    Color Displayed
���������������������������������������������������������������������������
1                              Cyan
2                              Red
3                              White

     Figure 4-9.  Palettes available in CGA 320-by-200 4-color mode.


���������������������������������������������������������������������������

     Listing 4-7.  Four-color palettes in CGA 320-by-200 4-color mode.

���������������������������������������������������������������������������


     You can use INT 10H functions to select among the three 4-color
     palettes. The video BIOS assigns two video mode numbers to 320-by-200
     4-color graphics mode: In BIOS mode 4, bit 2 of the Mode Control
     register is 0, and in mode 5, bit 2 is set to 1. Thus, to select the
     cyan-red-white palette, use INT 10H function 0 to set mode 5. To
     select the other two palettes, use INT 10H function 0 to set mode 4,
     and then call INT 10H function 0BH to choose either green-red-yellow
     or cyan-violet-white, as shown in Listing 4-8.


���������������������������������������������������������������������������

     Listing 4-8.  Four-color palettes in CGA 320-by-200 4-color mode using
     video BIOS.

���������������������������������������������������������������������������


     You can select high-intensity colors in the 320-by-200 4-color palette
     by setting bit 4 of the Color Select register to 1. When this bit is
     0, the same four colors are displayed with normal intensity.


Hercules Graphics Card

     Life is easy with an HGC as far as graphics attributes are concerned.
     In the 720-by-348 monochrome graphics mode on the HGC and HGC+, one
     bit represents each pixel. If the bit is set to 1, the pixel is
     displayed. If the bit is set to 0, the pixel is not displayed.


Enhanced Graphics Adapter

     Although the EGA supports a number of graphics modes with pixel values
     ranging from 1 to 4 bits, it decodes pixel values in a straightforward
     manner. As in alphanumeric modes, each pixel's value is masked by the
     value in the Attribute Controller's Color Plane Enable register; the
     resulting 4-bit value selects one of the Attribute Controller's 16
     palette registers. Thus, a pixel's displayed attribute is derived from
     the palette register that corresponds to the pixel value.

     When you use INT 10H function 0 to select an EGA video mode, the BIOS
     routine loads a default set of color values into the palette registers
     (see Figure 4-10). The actual values depend on the video mode, but
     each set maps the palette registers so that the color displayed for a
     given pixel value is the same as a CGA would display. Using this
     function improves the portability of programs between the CGA and the
     EGA, since a program that never touches the palette registers can run
     with the same set of colors on both adapters.

       ���ͻ     The BIOS default palette register values for 320-by-200
       � T �     and 640-by-200 16-color modes are correct for 200-line
       � I �     monitors but incorrect for some EGA-compatible monitors.
       � P �     IBM's Enhanced Color Display converts the 4-bit default
       ���ͼ     color values in 200-line graphics modes (see Figure 4-10)
                 to 6-bit color values that emulate the 16 CGA colors.
                 Unfortunately, not all EGA-compatible monitors do this.
                 Thus, if you use INT 10H function 0 to invoke these modes
                 (mode numbers 0DH and 0EH), you generally should program
                 the palette registers with an appropriate set of values,
                 such as the default set used in 640-by-350 16-color mode.


     CGA Emulation Modes
     In 640-by-200 2-color mode, when bit 3 of the Attribute Controller
     Mode Control register (10H) is 0, a pixel value of 0 designates
     palette register 0, and a pixel value of 1 designates palette register
     1. When Mode Control bit 3 is 1, palette registers 8 and 9 are used.
     With a CGA-compatible display, these four palette registers can
     contain any of the 16 displayable color values. With an EGA-compatible
     350-line monitor, these registers can contain any four of the 64
     displayable color values.


350-Line 16-Color Modes
����������������������������������������������������������������������������ķ
Palette Register  Color Value          Attribute
Palette Register  Color Value          Attribute
��������������������������������������������������������������������������
00H               00H                  Black
01H               01H                  Mid-intensity blue
02H               02H                  Mid-intensity green
03H               03H                  Mid-intensity cyan
04H               04H                  Mid-intensity red
05H               05H                  Mid-intensity violet
06H               14H                  Brown
07H               07H                  Mid-intensity white
08H               38H                  Low-intensity white (gray)
09H               39H                  High-intensity blue
0AH               3AH                  High-intensity green
0BH               3BH                  High-intensity cyan
0CH               3CH                  High-intensity red
0DH               3DH                  High-intensity violet
0EH               3EH                  High-intensity yellow
0FH               3FH                  High-intensity white


200-Line 16-Color Modes
����������������������������������������������������������������������������ķ
Palette Register  Color Value          Attribute
��������������������������������������������������������������������������
00H               00H                  Black
01H               01H                  Blue
02H               02H                  Green
03H               03H                  Cyan
04H               04H                  Red
05H               05H                  Violet
06H               06H                  Yellow (brown)
07H               07H                  White
08H               10H                  Black (gray)
09H               11H                  High-intensity blue
0AH               12H                  High-intensity green
0BH               13H                  High-intensity cyan
0CH               14H                  High-intensity red
0DH               15H                  High-intensity violet
0EH               16H                  High-intensity yellow
0FH               17H                  High-intensity white


640-by-350 Monochrome Graphics
����������������������������������������������������������������������������ķ
Palette Register  Color Value          Attribute
��������������������������������������������������������������������������
00H               00H                  Not displayed
01H               08H                  Normal intensity
04H               18H                  High intensity
05H               18H                  High intensity
08H               00H                  Not displayed
09H               08H                  Normal
0CH               00H                  Not displayed
0DH               18H                  High intensity

     Figure 4-10.  Default EGA and VGA palette register values.


     In 320-by-200 4-color mode, each of the four possible pixel values (0
     through 3) designates a corresponding palette register. When bit 3 in
     the Attribute Controller Mode Control register is 0, palette registers
     0-3 are used; when bit 3 is 1, palette registers 8-0BH are used. With
     a CGA-compatible monitor, you can store any eight of the 16
     displayable color values in these palette registers. With an EGA-
     compatible monitor, you can use any eight of the 64 displayable color
     values in these registers.

     In both CGA emulation modes, the video BIOS initializes the palette
     registers with default color values that match the colors in the CGA
     hardware palettes. In 640-by-200 2-color mode, the default colors are
     black, white, and intense white. In 320-by-200 4-color modes, the BIOS
     supports the green-red-yellow and cyan-violet-white palettes in normal
     and high intensities.


     16-Color Modes
     In 320-by-200, 640-by-200, and 640-by-350 16-color modes, each 4-bit
     pixel value designates one of the 16 palette registers. For a CGA-
     compatible monitor, the palette registers can contain the usual 16
     colors, but with an EGA-compatible monitor, you can specify any of the
     64 displayable colors in each palette register.


     Monochrome Graphics
     There are two bits per pixel in the EGA's 640-by-350 monochrome
     graphics mode, so pixel values can range from 0 through 3. However,
     this graphics mode uses only even-numbered bit planes, so the EGA's
     Attribute Controller interprets only the even-numbered bits of the
     usual 4-bit pixel value. Thus, bits 0 and 1 of a 2-bit monochrome
     pixel value designate bits 0 and 2 of the corresponding 4-bit palette
     register number. (Bits 1 and 3 of the palette register number are
     always 0.) Thus, the four possible pixel values--0, 1, 2, and 3--
     actually reference palette registers 0, 1, 4, and 5 respectively (see
     Figure 4-11).


����������������������������������������������������������������������������ķ
Pixel Value        Corresponding Palette Register
��������������������������������������������������������������������������
0 (00B)            0 (0000B)
1 (01B)            1 (0001B)
2 (10B)            4 (0100B)
3 (11B)            5 (0101B)

     Figure 4-11.  Pixel values and palette registers in 640-by-350
     monochrome graphics.


       ���ͻ     On EGAs with only 64 KB of video RAM, the odd bit planes
       � T �     represent pixels at odd buffer addresses, and the even bit
       � I �     planes represent pixels at even buffer addresses (see
       � P �     Figure 4-5). In this situation, pixel values in 640-by-
       ���ͼ     350 monochrome and 640-by-350 4-color graphics modes are
                 two bits in size, but bits 0 and 2 are used for pixels at
                 even byte addresses, while bits 1 and 3 are used for
                 pixels at odd byte addresses.

     Monochrome pixels can be undisplayed (palette register value 0), can
     be displayed with normal intensity (08H), or can be displayed with
     high intensity (18H). INT 10H function 00H loads the palette registers
     with a default set of monochrome values whenever you select video mode
     0FH (see Figure 4-11).


     Blinking
     In native graphics modes on the EGA (as well as on the VGA), pixels
     can have a blinking attribute. As in alphanumeric modes, you select
     blinking by setting the Enable Blink bit of the Attribute Controller's
     Mode Control register (bit 3 of register 10H at port 3C0H) to 1. In
     16-color modes, this causes the adapter to interpret the high-order
     bit (bit 3) of each 4-bit pixel value as a blink attribute, in the
     same way the high-order bit of a character's attribute byte is used in
     alphanumeric modes. Thus, when the Enable Blink bit is set, pixels
     with values 8 through 0FH blink, and pixels with values 0 through 7 do
     not. In monochrome graphics mode, all pixels blink regardless of their
     value.

     However, the EGA blinks pixels differently in graphics modes than it
     blinks characters in alphanumeric modes. In graphics modes, pixels are
     blinked by alternately selecting two different palette registers for
     each pixel's value. The two registers are designated by turning bit 3
     of the pixel value on and off at the blink rate (about twice per
     second). Thus, pixels are blinked by alternating the values in the
     first eight palette registers (registers 00H through 07H) with the
     values in the second eight (08H through 0FH).

     For example, a pixel with a value of 0AH is blinked by repeatedly
     changing the value of bit 3 whenever the Enable Blink bit is set.
     Thus, the pixel's color alternates between that designated by palette
     register 0AH (1010B) and that in palette register 02H (0010B). If you
     use the set of BIOS default palette registers, this pixel blinks
     between green and high-intensity green.

     A peculiarity of the EGA's blinking attribute in color graphics modes
     is what happens to pixels with values from 0 through 7; that is, where
     bit 3 of the pixel value is 0. These pixels do not blink, but they are
     displayed as if bit 3 were 1. For example, if you use the BIOS default
     palette values, pixels displayed at lower intensity (pixel values 0
     through 7) become nonblinking pixels displayed at high intensity using
     palette registers 08H through 0FH.

     Thus, in using the blinking attribute in graphics modes, you should
     reprogram the palette registers each time you change the Enable Blink
     bit, to maintain a consistent set of colors. For example, the palette
     register values shown in Figure 4-12 might be useful in this context.
     This palette is designed for use as an alternative to the default BIOS
     palette (see Figure 4-10) when blinking is enabled. If this palette
     is used with the Enable Blink bit set to 1, all high-intensity pixels
     (pixel values 08H through 0FH) blink, but all normal-intensity pixels
     do not.


     Border Color
     As in alphanumeric modes, you can set the overscan (border) color by
     storing a color value in the Attribute Controller's Overscan Color
     register (register 11H, port 3C0H). Techniques for setting the border
     color are covered in Chapter 3.


����������������������������������������������������������������������������ķ
Palette Register    Color Value    Attribute
��������������������������������������������������������������������������
00H                 00H            Black (background)

01H                 39H�
02H                 3AH�
03H                 3BH�
04H                 3CH�����������(high-intensity colors)
05H                 3DH�
Palette Register    Color Value    Attribute
05H                 3DH�
06H                 3EH�
07H                 3FH�

08H                 00H            Black (background)

09H                 01H�
0AH                 02H�
0BH                 03H�
0CH                 04H�����������(mid-intensity colors)
0DH                 05H�
0EH                 14H�
0FH                 07H�

     Figure 4-12.  Palette register values for blinking in 640-by-350
     16-color mode.


Hercules InColor Card

     On the InColor Card, the value of bit 4 of the Exception register
     (17H) determines whether the palette registers are used to decode
     pixel values, just as it does in alphanumeric modes. When this bit is
     set to 1, each 4-bit pixel value specifies a palette register, and the
     6-bit color value in the palette register determines the displayed
     color of the pixel.

     Setting Exception register bit 4 to 0 bypasses the palette registers.
     Each 4-bit pixel value is extended to 6 bits by replicating the high-
     order bit, and the new value determines the color. This procedure,
     called sign extension, in effect causes the high-order bit of a pixel
     value to act as an "intensity" bit, similar to the way alphanumeric
     attributes are decoded.


MCGA

     The MCGA emulates both of the CGA's graphics modes and adds two of its
     own, a 640-by-480 2-color mode and a 320-by-200 256-color mode. The
     256-color mode is the only MCGA video mode that uses the video Digital
     to Analog Converter (DAC) to full advantage.


     2-Color Graphics Modes
     Pixel attributes in 640-by-200 and 640-by-480 2-color modes are
     directed through the video DAC registers. Pixels with the value 0 are
     always mapped through video DAC color register 0. Nonzero pixels also
     select a predesignated video DAC color register, but this is done in
     one of two ways, depending on the value of bit 2 of the Mode Control
     register at 3D8H. If bit 2 is 1, video DAC color register 7 is
     selected. If bit 2 is 0, bits 0 through 3 of the Color Select register
     (port 3D9H) designate a video DAC register.

     On the MCGA, the background color in 2-color graphics modes is not
     necessarily black as it is on the CGA. Instead, both background and
     foreground can be any of the 256 K colors or the 64 gray-scale values
     that the MCGA can display. Use INT 10H function 10H to set the
     appropriate video DAC color registers.

       ���ͻ     When the video BIOS sets up 2-color graphics modes, it
       � T �     sets bit 2 of the Mode Control register to 0 and bits 0
       � I �     through 3 of the Color Select register to 1111B (0FH).
       � P �     Since the first 16 video DAC color registers contain the
       ���ͼ     16 colors available on a CGA, this configuration emulates
                 the default color configuration on a CGA in 640-by-200 2-
                 color mode: Background pixels are displayed as black (the
                 value in video DAC color register 0) and foreground pixels
                 appear intense white (the value in video DAC color
                 register 0FH).


     4-Color Graphics Mode
     The MCGA faithfully emulates this CGA graphics mode. The major
     difference is that the MCGA maps the four available colors through the
     video DAC color registers just as it does in 2-color graphics modes.
     Thus, all four colors can be selected from the 256 K possibilities
     that the video DAC offers.

     The MCGA combines bits 4 and 5 of the Color Select register (port
     3D9H) with each pixel's 2-bit value to create a 4-bit value that
     designates one of the first 16 video DAC color registers (see Figure
     4-13). The video BIOS initializes the video DAC color registers with
     CGA-compatible palettes. The colors are chosen so that bit 5 of the
     Color Select register selects the green-red-yellow and cyan-violet-
     white palettes, and bit 4 toggles between normal- and high-intensity
     palettes, as they do on the CGA. Of course, you can establish
     completely arbitrary 4-color palettes by loading different color
     values into the video DAC color registers.


����������������������������������������������������������������������������ķ
3D9H         Pixel Value    3D9H       Video DAC
Bit 4                       Bit 5      Color Register
(intensity)  Bit 1 Bit 0    (palette)  Number          Default Color
��������������������������������������������������������������������������
1           0     0        1         00H             Black

0            0     1        0          02H             Green
0            1     0        0          04H             Red
0            1     1        0          06H             Brown

1            0     1        0          0AH             High-intensity green
1            1     0        0          0CH             High-intensity red
1            1     1        0          0EH             High-intensity
3D9H         Pixel Value    3D9H       Video DAC
Bit 4                       Bit 5      Color Register
(intensity)  Bit 1 Bit 0    (palette)  Number          Default Color
1            1     1        0          0EH             High-intensity
                                                       yellow

0            0     1        1          03H             Cyan
0            1     0        1          05H             Violet
0            1     1        1          07H             White

1            0     1        1          0BH             High-intensity cyan
1            1     0        1          0DH             High-intensity
                                                       violet
1            1     1        1          0FH             High-intensity white

     Figure 4-13.  Pixel values and palettes in MCGA 320-by-200 4-color
     mode.


     256-Color Graphics Mode
     In 256-color mode, each pixel's value designates one of the 256 video
     DAC color registers. To select a video DAC color register, a pixel's
     value is combined (using a logical AND) with the value in the video
     DAC Mask register (3C6H). The resulting value selects a DAC color
     register (see Figure 4-6). Since you can store any of 256 K color
     values in each video DAC color register, you can display a wide range
     of tones and intensities and create quite realistic video images.


            �����������������Ŀ
      0-0FH � CGA-compatible  �
            � default colors  �
            �����������������Ĵ
     10-1FH �                 �
            �   gray scale    �
            �����������������Ĵ
            �   blue, red,    � High        �
            �     green       � saturation  �
            �-----------------�             �
     20-67H �   blue, red,    � Moderate    � High intensity
            �     green       � saturation  �
            �-----------------�             �
            �   blue, red,    � Low         �
            �     green       � saturation  �
            �����������������Ĵ
            �   blue, red,    � High        �
            �     green       � saturation  �
            �-----------------�             �
     68-AFH �   blue, red,    � Moderate    � Moderate intensity
            �     green       � saturation  �
            �-----------------�             �
            �   blue, red,    � Low         �
            �     green       � saturation  �
            �����������������ij
            �   blue, red,    � High        �
            �     green       � saturation  �
            �-----------------�             �
     B0-F7H �   blue, red,    � Moderate    � Low intensity
            �     green       � saturation  �
            �-----------------�             �
            �   blue, red,    � Low         �
            �     green       � saturation  �
            �����������������Ĵ
     F8-FFH �                 �
            �     black       �
            �������������������

     Figure 4-14.  Default video DAC colors in 320-by-200 256-color mode
     (MCGA and VGA).


     Normally, the video BIOS programs the video DAC registers with a
     default spectrum of color values (see Figure 4-14) when 320-by-200
     256-color mode is selected. Registers 0 through 0FH contain the
     default gamut of CGA-compatible colors. Registers 10H through 1FH
     contain a gray scale of gradually increasing intensity. The next 216
     registers (20H through F7H) contain three groups of 72 colors, with
     the first group (registers 20H through 67H) at high intensity, the
     second (registers 68H through AFH) at an intermediate intensity, and
     the third (registers B0H through F7H) at low intensity. Each 72-color
     group is made up of three ranges of colors of decreasing saturation
     (increasing whiteness); each range varies smoothly in hue from blue to
     red to green.

       ���ͻ     To disable or enable default video BIOS programming of the
       � T �     video DAC color registers, use INT 10H function 12H (see
       � I �     Appendix A).
       � P �
       ���ͼ


VGA

     As on the EGA, VGA pixel values are decoded by the Attribute
     Controller, using the palette registers, and then passed to the video
     DAC, following the same logic as in alphanumeric modes (see Chapter
     3). Thus, a pixel value selects the corresponding palette register;
     the value in the palette register, along with the bit fields in the
     Attribute Controller's Color Select register, selects one of the 256
     video DAC color registers. The video DAC converts the 18-bit RGB value
     in its color registers to the corresponding analog RGB signals, which
     drive the monitor.

     The only exception to this scheme of attribute decoding occurs in 320-
     by-200 256-color mode. In this mode, as on the MCGA, each 8-bit pixel
     value specifies one of the video DAC's 256 color registers directly,
     without the Attribute Controller's mediation.



                        5  Pixel Programming


                        Bit-Plane Programming
                     EGA and VGA � InColor Card

                       Reading a Pixel's Value
                      CGA � HGC and HGC+ � EGA
                      InColor Card � MCGA � VGA

                       Setting a Pixel's Value
                      CGA � HGC and HGC+ � EGA
                      InColor Card � MCGA � VGA

                      Filling the Video Buffer
                  CGA � HGC and HGC+ � EGA and VGA
                         InColor Card � MCGA



     Many graphics programming techniques are based on routines that
     manipulate individual pixels in the video buffer. This chapter
     presents the fundamentals of pixel programming: reading a pixel's
     value, setting the value of a pixel in the video buffer, and
     initializing an area of the video buffer with a pattern of pixels.


Bit-Plane Programming


     There is a fundamental difference between graphics-mode programming
     using video subsystems whose video RAM is organized as parallel bit
     planes (the EGA, the VGA, and the InColor Card) and graphics-mode
     programming for the other IBM video subsystems. On the CGA, the MCGA,
     or the Hercules monochrome adapter, your program accesses pixels by
     directly reading and writing bytes in video RAM. In contrast, in
     native graphics modes on the EGA, VGA, or InColor Card, your program
     cannot access video RAM directly. Instead, special hardware logic in
     the video subsystem mediates accesses to pixels in the bit planes.

     The graphics-mode bit planes on the EGA, VGA, and InColor Cards are
     addressed in parallel; that is, when you execute a CPU read or write
     at a particular address in the video buffer, the address refers not to
     one byte, but to four bytes, one in each of the bit planes.

     When you execute an 80x86 instruction that attempts to read data from
     an address in the video buffer, four bytes of data are actually moved
     out of the buffer. The data does not go directly to the CPU, however.
     Instead, it is copied into a set of four 8-bit latches. Each latch is
     assigned to one of the four bit planes. Executing an 8-bit CPU read
     from an address in the video buffer thus has the effect of
     transferring four bytes (32 bits) of data from the video buffer into
     the latches (see Figure 5-1a). Instructions such as MOV reg,mem,
     LODS, and CMP reg,mem require a CPU read, and thus cause the latches
     to be updated.

     Similarly, instructions such as MOV mem,reg, STOS, and XOR mem,reg
     cause a CPU write; in this case, all four bit planes can be updated in
     parallel using a combination of the data in the latches, the data byte
     that the CPU writes, and a predefined pixel value stored in a graphics
     control register (see Figure 5-1b).

     Some CPU instructions require both a CPU read and a CPU write. (The
     CPU reads a value from memory, performs an operation on it, and then
     writes the result back to memory.) MOVS is an obvious example, but
     OR mem,reg, AND mem,reg, and XOR mem,reg also generate a CPU read and
     write. When such an instruction refers to an address in video RAM, the
     latches are updated during the CPU read, and then the bit planes are
     updated during the CPU write.

     The use of latches to process bit-plane data in parallel lets you
     write deceptively simple code. For example, consider the following
     fragment, which copies the second byte of pixels in the video buffer
     to the first byte.


       ��Ŀ
       �  �
       �  ��Ŀ
       ����  �
             �
       ��Ŀ  �    �����������������������Ŀ �Ŀ
       �  �  ��� �1  1  0  0  0  0  1  1 �   �
       �  ��Ŀ    �����������������������Ĵ   �
       ����  ��� �0  1  0  1  0  0  1  0 �   � �����������������������Ŀ
bit               �����������������������Ĵ   ��x  x  x  x  x  x  x  x �
planes ��Ŀ  ��� �1  0  1  0  1  1  0  1 �   � �������������������������
       �  ����    �����������������������Ĵ   �    8-bit CPU register
       �  �  ��� �1  0  1  1  0  1  1  0 �   �
       ����  �    ������������������������� ���
             �       four 8-bit latches
       ��Ŀ  �
       �  ����
       �  �
       ����
     a.

    �����������������������Ŀ                                  ��Ŀ
    �x  x  x  x  x  x  x  x ����Ŀ                             �  �
    �������������������������    �        ���������������������  �
       8-bit CPU register        �        �                    ����
                                 �        �
                                 �        �                    ��Ŀ
                                 �        �                    �  �
                                 �        �   �����������������  �
    �����������������������Ŀ    �        �   �                ����
    �1  1  0  0  0  0  1  1 �������������   �                      bit
    �����������������������Ĵ    �  �         �                ��Ŀ  planes
    �0  1  0  1  0  0  1  0 �����������������   �������������  �
    �����������������������Ĵ    �  �  �          �            �  �
    �1  0  1  0  1  1  0  1 ���������������������            ����
    �����������������������Ĵ    �  �  �  �
    �1  0  1  1  0  1  1  0 �������������������Ŀ            ��Ŀ
    �������������������������       �  �  �  �    �            �  �
       four 8-bit latches           �  �  �  �    �������������  �
                       �������������������Ŀ               ����
                       �            x  x  x  x �
                       �������������������������
                            4-bit pixel data
                     (EGA, VGA: Set/Reset register)
     b.           (InColor: Read/Write Color register)

     Figure 5-1.  Graphics mode data flow on the EGA, the VGA, and the
     InColor Card during CPU (a.) read and (b.) write.


        mov     ax,VideoBufferSegment
        mov     ds,ax
        mov     es,ax
        mov     si,1     ; DS:SI -> second byte
        mov     di,0     ; ES:DI -> first byte
        movsb


     This code looks straightforward. The MOVSB instruction apparently
     copies one byte from the memory location at DS:SI to the location at
     ES:DI--but this is not really what takes place in graphics modes that
     use bit planes in the EGA, VGA, or InColor video buffer.

     What actually happens is this: The MOVSB instruction causes a CPU
     read, followed by a CPU write. Because the CPU read references an
     address in the video buffer, a byte from each bit plane at that
     address is loaded into the latches. Then, because the CPU write
     references an address in the video buffer, the contents of the latches
     are copied into the bit planes at the specified address. Thus, the
     MOVSB actually causes four bytes of data to be moved instead of one.

       ���ͻ     There is more to this example than meets the eye. Consider
       � T �     what would happen if you substituted a MOVSW instruction
       � I �     for the MOVSB. Without bit planes and latches, this would
       � P �     result in two bytes of data being copied instead of one
       ���ͼ     byte. However, half of the pixel data would be lost on the
                 EGA, the VGA, or the InColor Card. The reason is that the
                 MOVSW executes as a sequence of two 8-bit CPU reads,
                 followed by two 8-bit CPU writes, so the second CPU read
                 updates the latches before the bytes latched by the first
                 CPU read can be written.

                 For this reason, you should use 16-bit 80x86 instructions
                 cautiously when accessing the video buffer on the EGA, the
                 VGA, and the InColor Card. Instructions such as
                 OR mem,reg, AND mem,reg, and XOR mem,reg do not work
                 properly with 16-bit data.

     The latches clearly improve efficiency in moving data to and from the
     video buffer, but the real fun begins in transferring data between the
     latches and the CPU. Since the latches contain 32 bits of data and a
     CPU byte register contains only eight bits, some sort of data
     compression must take place during CPU reads. Conversely, in
     transferring data from the CPU to the bit planes, you can combine the
     8-bit CPU data byte with the contents of all four latches in a number
     of ways. The key to graphics-mode programming on the EGA, the VGA, and
     the InColor Card is to exploit the data transformations involving the
     CPU and the latches.


EGA and VGA

     On the EGA and VGA, the Graphics Controller manages all transfers of
     data among the CPU, the latches, and the video buffer. The EGA's
     Graphics Controller consists of two LSI chips; the VGA's is part of
     the Video Graphics Array chip. The Graphics Controller has nine
     registers addressable at port 3CFH via an address register at port
     3CEH. The values you store in the registers control the way the
     Graphics Controller processes latched data during CPU reads and
     writes.

     In a sense, the Graphics Controller lets you manipulate the latched
     pixel data two-dimensionally. Some of the operations you can perform
     on the latched data are byte-oriented; they affect each latch
     separately. Other operations are pixel-oriented in that they regard
     the latched data as a set of eight pixel values; these operations
     affect each pixel value separately.

     The Graphics Controller can perform three different byte-oriented
     operations on latched data. It can copy the contents of the latches to
     and from the video buffer; this action occurs implicitly when a CPU
     write or read is executed. It can return the contents of one of the
     latches to a CPU register during a CPU read. It can also combine a
     data byte from a CPU register with the bytes in any or all of the
     latches during a single CPU write.

     The Graphics Controller also processes latched data pixel by pixel.
     During a CPU read, the Graphics Controller can compare each latched
     pixel value with a predefined value and return the result of the
     comparison to the CPU. During CPU writes, it can combine a 4-bit CPU
     value with any or all pixel values in the latches; it can use an 8-bit
     CPU value as a mask that indicates which of the eight latched pixels
     are copied back to the bit planes; and it can combine the latched
     pixel values with a predefined 4-bit value.

     Both byte-oriented and pixel-oriented operations are programmed by
     selecting a write mode and a read mode. Each write mode sets up a
     predefined sequence of byte-oriented and pixel-oriented operations
     which occur when a CPU write is executed. Similarly, each read mode
     defines a set of actions performed during CPU reads. The EGA has three
     write modes and two read modes; the VGA has these five modes and one
     additional write mode.

     Until you become familiar with each of the Graphics Controller's read
     and write modes, their raison d'etre may seem a bit obscure. However,
     each mode has practical advantages in certain programming situations,
     as the examples in this and subsequent chapters demonstrate.

     The Graphics Controller's Mode register (05H) contains two bit fields
     whose values specify the graphics read and write mode. For example, to
     establish read mode 1 you would set bit 3 of the Mode register to 1;
     to set up write mode 2, you would store the value 2 (10B) in bits 0
     and 1 of the Mode register (Listing 5-1).


���������������������������������������������������������������������������

     Listing 5-1.  How to set Graphics Controller read and write modes.
     This example sets read mode 0 and write mode 1 in in 640-by-350 16-
     color mode.

���������������������������������������������������������������������������


       ���ͻ     The video BIOS default values for the Graphics
       � T �     Controller's Mode register and its other registers are
       � I �     listed in Figure 5-2. It is good practice to restore the
       � P �     Graphics Controller registers to their default values
       ���ͼ     after you modify them in your program.


����������������������������������������������������������������������������ķ
Register    Function             Value
��������������������������������������������������������������������������
0           Set/Reset            0
1           Enable Set/Reset     0
2           Color Compare        0
3           Data Rotate          0
4           Read Map Select      0
5           Mode                 Bits 0-3 always 0
6           Miscellaneous        (depends on video mode)
7           Color Don't Care     0FH (16-color modes)
                                 01H (640-by-480 2-color mode)
8           Bit Mask             FFH

     Figure 5-2.  Default ROM BIOS values for EGA and VGA Graphics
     Controller registers.


     Read mode 0
     In graphics read mode 0, the Graphics Controller returns the contents
     of one of the four latches to the CPU each time a CPU read loads the
     latches (see Figure 5-3). The value in the Read Map Select register
     (04H) indicates which latch to read. Read mode 0 thus lets you read
     bytes from each individual bit plane; this is useful in transferring
     data between the bit planes and system RAM or a disk file.


                                               Read Map Select register
                                              �����������������������Ŀ
                                              �x  x  x  x  x  x  0  0 �
                                              �������������������������
                                                                 ����
                                           �������������������������
              �����������������������Ŀ    �
       �������1  1  0  0  0  0  1  1 �3���
              �����������������������Ĵ
       �������0  1  0  1  0  0  1  0 �2
bit           �����������������������Ĵ
planes �������1  0  1  0  1  1  0  1 �1               CPU data
              �����������������������Ĵ       �����������������������Ŀ
       �������1  0  1  1  0  1  1  0 �0������1  0  1  1  0  1  1  0 �
              �������������������������       �������������������������

     Figure 5-3.  EGA and VGA graphics read mode 0.


     Read Mode 1
     In graphics read mode 1, each of the eight pixel values latched during
     a CPU read is compared with the value in the Color Compare register
     (02H). The result of the comparison is returned to the CPU as a single
     byte (see Figure 5-4). Where a pixel value matches the Color Compare
     value, a bit in the CPU data byte is set to 1; where the values are
     different, the corresponding bit in the data byte is 0.

     Note how the value in the Color Don't Care register (07H) interacts
     with the pixel value and Color Compare value. In effect, setting a bit
     to 0 in the Color Don't  Care value excludes a latch from the
     comparison. For example, a Color Don't Care value of 0111B causes only
     the three low-order bits of each pixel value to participate in the
     comparison. Another example: If you store a 0 in the Color Don't Care
     register, all four bits in the comparison become "don't care" bits, so
     all pixel values match the Color Compare value, and the CPU always
     reads the value 11111111B in read mode 1.


                                                 Color Don't Care register
                                                 �����������������������Ŀ
��bit planes                                     �x  x  x  x  1  1  1  1 �
�                            latches             �������������������������
�        �����������������������������������������������Ŀ    ����������
������  �  1     1    �0��   0     0    �0��   1     1  �         �
�        �����������������������������������������������Ĵ         �
������  �  0     1    �0��   1     0    �0��   1     0  �         �
�        �����������������������������������������������Ĵ         �
������  �  1     0    �1��   0     1    �1��   0     1  �         �
�        �����������������������������������������������Ĵ         �
������  �  1     0    �1��   1     0    �1��   1     0  �         �
         �������������������������������������������������         �
                                                           �
pixel      1011  1100  0011  0101  0010  0011  1101  1010          �
values      ����ij���ij���ij���ij���ij���ij���ij����������ij
                                                           �
AND with   1011  1100  0011  0101  0010  0011  1101  1010          
Color       ����ij���ij���ij���ij���ij���ij���ij�������������Ŀ
Don't                                                         �
Care    ������������������������������������������������Ŀ            �
        �   0     0    �1��   0     0    �1��   0     0  �            �
        ��������������������������������������������������            �
                           CPU data byte                       ����������
                                                  �����������������������Ŀ
                                                  �x  x  x  x �0��0��1��1��
                                                  �������������������������
                                                   Color Compare register

     Figure 5-4.  EGA and VGA graphics read mode 1.


     Write mode 0
     Graphics write mode 0 sets up a combination of byte-oriented and
     pixel-oriented operations that occur when a CPU write is executed. The
     data byte written by the CPU can be used to update any or all of the
     bit planes; at the same time, a predefined pixel value can be used to
     update any or all of the eight pixels involved. This two-dimensional
     update of the latches is controlled in several different ways using
     the values in the Enable Set/Reset, Data Rotate/Function Select, and
     Bit Mask registers (see Figure 5-5).

     The Bit Mask register (08H) specifies how the new value of each of the
     eight pixels in the video buffer is derived. Where a bit in the Bit
     Mask register equals 0, the corresponding pixel value is copied
     directly from the latches into the video buffer. For each 1 bit in the
     Bit Mask value, the corresponding pixel is updated with the latched
     pixel value combined with either the CPU data or the pixel value in
     the Set/Reset register. Thus, if a CPU write immediately follows a CPU
     read at the same address, the only pixels updated are those for which
     the corresponding bit in the Bit Mask register is set to 1.


             Data Rotate/Function Select register
                  �����������������������Ŀ
                  �x  x  x  0  0  x  x  x �
                  �������������������������
      replace, AND. OR, XOR ����           �����������������������Ŀ
                             �          ���1  1  0  0 �0��0��0��0���
        latches              �       ����  ������������������������ٱ
�����������������������Ŀ�������������  �����������������������Ŀ   �
�1  1  0  0  0  0  1  1 ����������������0  1  0  1 �1��1��1��1��   �
�����������������������Ĵ         ���� �������������������������   �bit
�0  1  0  1  0  0  1  0 ������������  �����������������������Ŀ     �planes
�����������������������Ĵ�        ���1  0  1  0 �1��1��1��1��     �
�1  0  1  0  1  1  0  1 ������������  �������������������������     �
�����������������������Ĵ�  �      �����������������������Ŀ       �
�1  0  1  1  0  1  1  0 ������������1  0  1  1 �1��1��1��1��       �
������������������������ٳ  �  �   �������������������������
                       ������������  |  |  |  |  |  |  |  |
Set/Reset �������������������Ŀ ���������������Ŀ
register  �x  x  x  x �0��1��1��1�� �0  0  0  0  1  1  1  1 �
          ������������������������� �������������������������
           |  |  |  |  |  |  |  |      Bit Mask register
Enable     |  |  |  |  |  |  |  |      (Pixels 0-3 are derived from
Set/Reset ���������������Ŀ    Set/Reset; pixels 4-7 are
register  �x  x  x  x  1  1  1  1 �    copied from latches.)
          �������������������������
a.

                                           �����������������������Ŀ
                                        ���1  1  0  0 �0��1��1��1���
        latches                      ����  ������������������������ٱ
�����������������������Ŀ            �  �����������������������Ŀ   �
�1  1  0  0  0  0  1  1 ����������������0  1  0  1 �0��1��1��1��   �
�����������������������Ĵ         ���� �������������������������   �bit
�0  1  0  1  0  0  1  0 �����������������������������������Ŀ       �planes
�����������������������Ĵ       ���1  0  1  0 �0��1��1��1��       �
�1  0  1  0  1  1  0  1 ����������  �������������������������       �
�����������������������Ĵ         �����������������������Ŀ        �
�1  0  1  1  0  1  1  0 �����������1  0  1  1 �0��1��1��1��        �
�������������������������         �������������������������
        replace, AND, OR, XOR �     |  |  |  |  |  |  |  |
           � � � � � � � � � ij    ���������������Ŀ
           |                  �    �0  0  0  0  1  1  1  1 �
           �              00010111 �������������������������
           |         Rotate        Bit Mask register (Pixels 0-3
           �       � � � � � ij     are derived from CPU data; pixels
           |       |          �     4-7 are copied from latches.)
          ����  �������       �
�����������������������Ŀ ����������������������Ŀ
�x  x  x  0  0  0  0  0 � �0  0  0  1 �0��1��1��1��
������������������������� �������������������������
  Data Rotate/Function          CPU data byte
     Select register
b.

     Figure 5-5.  EGA and VGA graphics write mode 0: (a.) Enable Set/Reset
     Value = 1111B, (b.) Enable Set/Reset value = 0000B.


     The Data Rotate/Function Select register (03H) contains two bit fields
     whose contents affect the way the latched pixels are updated. Bits 3
     through 4 are important because their value specifies which bitwise
     logical operation (AND, OR, XOR, or replace) is used to update the
     pixels (see Figure 5-6). Bits 0 through 2 specify the number of bits
     by which to right-rotate the CPU data byte before combining it with
     latched data.


����������������������������������������������������������������������������ķ
    Bit Value            Function
Bit 4       Bit 3
��������������������������������������������������������������������������
    Bit Value            Function
Bit 4       Bit 3
��������������������������������������������������������������������������
0           0            Replace
0           1            AND
1           0            OR
1           1            XOR

     Figure 5-6.  Functions available for updating pixels in EGA and VGA
     write modes 0, 2, and 3. Bits 3 and 4 of the Data Rotate/Function
     Select register specify which is used.


       ���ͻ     This data-rotate capability is not particularly useful. In
       � T �     practice, it is generally easier to let the CPU rotate and
       � I �     shift data before writing it to the bit planes than it is
       � P �     to program the Graphics Controller to do this.
       ���ͼ

     The value in the Enable Set/Reset register (register 01H) determines
     whether the bit planes are updated byte by byte or pixel by pixel.
     When the Enable Set/Reset value is 0FH (1111B), each pixel is updated
     by combining the latched pixel value with the value in the Set/Reset
     register (register 00H) using the logical operation that the Data
     Rotate/Function Select register specifies (refer to Figure 5-5a).
     When the Enable Set/Reset value is 0, the rotated CPU data byte is
     combined with the bytes in each of the latches, again using the
     function that the Data Rotate/Function Select register specifies (see
     Figure 5-5b). In either case, only the pixels masked by the Bit
     Mask register are updated.

       ���ͻ     Of course, you can set the Enable Set/Reset register to
       � T �     any value from 0 through 0FH. Each bit in each pixel is
       � I �     then updated by combining it either with the corresponding
       � P �     bit in the Set/Reset register or with the corresponding
       ���ͼ     bit in the CPU data byte--depending on the value of the
                 corresponding bit in the Enable Set/Reset register.
                 Needless to say, this kind of programming is tricky and
                 infrequently used.


     Write mode 1
     In write mode 1, the latches are copied directly to the bit planes
     when a CPU write occurs (see Figure 5-7). Neither the value of the
     CPU data byte nor those of the Data Rotate/Function Select, the Bit
     Mask, the Set/Reset, and the Enable Set/Reset registers affect this
     process. Clearly, for a write mode 1 operation to make sense, you must
     first perform a CPU read to initialize the latches.


             latches
     �����������������������Ŀ
     �x  x  x  x  x  x  x  x ��������������
     �����������������������Ĵ
     �x  x  x  x  x  x  x  x ��������������
     �����������������������Ĵ               bit planes
     �x  x  x  x  x  x  x  x ��������������
     �����������������������Ĵ
     �x  x  x  x  x  x  x  x ��������������
     �������������������������

     Figure 5-7.  EGA and VGA graphics write mode 1.


     Write mode 2
     In write mode 2, the low-order bits of the byte written by the CPU
     play the same role as the Set/Reset register value in write mode 0.
     That is, the bit planes are updated by combining the pixel values in
     the latches with the CPU data, using the logical operation specified
     in the Data Rotate/Function Select register (see Figure 5-8). As in
     write mode 0, the Bit Mask register specifies which pixels are updated
     using the combined pixel values and which pixels are updated directly
     from the latches.


             Data Rotate/Function Select register
                   �����������������������Ŀ
                   �x  x  x  0  0  x  x  x �
                   �������������������������
                             ����
        replace, AND, OR, XOR �            �����������������������Ŀ
                              �   ���������1  1  0  0 �0��0��0��0���
         latches                 �        ������������������������ٱ
�����������������������Ŀ����������      �����������������������Ŀ  �
�1  1  0  0  0  0  1  1 ����������� �����0  1  0  1 �1��1��1��1��  �
�����������������������Ĵ          �    �������������������������  �
�0  1  0  1  0  0  1  0 �������������  �����������������������Ŀ    �bit
�����������������������Ĵ �        ���1  0  1  0 �1��1��1��1��    �planes
�1  0  1  0  1  1  0  1 �������������  �������������������������    �
�����������������������Ĵ �  �        �����������������������Ŀ    �
�1  0  1  1  0  1  1  0 ���������������1  0  1  1 �1��1��1��1��    �
������������������������� �  �  �     �������������������������
                          �  �  �  �    |  |  |  |  |  |  |  |
             �������������������Ŀ ���������������Ŀ
             �x  x  x  x �0��1��1��1�� �0  0  0  0  1  1  1  1 �
             ������������������������� �������������������������
                     CPU data              Bit Mask register

     Figure 5-8.  EGA and VGA graphics write mode 2.


     Write mode 3
     In write mode 3 (supported on the VGA only), the pixels are updated by
     combining the pixel values in the latches with the value in the
     Set/Reset register. Again, the Data Rotate/Function Select register
     specifies the logical operation used to combine the values. The CPU
     data byte is rotated by the number of bits indicated in the Data
     Rotate/Function Select register and combined with the value in the Bit
     Mask register using a logical AND. The resulting bit mask then plays
     the same role as the Bit Mask register value in write modes 0 and 2;
     that is, it determines which pixels in the bit planes are updated by
     combining the latched pixel values with the Set/Reset value, and which
     are updated directly from the latches (see Figure 5-9).


       Enable �����������������������Ŀ
    Set/Reset �x  x  x  x  1  1  1  1 �
     register �������������������������
                           |  |  |  |
              �������������������Ŀ
    Set/Reset �x  x  x  x �0��1��1��1��
     register �������������������������       �����������������������Ŀ
                           �  �  �  � ��������1  1  0  0 �0��0��0��0��  �
         latches           �  �  �  � �       �������������������������  �
�����������������������Ŀ    �  �  � �    �����������������������Ŀ     �
�1  1  0  0  0  0  1  1 �������������������0  1  0  1 �1��1��1��1��     �
�����������������������Ĵ       �  �  �   �������������������������     �
�0  1  0  1  0  0  1  0 ����������������  �����������������������Ŀ   bit�
�����������������������Ĵ          �  ���1  0  1  0 �1��1��1��1��planes�
�1  0  1  0  1  1  0  1 ����������������  �������������������������      �
�����������������������Ĵ               �����������������������Ŀ       �
�1  0  1  1  0  1  1  0 �����������������1  0  1  1 �1��1��1��1��       �
������������������������ٱ�������������� �������������������������
                               �          |  |  |  |  |  |  |  |
                               �          0  0  0  0  1  1  1  1
                               �                     
                               �             ������������Ŀ
                    ������������             � Logical AND �
                    �                        �   ����������������������Ŀ
       replace, AND,�                   00001111 �1  1  1  1  1  1  1  1 �
       OR, XOR      �           Rotate       �   �������������������������
                    �       � � � � � � � � �|       Bit Mask register
                    �       |                �
                  ����  �������              |
        �����������������������Ŀ  ����������������������Ŀ
        �x  x  x  0  0  0  1  0 �  �0  0  1  1  1  1  0  0 �
        �������������������������  �������������������������
       Data Rotate/Function Select        CPU data
                 register

     Figure 5-9.  VGA graphics write mode 3.


     Sequencer Map Mask
     One additional level of control is available in all of the EGA's and
     the VGA's Graphics Controller write modes. You can use the Sequencer
     Map Mask register (Sequencer register 02H) to selectively enable or
     disable data transfers to the bit planes. In 16-color graphics modes,
     bits 0 through 3 of this register are normally set to 1 to allow
     graphics writes to access all four maps. However, by zeroing one or
     more of these bits, you can write-protect the corresponding memory
     maps.

     The Sequencer Map Mask register is not often used, because the
     Graphics Controller provides better control for pixel-oriented
     operations. Use of this register is better suited to techniques such
     as bit-plane layering (see Chapter 12).


InColor Card

     The InColor Card has two gate arrays, the Encoder and the Decoder,
     which mediate CPU accesses to video RAM. The Encoder gate array
     participates in CPU writes to video RAM. The Decoder gate array
     manages the transfer of data from video RAM to the CPU, as well as to
     the card's attribute-decoding circuitry.

     The programming interface to the InColor Card's graphics-mode
     hardware, including the Encoder and Decoder chips, is unified through
     the card's control register set at I/O ports 3B4H and 3B5H (see Figure
     5-10). There is no distinction between the Encoder, the Decoder, and
     their associated circuitry from a software point of view. The InColor
     Card's graphics-mode control registers are similar to control
     registers on the EGA and the VGA (see Figure 5-11).


����������������������������������������������������������������������������ķ
Register Number    Register Function                  Read/Write Status
��������������������������������������������������������������������������
Register Number    Register Function                  Read/Write Status
��������������������������������������������������������������������������
18H                Plane Mask register                Write only
19H                Read/Write Control register        Write only
1AH                Read/Write Color register          Write only
1BH                Latch Protect register             Write only

     Figure 5-10.  Graphics control registers on the Hercules InColor Card.


����������������������������������������������������������������������������ķ
InColor                       EGA and VGA
��������������������������������������������������������������������������
Plane Mask register           Sequencer Map Mask register
                              Attribute Controller Color Plane
                              Enable register
Read/Write Control register   Graphics Controller Mode register
                              Graphics Controller Color Don't Care
                              register
Read/Write Color register     Graphics Controller Set/Reset register
Palette register              Attribute Controller Palette registers
InColor                       EGA and VGA
Palette register              Attribute Controller Palette registers

     Figure 5-11.  Functionally similar control registers on the EGA, VGA,
     and InColor Card.


     As on the EGA and VGA, video RAM accesses in graphics mode are
     performed using a set of four 8-bit latches. CPU reads and writes
     cause bytes to be transferred in parallel between the latches and the
     corresponding bit planes. When a CPU read is executed, the Decoder
     latches a byte from each bit plane and returns a single byte of data
     to the CPU. When a CPU write is executed, the Encoder combines the
     latched data with the pixel values stored in the Read/Write Color
     register and updates the bit planes with the result.

     Like the EGA and VGA, the InColor Card can process CPU data and
     latched data in several ways. The card supports four graphics write
     modes (see Figure 5-12), selected by bits 4 and 5 of the Read/Write
     Control register (19H). There is only one graphics read mode, which
     is similar to read mode 1 on the EGA and VGA.


����������������������������������������������������������������������������ķ
Write Mode   CPU Data Bit = 0    CPU Data Bit = 1
��������������������������������������������������������������������������
0            Background value    Foreground value
1            Latch               Foreground value
2            Background value    Latch
3            NOT latch           Latch

     Figure 5-12.  Source of pixel data in InColor graphics write modes.


     Write modes 0-3
     In all four InColor graphics write modes, the CPU data functions as an
     8-bit mask. The Encoder uses the value of each bit in the mask to
     determine how to update the corresponding pixel value in the latches.
     That is, the source of the pixel value at a particular bit position is
     determined by the value of the corresponding bit in the CPU data byte.

     For example, in graphics write mode 1, when a bit in the CPU data byte
     is 1, the corresponding pixel in the video buffer is replaced with the
     foreground value in the Read/Write Control register; when a bit in the
     CPU data byte is 0, the corresponding pixel value is copied from the
     latches. For example, in Figure 5-13, the pixels corresponding to
     bits 0 through 3 are replaced with the Read/Write Control register
     foreground value, while the remaining pixels are updated from the
     pixel values in the latches.

     Similarly, in the other three graphics write modes, the value of each
     bit in the CPU data byte controls how the corresponding pixel is
     updated. The write modes differ only in how the pixel values are
     derived (see Figure 5-12). In write mode 0, either the foreground or
     the background value in the Read/Write Control register replaces the
     pixels in the bit planes. In write mode 2, for each 0 bit in the CPU
     data byte, the Read/Write Control register background value is used to
     update the corresponding pixel in the bit planes. In write mode 3,
     each 0 bit in the CPU data byte causes the corresponding pixel in the
     video buffer to be replaced with the bitwise NOT of the pixel value in
     the latches.


                             Plane Mask register
                          �����������������������Ŀ
                          �0  0  0  0  x  x  x  x �
                          �������������������������
                           |  |  |  |        �����������������������Ŀ
                           |  |  |  | �������1  1  0  0 �0��0��0��0��  �
         latches           |  |  |  | �      �������������������������  �
�����������������������Ŀ  |  |  |  | �    �����������������������Ŀ    �
�1  1  0  0  0  0  1  1 ������|��|��|ͼ����0  1  0  1 �1��1��1��1��    �
�����������������������Ĵ �   |  |  |  �   �������������������������    �
�0  1  0  1  0  0  1  0 ���������|��|�ͼ �����������������������Ŀ   bit�
�����������������������Ĵ �  �   |  | ���1  0  1  0 �1��1��1��1��planes�
�1  0  1  0  1  1  0  1 ������������|ͼ  �������������������������      �
�����������������������Ĵ �  �  �   |  �����������������������Ŀ        �
�1  0  1  1  0  1  1  0 ���������������1  0  1  1 �1��1��1��1��        �
������������������������� �  �  �  �   �������������������������
                          �  �  �  �    |  |  |  |  |  |  |  |
             �������������������Ŀ ���������������Ŀ
             �x  x  x  x �0��1��1��1�� �0  0  0  0  1  1  1  1 �
             ������������������������� �������������������������
                 Read/Write Color              CPU data
                     register       (Pixels 0-3 copied from foreground
                                    value in R/W Color register; pixels
                                        4-7 copied from latches.)

     Figure 5-13.  InColor graphics write mode 1.


     CPU writes affect only those bit planes specified in the Plane Mask
     register (18H). This register's function is thus analogous to that of
     the EGA's Sequencer Map Mask register. Bits 4 through 7 of this
     register control which of the four bit planes are writable; setting
     any of these bits to 1 prevents updating of the corresponding bit
     planes during CPU writes.


     Read mode
     The InColor Card has only one graphics read mode (see Figure 5-14). It
     resembles read mode 1 on the EGA and the VGA. When a CPU read is
     executed, the latches are loaded with data from the bit planes. Unlike
     the EGA and the VGA, however, the InColor Card lets you control which
     individual pixel values are latched during a CPU read. The bit mask
     value in the Latch Protect register (1BH) indicates which pixel values
     are latched. Where a bit in the Latch Protect register is 0, the
     corresponding pixel value is latched; where a bit is 1, the
     corresponding pixel value in the latch remains unchanged.


�����������������������������������������������������������������������������
           ������������������������������������������������Ŀ Latch
           �  0     0     0     0     0     0     0     0   � Protect
           �������������������������������������������������� register
              |     |     |     |     |     |     |     |
           ���������������������������������������Ŀ
      �����  1     1    �0��   0     0    �0��   1     1  �
           �����������������������������������������������Ĵ
bit   �����  0     1    �0��   1     0    �0��   1     0  �
planes     �����������������������������������������������Ĵ
      �����  1     0    �1��   0     1    �1��   0     1  �
           �����������������������������������������������Ĵ
      �����  1     0    �1��   1     0    �1��   1     0  � ����������������
           ������������������������������������������������� � x  0  x  x  0
pixel                                                ����������������
pixel                                                ����������������
values       1011  1100  0011  0101  0010  0011  1101  1010    ����        ��
              �     �     �     �     �     �     �     �       �  ����������
OR with Don't ����ij���ij���ij���ij���ij���ij���ij��������Ĵ ��������
Care value                                              �  � ��0��0��
             1011  1100  0011  0101  0010  0011  1101  1010     �  � ��������
COMPARE with  �     �     �     �     �     �     �     �       �     ������
(background   ����ij���ij���ij���ij���ij���ij���ij� � � � � � � � � �
value OR      �     �     �     �     �     �     �     �       �
Don't Care                                              �
value.)       0     0     1     0     0     1     0     0       �
              �     �     �     �     �     �     �     �       �
XOR with Mask �     �     �     �     �     �     �     �       �
Polarity bit  ����Ĵ���Ĵ���Ĵ���Ĵ���Ĵ���Ĵ���Ĵ�������
              �     �     �     �     �     �     �     �
                                                 
CPU        �����������������������������������������������Ŀ
data       �  1     1    �0��    1     1    �0��  1     1  �
           �������������������������������������������������

     Figure 5-14.  InColor graphics read.


     After the specified pixel values in the latches have been updated from
     the bit planes, the Decoder compares each pixel value in the latches
     with the background value in the Read/Write Color register. The 8-bit
     result of the comparison is returned to the CPU. This is similar to
     read mode 1 on the EGA and the VGA.

     Bits 0 through 3 of the Read/Write Control register are "don't care"
     bits analogous to the Color Don't Care value on the EGA and the VGA.
     Setting a Read/Write Control "don't care" bit to 1 has the effect of
     excluding a latch from the background value compare operation. If you
     set all four "don't care" bits to 1, all pixel values match the
     background value no matter what it is.

     The polarity of the bits in the result returned to the CPU depends
     upon the value of the Mask Polarity bit (bit 6 of the Read/Write
     Control register). When this bit  is 0, bits in the result are 1 where
     a pixel value in the latches matches the background value. Setting the
     Mask Polarity bit to 1 inverts the result; that is, bits are 1 where a
     pixel value in the latches does not match the background value.


Reading a Pixel's Value


     Now it is time to turn to some specific programming techniques for
     manipulating pixels on the various PC and PS/2 video subsystems. Once
     you calculate the byte and bit offsets of a particular pixel in the
     video buffer, determining the pixel's value is a matter of isolating
     the bits that represent it in the buffer. This is as true on the CGA
     and HGC, with their simpler video RAM architecture, as it is on more
     complicated video subsystems that use bit planes.


CGA

     In 640-by-200 2-color mode, the value of a pixel is determined simply
     by reading the byte that contains the pixel from the video buffer and
     testing the value of the bit that represents the pixel (see Listing
     5-2).


���������������������������������������������������������������������������

     Listing 5-2.  Determining a pixel value in CGA 640-by-200 2-color
     mode.

���������������������������������������������������������������������������


     The technique for determining the value of a pixel in 320-by-200
     4-color graphics mode, as shown in Listing 5-3, is similar. After
     isolating the bits that represent the pixel, however, your program
     must shift them rightward so that the value returned represents the
     actual pixel value.


���������������������������������������������������������������������������

     Listing 5-3.  Determining a pixel value in CGA 320-by-200 4-color
     mode.

���������������������������������������������������������������������������


HGC and HGC+

     The only difference between the pixel-read routines for the Hercules
     mono- chrome adapters and the ones used in the CGA's 640-by-200 2-
     color mode lies in how the pixel's address is computed. For example,
     you can adapt the CGA routine shown in Listing 5-2 for the HGC simply
     by substituting PixelAddrHGC for PixelAddr06.


EGA

     In CGA-emulation modes, the routines used for the CGA work unchanged.
     However, in 16-color 200-line modes and in 350-line modes, you must
     program the Graphics Controller to isolate the bits that represent a
     pixel in the video buffer's bit planes, as the routine in Listing 5-4
     does.


���������������������������������������������������������������������������

     Listing 5-4.  Determining a pixel value in native EGA graphics
     modes.

���������������������������������������������������������������������������


     This routine uses the Graphics Controller's read mode 0 to read a
     single byte from each of the EGA's planes. As the bytes are read, the
     desired pixel's bits are masked and concatenated to form the pixel's
     value.

       ���ͻ     In 640-by-350 monochrome graphics mode, only bit planes 0
       � T �     and 2 are used to represent pixel values. In these modes,
       � I �     only bits from these two planes are concatenated to form a
       � P �     pixel value (see Listing 5-5).
       ���ͼ

                 As described in Chapter 4, 640-by-350 graphics modes are
                 mapped differently on an EGA with only 64 KB of video RAM
                 than on an EGA with more memory. Memory maps 0 through 1
                 and 2 through 3 are chained to form two bit planes. Pixels
                 at even byte addresses are represented in maps 0 and 2,
                 while pixels at odd byte addresses are represented in maps
                 1 and 3. A routine to read pixel values in these modes
                 must use the pixel's byte address to determine which maps
                 to read (see Listing 5-6).


���������������������������������������������������������������������������

     Listing 5-5.  Determining a pixel value in EGA monochrome graphics
     mode.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 5-6.  Determining a pixel value in 640-by-350 modes on an EGA
     with 64 KB.

���������������������������������������������������������������������������


InColor Card

     As with the EGA, to read a pixel's value on the InColor Card requires
     reading each bit plane separately. To do this, you must use the "don't
     care" bits in the Read/Write Control register along with the
     background value in the Read/Write Color register to isolate the
     contents of each latch.

     The routine in Listing 5-7 accumulates a pixel's 4-bit value by
     concatenating one bit from each of the InColor card's four bit planes.
     The routine determines the contents of each of the bit planes by
     setting the background value in the Read/Write Color register to 0FH
     (1111B) and by individually zeroing each Read/Write Control register
     "don't care" bit. When each CPU read is executed (with the
     AND CH,ES:[SI] instruction), the value returned to the CPU is thus
     the 8-bit value in one of the four latches. This value is ANDed with
     the bit mask in CH, and the isolated bits are accumulated in BL.


���������������������������������������������������������������������������

     Listing 5-7.  Determining a pixel value in InColor graphics mode.

���������������������������������������������������������������������������


       ���ͻ     As usual in bit-plane programming, the tricky part of this
       � T �     process is in setting up the control register values to
       � I �     produce the desired result. For example, here is what
       � P �     happens when the AND CH,ES:[SI] instruction executes:
       ���ͼ

                 1. One byte from each bit plane is copied into the
                    latches.

                 2. Each of the eight pixels in the latches is compared
                    with the background value (1111B), and the eight bits
                    that reflect the result of the eight comparisons are
                    returned to the CPU. Because only one of the four
                    "don't care" bits in the Read/Write Control register
                    is 0, only one of the four bits in each pixel value
                    participates in each comparison. If this bit is 1, the
                    comparison is true, and the Decoder returns a 1 in the
                    bit position corresponding to this pixel value.

                 3. The eight bits returned to the CPU are ANDed with the
                    bit mask in CH to give the desired result.

                 That's a lot of action for a single AND instruction.


MCGA

     In 640-by-200 2-color and 320-by-200 4-color modes, the routines
     written for the CGA (shown in Listings 5-2 and 5-3) also work on the
     MCGA. The two other MCGA graphics modes pose no additional problems
     (see Listings 5-8 and 5-9), because they use no buffer interleave as
     do CGA-compatible modes, and because there are no bit planes to worry
     about.


���������������������������������������������������������������������������

     Listing 5-8.  Determining a pixel value in MCGA and VGA 640-by-480
     2-color mode.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 5-9.  Determining a pixel value in MCGA and VGA 320-by-200
     256-color mode.

���������������������������������������������������������������������������


VGA

     Once you write pixel-read routines for the CGA, the EGA, and the MCGA,
     you have covered all the bases as far as the VGA is concerned. The
     only VGA graphics mode not available on the other subsystems is 640-
     by-480 16-color mode. However, pixel representation and addressing are
     the same in this mode as in the EGA's 640-by-350 16-color mode, so you
     can use the routine in Listing 5-4 for both.


Setting a Pixel's Value


     In some ways, setting a pixel's value is the converse of determining
     its value. Once the byte and bit offsets of a particular pixel have
     been calculated, setting its value is a simple matter of putting the
     right bits in the right places in the video buffer.

     What complicates pixel-setting routines is that you may not always
     wish simply to replace a pixel's old value with a new value. It is
     sometimes desirable to derive a pixel's new value by performing a
     bitwise logical operation on its old value. This is why the EGA and
     the VGA Graphics Controllers directly support logical AND, OR, and XOR
     operations on pixel values, as well as direct replacement of old
     values with new ones.

       ���ͻ     Since the bulk of the overhead in a pixel-setting routine
       � T �     is in calculating the pixel's location in the video
       � I �     buffer, you can keep your code small and modular by
       � P �     integrating different pixel-value manipulations into a
       ���ͼ     single routine rather than writing separate routines to
                 replace pixels and to perform bitwise logical operations
                 on them. The examples in this chapter combine these
                 different pixel-value operations into unified routines.

                 Where each bitwise operation requires a different
                 subroutine, the subroutine's address is stored in a
                 variable (SetPixelOp). This technique is more flexible
                 than coding a jump to the desired pixel operation
                 (replace, AND, OR, or XOR), because you can change the
                 address in the variable with another independent
                 subroutine.

                 The examples in this chapter do not include code for
                 updating a pixel's value by performing a bitwise NOT
                 operation. You can use the XOR operation to obtain the
                 same result as NOT without decreasing performance and
                 without writing additional code.


CGA

     To set a pixel in 640-by-200 2-color mode, mask the appropriate bit in
     a byte in the video buffer and then set the bit's value. The routine
     in Listing 5-10 implements four different ways of setting the value--
     by replacing the old pixel value with a new value and by using the
     logical operations OR, AND, and XOR.


���������������������������������������������������������������������������

     Listing 5-10.  Setting a pixel value in CGA and 640-by-200 2-color
     mode.

���������������������������������������������������������������������������


     The routine for 320-by-200 4-color mode is similar. This routine,
     shown in  Listing 5-11, differs from the routine for 640-by-200
     2-color mode (see Listing 5-10) only in its technique for computing
     pixel addresses and in its representation of pixels in bit fields that
     are two bits wide.


���������������������������������������������������������������������������

     Listing 5-11.  Setting a pixel value in CGA 320-by-200 2-color mode.

���������������������������������������������������������������������������


HGC and HGC+

     As you might expect, a routine for writing a pixel in the HGC's 720-
     by-348 monochrome graphics mode can be derived from the equivalent
     routine for the CGA's 640-by-200 2-color mode in Listing 5-10 by
     substituting the HGC's pixel-address computation routine
     (PixelAddrHGC) for the CGA's.


EGA

     You don't need to worry about CGA-emulation modes (640-by-200 2-color
     and 320-by-200 4-color), because the routines that work on the CGA
     work equally well on the EGA. However, things become considerably more
     complicated in the EGA's native graphics modes. In these modes, there
     are several different ways you can program the Graphics Controller to
     set the value of an individual pixel. Also, the pixel-setting routine
     must properly handle the video memory maps in monochrome and 640-by-
     350 4-color graphics modes (on an EGA with 64 KB).


     Write mode 0
     The method for setting a pixel's value in write mode 0 is shown in
     Listing 5-12. First, as usual, you calculate the byte offset and bit
     mask, which identify the pixel's location in the video buffer. Then
     you program the Graphics Controller: Set up write mode 0, store the
     bit mask value in the Bit Mask register, and configure the Set/Reset
     and Enable Set/Reset registers for the pixel value. Then you can
     perform a CPU read to latch the bit planes, followed by a CPU write to
     copy the contents of the latches and the new pixel value into the bit
     planes.


���������������������������������������������������������������������������

     Listing 5-12.  Setting a pixel value in native EGA graphics modes
     using write mode 0.

���������������������������������������������������������������������������


     Note how the contents of the Graphics Controller registers determine
     how the bit planes are updated during the CPU write in the OR
     instruction. The value in the Bit Mask register has only one nonzero
     bit, so only one pixel is updated. This pixel takes its value from the
     Set/Reset register. (The other seven pixels are updated from the
     latches; since the CPU read loaded the latches with these same pixels,
     the CPU write doesn't change them.) The Enable Set/Reset value is
     1111B, so the CPU data byte in AL plays no part in the operation.

       ���ͻ     IBM's EGA BIOS uses write mode 0 to set the values of
       � T �     individual pixels in INT 10H function 0CH, but the BIOS
       � I �     routine does not use the Set/Reset register to specify the
       � P �     pixel value. Instead, it first zeroes the pixel by using
       ���ͼ     the Bit Mask register to isolate it and by writing a CPU
                 data byte of 0. Then the BIOS programs the Sequencer Map
                 Mask register to select only those bit planes in which the
                 desired pixel value contains a nonzero bit. The routine
                 then performs a second CPU write to set the nonzero bits,
                 as shown in Listing 5-13.

                 This technique has two weaknesses: There are easier ways
                 to do the same job, and the routine requires extra coding
                 if you want to AND, OR, or XOR the pixel value in the
                 video buffer. For both reasons, video BIOS INT 10H
                 function 0CH is limited in both speed and flexibility.


���������������������������������������������������������������������������

     Listing 5-13.  Setting a pixel value in native EGA graphics modes
     using the Sequencer Map Mask.

���������������������������������������������������������������������������


     Write mode 2
     A somewhat simpler way to set the value of an individual pixel is to
     use write mode 2. The routine in Listing 5-14 demonstrates this
     technique. As in write mode 0, the Bit Mask register determines how
     each of the eight pixels is updated. In write mode 2, however, new
     pixel values are derived by combining the CPU data byte with the
     latched pixel values; this avoids the need to program the Set/Reset
     and Enable Set/Reset registers and leads to shorter, faster code.


���������������������������������������������������������������������������

     Listing 5-14.  Setting a pixel value in native EGA graphics modes
     using write mode 2.

���������������������������������������������������������������������������


     The routines in Listings 5-12 and 5-14 are designed to work correctly
     when the Function Select register specifies the AND, OR, or XOR
     function. Thus, you need write no extra code to perform these
     alternative pixel manipulations in the EGA's native graphics modes.

     Furthermore, if you are careful to use the proper pixel values, the
     routines in Listings 5-12 and 5-14 can be used in any native EGA
     graphics mode. To ensure that the appropriate bits in the memory maps
     are updated in 640-by-350 monochrome mode, use pixel values of 0, 1,
     4, and 5 only. On an EGA with 64 KB of RAM, use pixel values 0, 3,
     0CH, and 0FH.


InColor Card

     The routine in Listing 5-15 updates a single pixel in the InColor
     Card's 720-by-348 16-color mode. The InColor Card lacks a functional
     equivalent of the EGA's Function Select register, so this routine
     contains four separate subroutines which perform AND, OR, or XOR
     operations on pixel values.


���������������������������������������������������������������������������

     Listing 5-15.  Setting a pixel value in InColor graphics
     mode.

���������������������������������������������������������������������������


     Each one of these subroutines begins by programming the Read/Write
     Control, Read/Write Color, and Plane Mask registers. Then a CPU read
     loads the latches, and a subsequent CPU write updates the bit planes.

     Each subroutine starts by programming the Read/Write Control register
     for one of the four graphics write modes. At the same time, the "don't
     care" bits are all set to 1 and the Mask Polarity bit is zeroed so
     that the Decoder always returns 11111111B as the result of a CPU read.
     Then the Plane Mask and Read/Write Color foreground values are set up;
     these values depend upon whether the pixel value is to be replaced or
     manipulated by an AND, OR, or XOR operation.

     The instruction AND ES:[BX],CH (or XOR ES:[BX],CH for the pixel XOR
     operation) causes the CPU read and write. During the CPU read, the
     latches are loaded and the value 11111111B is returned to the CPU; the
     CPU ANDs (or XORs) this value with the bit mask in CH and writes the
     result back to the same address in the video buffer. In this way, the
     bit mask in CH selects which pixel value is updated during the CPU
     write.

     Except for the pixel that the bit mask specifies, the contents of the
     latches are copied back into the bit planes from which they were just
     read; the value of the pixel being updated derives from the foreground
     value in the Read/Write Color register. Only the bit planes that the
     Plane Mask register specifies are modified, so the only bits in the
     bit planes that are updated are those that the replace, AND, OR, or
     XOR operation modifies.

       ���ͻ     It is instructive to compare the interaction of the write
       � T �     mode, foreground color, and Plane Mask values within each
       � I �     of the subroutines. The logical operation that takes place
       � P �     (replace, AND, OR, or XOR) is not programmed explicitly
       ���ͼ     with an 80x86 instruction. It is implicit in the contents
                 of the graphics control registers, which are programmed to
                 emulate the logical operation by modifying the individual
                 bits in the updated pixel.


MCGA

     In CGA-compatible graphics modes, the same routines for setting pixel
     values run unchanged on both the CGA and the MCGA. The two non-CGA
     modes (640-by-480 2-color and 320-by-200 256-color) can be handled
     easily with simple modifications to the routine for 640-by-200 2-color
     mode. Listings 5-16 and 5-17 show the necessary changes.


���������������������������������������������������������������������������

     Listing 5-16.  Setting a pixel value in MCGA or VGA 640-by-480 2-color
     mode.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 5-17.  Setting a pixel value in MCGA or VGA 320-by-200 256-
     color mode.

���������������������������������������������������������������������������


VGA

     Once you create routines to update pixels on the MCGA and EGA, doing
     the same for the VGA is easy. The only VGA video mode that does not
     exist on the other subsystems is 640-by-480 16-color mode. Pixel
     addressing in this mode is the same as in the EGA's 640-by-350 16-
     color mode, so the routines in Listings 5-12 through 5-14 may
     be used.


Filling the Video Buffer


     Usually the first thing you do after selecting a new video mode is
     clear the video buffer by filling it with a uniform background of
     repetitive data. In alphanumeric modes, it is easy and efficient to
     fill the buffer with blanks or nulls by using the 80x86 STOSW
     instruction.

     Filling the video buffer in graphics modes is more of a challenge.
     Zeroing the entire buffer is relatively easy, but filling the screen
     with a solid color or pixel pattern is more difficult, particularly on
     the EGA, the VGA, and the InColor Card.


CGA

     On the CGA, you can set the entire buffer to a single pixel value or a
     pattern of vertical stripes with a REP STOSW operation, as the routine
     in Listing 5-18 does. Because of the two-way interleave in the video
     buffer map, this technique fills all even-numbered scan lines before
     filling the odd-numbered lines. You might prefer to clear the buffer
     from the top down by filling it a line at a time. This technique, used
     in Listing 5-19, achieves a slightly smoother appearance, but requires
     slower and bulkier code.


���������������������������������������������������������������������������

     Listing 5-18.  Simple CGA graphics buffer fill.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 5-19.  CGA graphics buffer fill using two-way interleave.

���������������������������������������������������������������������������


     You can exploit the two-way interleave in the video buffer map to
     create a color blend or a simple pattern (see Listing 5-20). In this
     case, the pixel pattern in the even-numbered scan lines is shifted in
     position from the pattern in the odd-numbered scan lines. This creates
     a dithered or halftone pattern on the screen. Because the pixels are
     so close together, the eye blends them, perceiving the dithered
     pattern as gray in 640-by-200 2-color mode or as an intermediate color
     blend in 320-by-200 4-color mode.


���������������������������������������������������������������������������

     Listing 5-20.  CGA graphics buffer fill with different pixel pattern
     in odd and even rows.

���������������������������������������������������������������������������


HGC and HGC+

     You can use the same basic techniques for clearing the video buffer in
     the HGC's 720-by-348 monochrome graphics mode as in the CGA's 640-by-
     200 2-color mode. However, your routine must be able to clear either
     of the two displayable portions of the HGC's video buffer. +Listing
     5-21 demonstrates how you can do this. Again, you can take advantage
     of the interleaved video memory map to create a dithered pattern as
     you clear the buffer.


���������������������������������������������������������������������������

     Listing 5-21.  HGC graphics buffer fill using four-way interleave.

���������������������������������������������������������������������������


EGA and VGA

     The Graphics Controller can provide a certain amount of hardware
     assistance in filling the EGA and VGA video buffer. Also, because the
     buffer holds more data than can be displayed on the screen, you can
     choose to clear only the displayed portion, an undisplayed portion, or
     the entire buffer.

     In 640-by-200 2-color and 320-by-200 4-color modes you can use the
     routines for the CGA (see Listings 5-18 through 5-20). Remember,
     however, that the EGA and the VGA have enough video RAM to support two
     screens of data in 320-by-200 4-color mode. Your routine should
     therefore be capable of clearing any designated area of the buffer.
     Filling the video buffer in 640-by-480 2-color mode (see Listing 5-22)
     and 320-by-200 256-color mode (see Listing 5-23) is also a relatively
     easy task, because pixel addressing in these modes is simple.


���������������������������������������������������������������������������

     Listing 5-22.  MCGA and VGA 640-by-480 2-color graphics buffer fill.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 5-23.  MCGA and VGA 320-by-200 256-color graphics buffer fill.
     This routine fills alternate pixel rows separately to allow dithered
     pixel patterns.

���������������������������������������������������������������������������


     In 16-color 200-line graphics modes and all 350-line graphics modes,
     your routines should program the Graphics Controller to exploit its
     parallel processing capabilities. The most efficient way to fill the
     video buffer with a solid color is to use write mode 0 to repeatedly
     copy the Set/Reset value into the video buffer. Because no CPU read is
     required for this operation, you can set the entire video buffer to a
     solid color with a single REP STOSW instruction as shown in Listing
     5-24.


���������������������������������������������������������������������������

     Listing 5-24.  Solid buffer fill for EGA and VGA native graphics
     modes. The code assumes that the Graphics Controller is already in
     write mode 0 (the BIOS default).

���������������������������������������������������������������������������


     Filling the video buffer with an arbitrary pixel pattern is more
     difficult. Although the basic technique is the same, each component of
     the pattern must be written separately to the bit planes. The example
     in Listing 5-25 fills the video buffer with an 8-by-2 pattern of
     pixels in the VGA's 640-by-480 16-color mode. You can adapt the
     routine to 200-line and 350-line 16-color modes on both the EGA and
     VGA.


���������������������������������������������������������������������������

     Listing 5-25.  Patterned buffer fill for EGA and VGA native graphics
     modes. The code assumes that the desired pixel pattern is already
     stored in the first eight pixels of the first two rows of the video
     buffer (that is, at A000:0000 and A000:0050).

���������������������������������������������������������������������������


InColor Card

     As with the EGA and the VGA, you should use the InColor Card's
     graphics data latches to update the four bit planes in parallel.
     Filling the video buffer with a solid color is straightforward, as
     shown in Listing 5-26. Filling it with a pixel pattern demands the
     same sort of logic used in the equivalent routine for the EGA and VGA
     (shown in Listing 5-27).


���������������������������������������������������������������������������

     Listing 5-26.  Solid buffer fill for Hercules InColor graphics mode.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 5-27.  Patterned buffer fill for InColor Card. The code
     assumes that the desired pixel pattern is already stored in the first
     eight pixels of the first two rows of the video buffer (that is, at
     offsets 0 and 2000H in BufferSeg).

���������������������������������������������������������������������������


MCGA

     You can use the routines written for the CGA and the VGA to fill the
     video buffer in equivalent graphics modes on the MCGA.



                             6  Lines


                 An Efficient Line-drawing Algorithm
       Scan-converting a Straight Line � Bresenham's Algorithm

                            Optimization
                     Efficient Pixel Addressing
               Performance Comparisons � Special Cases

                     PC and PS/2 Implementations
                          Modular Routines
                  Minimizing Video Buffer Accesses
                   Efficient Address Calculations
                           CGA � HGC � EGA
                             MCGA � VGA
                            InColor Card

                           Line Attributes

                              Clipping
          Pixel-by-Pixel Clipping � A Brute-Force Approach
                         A Better Algorithm



     Most video graphics applications rely on routines that draw straight
     lines on the screen. Straight lines are components of many graphics
     images, including polygons, filled areas (made up of groups of
     contiguous lines), and curves (made up of a series of short line
     segments joined end to end). Because lines are used frequently in
     video graphics, you need fast line-drawing subroutines to obtain high-
     performance video graphics. This chapter describes how to construct
     efficient and flexible line-drawing routines for IBM video subsystems.


An Efficient Line-drawing Algorithm


     Imagine what would happen if you tried to draw a straight line on a
     piece of paper by painting through the square holes in a sieve (see
     Figure 6-1). The result would not really be a line, but a group of
     square dots that approximates a line.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 6-1 is found on page 162       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 6-1.  Line painted through a sieve.


     A raster video display's rectangular grid of pixels resembles an
     electronic "sieve" when it comes to drawing straight lines and
     geometric curves. The best you can do is to represent each line or
     curve with a group of pixels that closely approximates it. The process
     of determining which set of pixels in the video buffer best
     approximate a particular geometric figure is called scan-conversion.

       ���ͻ     The visual consequence of scan-conversion is that
       � T �     mathematically smooth lines and curves appear jagged on
       � I �     the screen. Consider the nearly horizontal line in Figure
       � P �     6-2a. The only way to represent such a line within a grid
       ���ͼ     of pixels is as a series of connected horizontal line
                 segments. The more nearly horizontal or vertical the line,
                 the more jagged it appears. Although sophisticated
                 software techniques can minimize the jagged appearance of
                 a scan-converted line, the easiest way to smooth out a
                 line is to "use a finer sieve"; that is, to use a higher-
                 resolution video mode or higher-resolution video display
                 hardware (see Figure 6-2b).


             ������������������������������������������ͻ
             �                                          �
             �    Figure 6-2 is found on page 163       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 6-2.  A nearly horizontal line displayed with (a) low
     resolution and (b) higher resolution.


Scan-converting a Straight Line

     The simplest way to draw a line is to use the equation of the line

     y = mx + b

     where m is the slope of the line and b is the y-intercept (the value
     of y at the point where the line crosses the y-axis). You can use this
     equation to calculate the corresponding y-coordinate for each pixel x-
     coordinate between the line's endpoints as shown in Listing 6-1. This
     technique is slow, but it is easy to implement.


���������������������������������������������������������������������������

     Listing 6-1.  Drawing a line using the equation of the
     line.

���������������������������������������������������������������������������


     The problem is that the computational overhead in performing the
     multiplication, addition, and rounding necessary to generate y for
     each x in the line is considerable. Furthermore, the slope m must be
     maintained as a floating-point number, and using floating-point
     arithmetic in the calculations slows them down.


Bresenham's Algorithm

     Incrementally calculating the appropriate y-coordinates is much more
     efficient. Given the x- and y-coordinates of the first pixel in the
     line, you can calculate the location of each subsequent pixel by
     incrementing the x- and y-coordinates in proportion to the line's
     slope. The arithmetic is simpler and faster than that involved in
     directly using the equation of the line.

     The algorithm presented by J. E. Bresenham in 1965 (IBM Systems
     Journal 4 (1) 1965, pp. 25-30) plots the set of pixels that lie
     closest to the line between two given pixels--(x1,y1) and (x2,y2)--
     assuming that x1 is less than x2 and that the slope of the line is
     between 0 and 1. To simplify the equation of the line, the algorithm
     assumes the location of the first endpoint (x1,y1) is (0,0). The
     equation of the resulting line is

     y = (dy/dx) * x

     where

     dy = y2 - y1

     and

     dx = x2 - x1

     To visualize how Bresenham's algorithm works, consider the portion of
     a line shown in Figure 6-3. The algorithm proceeds by iteratively
     determining the corresponding y-coordinate for each value of x from x1
     to x2. After plotting the pixel at (x(sub i-1),y(sub i-1)), for
     example, the algorithm determines whether pixel A or pixel B is closer
     to the exact line and plots the closer pixel.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 6-3 is found on page 165       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 6-3.  Bresenham's incremental line-drawing algorithm. Given the
     pixel at (x(sub i-1),y(sub i-1)), the algorithm selects either pixel
     A or B depending on the values of a and b.


     The difference between pixel A's y-coordinate and the y-coordinate on
     the exact line at x(sub i) is

     a = (y(sub i)+1) - (dy/dx)*x(sub i)

     where (dy/dx) represents the line's slope. Similarly, the distance b
     from pixel B to the line is

     b = (dy/dx)*x(sub i) - y(sub i)

     If distance b is smaller than distance a, pixel B lies closer to the
     exact line. If a is smaller than b, pixel A is closer. In other words,
     the sign of the difference (b - a) determines whether pixel A or pixel
     B is closer to the line.

     Now, this may seem like much more work than simply using the equation
     for the line. However, the values of a and b can be compared
     implicitly for each x(sub i) by iteratively computing the value of
     (b - a) for each succeeding x(sub i) in terms of simpler quantities
     like dy and dx. The resulting computation is simple, although deriving
     it requires a bit of algebra.

     To derive the computation, combine the equations for a and b:

     (b-a) = 2*(dy/dx)*x(sub i) - 2*y(sub i) - 1

     Since x1 is less than x2, dx is always positive, so dx * (b - a) can
     be used instead of (b - a) to decide whether to plot pixel A or pixel
     B:

     dx*(b-a) = 2*dy*x(sub i) - 2*y(sub i)*dx - dx

              = 2*(dy*x(sub i) - dx*y(sub i) - dx

     Let d(sub i) represent the quantity dx*(b-a). To calculate d(sub i)
     iteratively, you need to know how to compute it from d(sub i-1):

     (d(sub i)-d(sub i-1)) = (2*(dy*x(sub i) - dx*y(sub i))) -
                               (2*(dy*x(sub i-1) - dx*y(sub i-1)))

                           = 2*(dy*(x(sub i)-x(sub i-1))) - dx*(y(sub i)-
                                y(sub i-1))

     x(sub i) - x(sub i-1) is always 1, and y(sub i) - y(sub i-1 is either
     1 (if pixel A at (x(sub i),y(sub i + 1) is plotted) or 0 (if pixel B
     at (x(sub i),y(sub i) is plotted). Thus, computing the difference
     between d(sub i) and d(sub i-1) is easy, and d(sub i) can be
     calculated simply by incrementing d(sub i-1) with one of two
     constants:

     If d(sub i-1) >= 0, plot pixel A at (x(sub i),y(sub i + 1)). The
     increment for d(sub i-1) is then

     (d(sub i)-d(sub i-1)) = 2*(dy-dx)

     If d(sub i-1) < 0, plot pixel B at (x(sub i),y(sub i)). The
     increment for d(sub i-1) is then

     (d(sub i)-d(sub i-1)) = 2*dy

     To calculate d(sub i)'s initial value, remember that the first pixel
     in the line is assumed to be at (0,0). Substituting x(sub i) = 1 and
     y(sub i) = 0 into the equation for d(sub i) gives

     d(sub i) = 2*dy - dx

     The resulting algorithm is efficient, because the most complicated
     calculations are performed only once, outside the loop that plots the
     pixels (see Listing 6-2). Within the loop, incrementally determining
     which pixels lie closest to the desired line (using the decision
     variable d(sub i) eliminates the need for time-consuming floating-
     point arithmetic. The result is a faster line-drawing algorithm.


Optimization


     Nevertheless, there is still room for improvement. The slowest part of
     the line-drawing primitive in Listing 6-2 is the call to SetPixel(),
     which calculates the pixel's address in the video buffer and then sets
     the pixel's value. The pixel address calculation is clearly the
     slowest part of the procedure.


���������������������������������������������������������������������������

     Listing 6-2.  A high-level implementation of Bresenham's
     algorithm.

���������������������������������������������������������������������������


Efficient Pixel Addressing

     Fortunately, you can optimize the pixel address calculation
     significantly: The pixel addresses themselves can be calculated
     incrementally, in the same way you increment the decision variable
     d(sub i). After calculating the address of the first pixel in the
     line, you can find its neighbors in the video buffer either by
     incrementing the pixel's byte offset or by rotating the bit mask that
     represents its bit offset. Calculating pixel addresses incrementally
     is significantly faster than performing the computation from scratch
     for each (x,y) coordinate pair in the line.

     For example, you can identify the pixel immediately to the right of a
     given pixel by rotating the given pixel's bit mask one pixel position
     to the right. (If the given pixel is the rightmost pixel in its byte,
     increment the byte offset as well.) To find the pixel immediately
     above a given pixel, decrement the byte offset by the number of bytes
     per row of pixels, but keep the bit mask the same. This calculation is
     slightly more complicated in video modes with an interleaved video
     buffer map, but the principle is the same.


Performance Comparisons

     When you compare the techniques for scan-converting lines, the
     performance gains from using an incremental line-drawing algorithm and
     incremental address calculations are remarkable (see Figure 6-4).
     Writing your line-drawing routines in assembly language also helps.
     Coding and optimizing bit mask manipulation and address computations
     is much easier in assembly language than in a high-level language.


����������������������������������������������������������������������������ķ
Algorithm                              Language         Pixels per Second
��������������������������������������������������������������������������
Algorithm based on the equation
of a line                              C                 4,800
Bresenham's algorithm                  C                16,000
Bresenham's algorithm                  Assembler        26,000
Bresenham's algorithm with             Assembler        70,000
incremental pixel address calculation

     Figure 6-4.  Performance of line-drawing algorithms in C and in
     assembly language. Timings were obtained on a 6 MHz IBM PC/AT with a
     Hercules Graphics Card.


Special Cases

     To further improve the overall performance of your video graphics
     drivers, use special routines for drawing horizontal and vertical
     lines. In many applications, these special cases account for a
     surprising percentage of the calls to the line-drawing primitive. This
     is especially true if you use lines to fill regions.


���������������������������������������������������������������������������

     Listing 6-3.  A routine that draws horizontal lines.

���������������������������������������������������������������������������


     For example, the routine FilledRectangle() in Listing 6-3 calls on the
     line-drawing function to draw horizontal lines exclusively. If you
     fill a rectangle that is 100 pixels high, the line-drawing function is
     called 100 times to draw a horizontal line. When the line-drawing
     function recognizes the special case of horizontal lines, functions
     such as FilledRectangle() run significantly faster.

     A special-purpose routine can draw horizontal lines 10 times faster
     than a general-purpose line-drawing routine. For vertical lines, a
     special-purpose routine is about 25 percent faster. Horizontal lines
     are represented in the video buffer by contiguous sequences of bytes
     you can fill with an 80x86 REP STOSB instruction, which runs much
     faster than the iterative loop the general line-drawing primitive
     requires. In drawing vertical lines, no logic is required to determine
     pixel locations. You simply increment the pixel address. Again, the
     resulting code is simpler and faster.


PC and PS/2 Implementations


     Implementations of Bresenham's line-drawing algorithm on IBM video
     hardware are strongly influenced by the CPU's capabilities and by the
     idiosyncrasies of pixel mapping in the video buffer in various
     graphics modes. Nevertheless, once you write a line-drawing routine
     for one graphics mode, you can adapt the source code to other graphics
     modes or to other video hardware with little difficulty.


Modular Routines

     You should build your line-drawing routines with a modular structure.
     One practical way to break your code into modules is to write separate
     routines for horizontal lines, vertical lines, lines with slope less
     than 1, and lines with slope greater than 1. Each module itself
     comprises a set of modules for performing each of the necessary pixel
     manipulations--XOR, AND, OR, and pixel replacement.

       ���ͻ     Bresenham's algorithm as derived in this chapter is
       � T �     applicable only to lines whose slope lies between 0 and 1.
       � I �     However, it is easy to use the same algorithm for lines
       � P �     with other slopes. For lines with slopes between -1 and 0,
       ���ͼ     simply change the sign of the y-increment (see Listing
                 6-2). For lines with slopes less than -1 or greater than 1
                 (that is, |(dy/dx)|> 1), use the same algorithm but
                 exchange the x- and y- coordinates.

                 For example, each of the assembly-language line-drawing
                 routines in this chapter contains two similar subroutines,
                 one for |(dy/dx)|<= 1 and another for |(dy/dx)|> 1. Each
                 routine contains a prologue that detects the special
                 cases of horizontal and vertical lines, initializes the
                 appropriate increment values, and selects the proper
                 subroutine  for the slope.

     Breaking your routines into modules helps when you customize your code
     for an application. It also simplifies the task of writing code to run
     symmetrically in different graphics modes. For example, a routine that
     draws a vertical line in 640-by-200 2-color mode on a CGA requires
     little modification to run properly in 320-by-200 4-color mode.


Minimizing Video Buffer Accesses

     In the 8086 family of microprocessors, data transfer instructions of
     the form MOV mem,reg are among the slowest. Try to minimize use of
     this CPU instruction within your line-drawing primitives. Neighboring
     pixels in a line frequently are grouped in the same byte in the video
     buffer. (Obviously, such groups occur more frequently in more nearly
     horizontal lines.) You can speed your line-drawing routines by
     updating all neighboring pixels in each byte you store in the video
     buffer.


Efficient Address Calculations

     To maximize performance, use CPU registers carefully to hold the
     values most frequently updated in the inner loops of your routines:
     the pixel bit mask, the buffer offset, and the decision variable. In
     Listing 6-4, for example, registers DH and DL hold bit masks,
     register BX holds the buffer offset, and the decision variable d is
     maintained in register DI. These values are the ones most frequently
     updated in these routines, so they are the ones you should try to keep
     in registers rather than in memory variables.

     If you neglect to use the CPU registers effectively, your routines may
     run much slower than necessary. Consider what would happen if you
     rewrote the routine in Listing 6-4 to store the decision variable in
     a memory variable instead of in register DI. Just this minor change
     would cause the routine to run about 20 percent slower. (Not only does
     this emphasize why you must make the best possible use of the CPU
     registers, but it also suggests why writing highly optimized video
     graphics primitives in a high-level language is very difficult.)


CGA

     Listing 6-4 contains code for drawing lines in the CGA's 640-by-200
     2-color graphics mode. The routine consists of a prologue and four
     line-drawing modules. The prologue puts the endpoints in ascending
     order by their x-coordinates, sets  up appropriate vertical increments
     for computing the pixel address within the  inner loop, and selects an
     appropriate line-drawing module according to the  slope of the line.
     The line-drawing modules (VertLine06, HorizLine06, LoSlopeLine06, and
     HiSlopeLine06) contain the inner loops that actually update pixels and
     increment addresses.


���������������������������������������������������������������������������

     Listing 6-4.  A line-drawing routine for CGA 640-by 200 2-color mode.

���������������������������������������������������������������������������


     Most of the execution time in this routine is spent in the inner loops
     of the four line-drawing modules. To optimize the speed of the inner
     loops, as much computation as possible is performed outside of them.
     In particular, the inner loop of HorizLine06 (at label L43) is very
     fast because it consists only of a single 80x86 machine instruction.

     The routines LoSlopeLine06 and HiSlopeLine06 implement Bresenham's
     algorithm. The inner loop of HiSlopeLine06 (at L21) is simpler than
     the inner loop of LoSlopeLine06 (at L11). This is because
     HiSlopeLine06 increments the pixel y-coordinate, and thus the buffer
     offset, on every iteration, so the only other code needed in the loop
     is the code to increment the decision variable and update the pixel
     bit mask. In LoSlopeLine06, the x-coordinate is incremented on each
     iteration by rotating the pixel bit mask. This necessitates some extra
     code to update the bit mask and buffer offset in accordance with the
     decision variable's value.

     The routine for 320-by-200 4-color mode, shown in Listing 6-5, is
     similar to the one for 640-by-200 2-color mode. In fact, you could
     write a single routine that works in either mode without undue
     sacrifice in performance. The differences lie in how the address of
     the first pixel in the line is calculated (that is, a call to
     PixelAddr04 versus one to PixelAddr06) and in how many bits are masked
     and updated for each pixel in the buffer. The bit mask is 1 bit wide
     in 640-by-200 2-color mode and 2 bits wide in 320-by-200 4-color mode.


���������������������������������������������������������������������������

     Listing 6-5.  A line-drawing routine for CGA 320-by-200 4-color
     mode.

���������������������������������������������������������������������������


     On the CGA, the code that handles vertical increments is complicated
     by the need to step across the interleaves in the video buffer. The
     pixel address is incremented by 2000H to move from the first
     interleave (even y-coordinates) to the second interleave (odd y-
     coordinates). To increment from a pixel at an odd y-coordinate to the
     pixel just below it, you add -2000H (to increment from the second to
     the first interleave) plus 80 (the number of bytes in each pixel row
     in the buffer). The increment is thus 0E050H (80 - 2000H).

       ���ͻ     The routines for the CGA presented in Listings 6-4 and
       � T �     6-5 can only copy the specified pixel value into the
       � I �     video buffer. To perform a XOR, an OR, or an AND operation
       � P �     on the preexisting values in the buffer using the
       ���ͼ     specified pixel value, change the inner loops of each of
                 the four line-drawing modules.

                 In selecting among pixel operations (XOR, AND, and so on),
                 you face the usual trade-off between speed and code size.
                 To maximize speed, write a separate line-drawing module
                 for each pixel operation (AND, OR, XOR, and replace). To
                 minimize redundant code, call a short subroutine, or add
                 some branching logic to perform one of the pixel
                 operations.


HGC

     The routine for the HGC, contained in Listing 6-6, is similar to the
     one for the CGA's 640-by-200 2-color mode. The important difference is
     in how the HGC's video buffer is mapped. Because of the Hercules video
     buffer's four-way interleave, the pixel address is incremented by
     adding the buffer interleave value (2000H or -2000H) until the result
     exceeds the limit of valid buffer offsets. Because valid buffer
     offsets lie in the range 0 through 7FFFH, the routine detects the
     overflow condition by examining the high-order bit of the result. When
     the result overflows, it adds another value (90 - 8000H or 8000H - 90)
     to it, so that the new result is the proper offset in the next buffer
     interleave.


���������������������������������������������������������������������������

     Listing 6-6.  A line-drawing routine for Hercules monochrome graphics
     mode.

���������������������������������������������������������������������������


     The routines for the HGC never access the video buffer with 16-bit
     read/write operations such as MOVSW or AND [BX],DX. Avoiding these
     16-bit operations ensures that the routines will run on the InColor
     Card as well as on the HGC and HGC+.

       ���ͻ     You can use the same line-drawing routines on either of
       � T �     the HGC's video pages by setting the appropriate value for
       � I �     VideoBufferSeg in PixelAddrHGC. For video page 0, set
       � P �     VideoBufferSeg to B000H. For video page 1, use B800H.
       ���ͼ


EGA

     For the EGA, three line-drawing routines can cover all available
     graphics modes. The routines for the CGA's 640-by-200 2-color and 320-
     by-200 4-color modes work equally well in equivalent modes on the EGA.
     The routine for the remaining graphics modes (200-line 16-color modes
     and all 350-line modes) is complicated by the need to program the
     Graphics Controller, but simplified in that the Graphics Controller
     hardware handles some pixel manipulations that must be performed in
     software on the CGA.

     The routine in Listing 6-7 uses Graphics Controller write mode 0 to
     update the video buffer. The routine stores the pixel value for the
     line in the Set/Reset register. For each pixel updated in the buffer,
     the routine writes the appropriate bit mask to the Bit Mask register.
     Thus, a single 80x86 instruction can read, update, and rewrite up to 8
     pixels at a time.


���������������������������������������������������������������������������

     Listing 6-7.  A line-drawing routine for native EGA graphics modes.

���������������������������������������������������������������������������


     Within the line-drawing modules, the value 3CEH (the port for the
     Graphics Controller Address register) is maintained in DX, the value 8
     (the Bit Mask register number) is kept in AL, and the current pixel
     bit mask is kept in AH. This lets you update the bit planes with only
     two machine instructions: OUT DX,AX to update the Bit Mask register
     and a MOVSB or OR ES:[DI],AL instruction that causes a CPU read and
     CPU write to occur.

     This routine makes careful use of the 80x86 registers and the Graphics
     Controller. The Graphics Controller's parallel processing helps the
     routine run at about the same speed as do CGA and HGC line-drawing
     routines.

     Native EGA graphics modes use no video buffer interleave, so locating
     a pixel's vertical neighbors in the video buffer is easy. If each line
     contains n bytes of pixels, the next pixel up from a given pixel is -n
     bytes away, and the next pixel down is n bytes away. The code for
     incrementing pixel addresses vertically is thus simpler than the
     corresponding code for the CGA or the HGC. (Compare, for example, the
     code in the loop at label L32 in Listings 6-4 and 6-7.)

     The Graphics Controller handles any of four pixel operations for you
     (XOR, AND, OR, and replace), so the only extra code required to
     support these functions consists of a few instructions to load the
     Data Rotate/Function Select register (03H). This task is part of the
     "configure the Graphics Controller" code near the beginning of the
     routine in Listing 6-7.

       ���ͻ     You can use this line-drawing routine in 640-by-350
       � T �     4-color and monochrome modes. Be sure to specify the
       � I �     proper pixel value in these modes so that the routine sets
       � P �     bits in the proper bit planes (see Chapter 4).
       ���ͼ


MCGA

     In CGA-compatible modes, you can use the CGA line-drawing routines on
     the MCGA. The non-CGA modes (640-by-480 2-color and 320-by-200 256-
     color) require their own routines, as shown in Listings 6-8 and 6-9,
     but these are easily derived from the code for 640-by-200 2-color
     mode.


���������������������������������������������������������������������������

     Listing 6-8.  A line-drawing routine for MCGA and VGA 640-by-480
     2-color mode.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 6-9.  A Line-drawing routine for MCGA and VGA 320-by-200
     256-color mode.

���������������������������������������������������������������������������


VGA

     Once you implement routines for the EGA and the MCGA, you can draw
     lines in any of the VGA's graphics modes. To draw lines in 640-by-480
     16-color mode, use the 640-by-350 16-color routine.


InColor Card

     Because pixel addressing in the video buffer is the same on the
     InColor Card as on Hercules monochrome cards, the only significant
     difference in the line-drawing routines for the InColor Card, as
     you'll see in Listing 6-10, is some extra code to select the
     specified pixel value. Note how the InColor Card's write mode 1 is
     used along with an appropriate foreground value in the Read/Write
     Color register to set the values of neighboring pixels in each byte of
     the buffer. This technique parallels the use of write mode 0 and the
     Set/Reset register on the EGA.


���������������������������������������������������������������������������

     Listing 6-10.  A line-drawing routine for the InColor Card's 720-by-
     348 16-color mode.

���������������������������������������������������������������������������


Line Attributes


     The line-drawing algorithm in this chapter draws lines that are
     exactly one pixel wide. Consequently, diagonal lines appear less
     bright than horizontal or vertical lines. You can fatten diagonal
     lines by modifying the pixel-setting inner loop of a Bresenham line-
     drawing routine so that it always sets pixel B before selecting the
     next pixel in the line. The resulting lines are fatter, but the
     modified routine runs more slowly because it must update more pixels,
     particularly in lines with slopes near 1 or -1.

     To draw wider lines, simply draw contiguous, neighboring parallel
     lines. If you are using a pointing device to draw a wide line
     interactively, use a series of neighboring horizontal or vertical
     lines. After implementing a fast routine for drawing horizontal lines,
     you can write a high-level routine that paints wide lines by calling
     the horizontal line primitive.

     In some applications, you may wish to draw dashed lines or
     multicolored lines that incorporate a pattern of pixel values. To do
     this, modify the inner loop of your line-drawing routine to select
     pixel values from a circular list of possible values. Rotate the list
     each time you set a pixel.


Clipping


     Not one of the assembly-language routines in this chapter validates
     the pixel coordinates you supply as endpoints. For example, if you
     call the 640-by-200 2-color routine to draw a line from (0,0) to
     (1000,1000), the routine blithely updates about 800 pixels at memory
     locations that don't exist in the available video RAM, all the way
     from (200,200) through (1000,1000). To avoid this sort of error, you
     must determine which part of any arbitrary line lies within a given
     area of the video buffer. This process is known as clipping.

     In the case of 640-by-200 2-color mode, the area into which lines must
     be clipped is the rectangular region defined by (0,0) in the upper
     left corner and (639,199) in the lower right corner. You would
     therefore clip a line with endpoints at (0,0) and (1000,1000) so that
     only the portion from (0,0) to (199,199) is drawn. In avoiding the
     error of updating nonexistent RAM, you might also improve your
     program's performance, since the line-drawing primitive will not
     attempt to update those nonexistent pixels.


Pixel-by-Pixel Clipping

     A simplistic approach to clipping is to include a clipping test in the
     inner loop of your line-drawing routines. Just before setting the
     value of each pixel, your routine could compare the current pixel bit
     mask and buffer address with a set of precalculated limits. If the
     address, the bit mask, or both exceeded the limits, the routine would
     not update the video buffer. However, the overhead involved in
     clipping in this manner can be considerably greater than the work
     required to calculate and draw the pixels in the line.

       ���ͻ     In general, avoid integrating code for line clipping into
       � T �     low-level line-drawing routines, regardless of how
       � I �     efficient the code might be. Keeping the functions
       � P �     separate can improve performance, because an application
       ���ͼ     can invoke the line-drawing routines directly, bypassing
                 the clipping code altogether when it's not needed.


A Brute-Force Approach

     Another way to clip a line is to use its equation to calculate where,
     if anywhere, the line segment to be drawn intersects the edges of the
     rectangular display region. For example, in Figure 6-5, the slope m
     of the line is

     m = dy/dx = (y2-y1)/(x2-x1) = (100-40)/(750-150) = 0.1

     The y-intercept can be calculated by substituting x1 and y1 into the
     equation of  the line:

     b = y1 - m*x1 = 40 - (0.1*150) = 25

     The equation of the line is thus

     y = 0.1*x + 25

     To calculate the coordinates of the intersections of the line and the
     edges of the window, substitute the x-coordinates of the window's
     vertical edges and the y-coordinates of its horizontal edges into the
     equation. Each time you solve the equation for one side of the
     rectangle, check the result to see whether the intersection point
     actually lies within the line segment to be drawn as well as within
     the rectangle.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 6-5 is found on page 216       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 6-5.  Line segment (150,40)-(750,100) clipped at (639,89) in
     640-by-200 2-color mode.


     This approach to line clipping involves a lot of computation,
     primarily because the equation of the line must be solved four times
     for every line segment you clip. You must also check the results
     against the limits of the line segment to determine whether the
     intersection points fall between the endpoints. Furthermore, you must
     still handle special cases such as horizontal and vertical lines or
     degenerate "lines" consisting of a single pixel. This computational
     burden makes brute-force clipping impractical.


A Better Algorithm

     A more efficient algorithm for line clipping compares the endpoints of
     the line segment with the boundaries of the rectangular region before
     computing intersection points. Thus, little computation is performed
     for lines that need not be clipped. The Sutherland-Cohen algorithm,
     which uses this approach, is widely known because of its simplicity
     and computational efficiency. (See Sproull and Sutherland, "A Clipping
     Divider," Conference Proceedings, Fall Joint Computer Conference,
     volume 33, pp. 765-776. AFIPS Press, 1968.)

     Conceptually, the algorithm extends the edges of the rectangular
     clipping region, dividing the plane into nine regions (see Figure
     6-6). Each endpoint of the line segment to be clipped falls into
     one of these regions. Identifying the region that corresponds to each
     endpoint makes it easy to determine the location of the line segment
     relative to the rectangular clipping area.


                  �              �
         0011     �     0010     �     0110
                  �              �                Mapping of bits
                  �              �                in 4-bit codes
     �����������������������������������������
                  �(x(sub ul),y(sub ul))          bit 0:1 = left
                  �              �                    1:1 = above
         0001     �     0000     �     0100           2:1 = right
                  �              �                    3:1 = below
     �����������������������������������������
                  �              �(x(sub lr),y(sub lr))
                  �              �
         1001     �     1000     �     1100
                  �              �

     Figure 6-6.  Rectangular clipping using the Sutherland-Cohen
     algorithm.


     The algorithm uses a computational shortcut to determine the relative
     location of the line segment. Each of the nine regions is represented
     by a 4-bit code; each bit corresponds to whether the region is above,
     below, left, or right of the clipping rectangle. The relationship of
     the endpoints to the rectangle is then quickly determined by bitwise
     combination of the 4-bit codes.

     If the logical OR of the two codes is 0 (that is, the 4-bit code for
     both endpoints is 0), both endpoints lie within the rectangle, and no
     clipping is needed. If the logical AND of the two 4-bit codes is
     nonzero, both endpoints lie on the same side of the rectangle, and the
     entire line is clipped out. These tests can be performed rapidly,
     because both the bitwise AND and OR operations can be implemented in
     single 80x86 machine instructions.

     If the logical OR of the endpoints' 4-bit codes is nonzero but the
     logical AND is 0, then the line segment is clipped against one of the
     rectangle's edges. The values of the 4-bit codes then determine which
     edge is used. The resulting intersection point becomes a new endpoint
     for the line segment.

     This process is repeated for the new line segment. The 4-bit codes are
     recalculated, the bitwise comparison is again performed, and, if
     necessary, a new endpoint is calculated. Since a rectangle has four
     sides, the algorithm requires at most four iterations.

     The routine Clip() in Listing 6-11 is a C illustration of the
     Sutherland-Cohen algorithm. The while block repeats while neither of
     the two termination conditions (Inside or Outside) is true. The 4-bit
     codes are used to determine which of the four sides of the rectangle
     the clipping calculation uses. The intersection point between the line
     segment and the side of the rectangle becomes a new endpoint. At the
     bottom of the while block, the 4-bit code for the new endpoint is
     calculated, and the loop repeats.


���������������������������������������������������������������������������

     Listing 6-11.  An implementation of the Sutherland-Cohen clipping
     algorithm.

���������������������������������������������������������������������������


     A program could call Clip() before drawing a line with a fast
     primitive such as Line(). If you are careful to define the values XUL,
     YUL, XLR, and YLR as variables rather than constants, you can use
     Clip() in any video mode. Furthermore, line clipping need not be
     limited to clipping lines to the limits of available RAM in the video
     buffer. You may instead want to define an arbitrary rectangular region
     in the video buffer and clip lines against it. A good high-level video
     graphics interface supports clipping into such arbitrary regions.



                      7  Circles and Ellipses

                      Circles and Pixel Scaling

                    An Ellipse-drawing Algorithm
                     Scan-converting an Ellipse
                      An Incremental Algorithm
                      A Typical Implementation
                        Problems and Pitfalls
                              Accuracy

                            Optimization

                              Clipping

                            True Circles



     Circles and ellipses are probably the most common graphics elements
     other than straight lines. This chapter describes techniques for
     displaying circles, ellipses, and arcs with IBM video hardware. These
     techniques are similar to the algorithms and programming examples for
     displaying straight lines (described in Chapter 6). Although an
     ellipse-drawing routine is somewhat more complicated than a routine
     for drawing straight lines, the algorithmic design and programming
     techniques are similar.


Circles and Pixel Scaling


     The only way to draw a circle on the IBM video subsystems discussed in
     this book is to calculate and draw an ellipse. The reason is that the
     horizontal scale in which pixels are displayed differs from the
     vertical scale in most graphics modes (Chapter 4). If you display a
     "circle" whose pixels are computed without scaling, what you see on
     the screen is an ellipse. For example, Figure 7-1a shows a "circle"
     with a radius of 100 pixels in both horizontal and vertical directions
     as displayed in a 640-by-200 graphics mode.

     Because of this problem of pixel scaling, drawing a circle on the
     screen requires that you compute the pixels that correspond to a
     mathematical ellipse. In other words, to draw a circle that really
     looks like a circle, you must compute an ellipse whose major and
     minor axes are in the same ratio as the pixel coordinate scaling
     factor. On the screen, such an ellipse appears circular (see Figure
     7-1b). For this reason, this chapter concentrates on a practical
     algorithm for drawing ellipses.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-1 is found on page 222       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-1.  In Figure 7-1a, a mathematical circle with a 100-pixel
     radius appears elliptical in 640-by-200 2-color mode. In Figure 7-1b,
     an ellipse whose axes have been properly scaled appears circular when
     displayed in this mode.


An Ellipse-drawing Algorithm


Scan-converting an Ellipse

     You can use the algebraic formula for an ellipse to compute x- and y-
     coordinates for all of the pixels that represent a given ellipse. As
     in the case of scan-converting a straight line, many of these pixel
     coordinates will necessarily approximate the actual values, and the
     resulting figure will be jagged. This effect is especially noticeable
     when displaying a very thin ellipse (see Figure 7-2), but in most
     cases this side effect is acceptable.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-2 is found on page 223       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-2.  A thin ellipse can appear jagged when it is scan-
     converted.


     You can use the equation of the ellipse

     (x - xc)^2  +  (y - yc)^2 = 1
        a^2            b^2

     to scan-convert and display an ellipse. This equation describes an
     ellipse centered at (xc,yc) with major and minor axes a and b parallel
     to the x- and y-axes. However, the computational overhead of drawing
     ellipses by solving this equation, as in Listing 7-1, is very large.
     The multiplication, division, and square-root operations to determine
     each pixel's coordinates are very time-consuming. A better approach is
     to compute pixel coordinates incrementally in a manner similar to that
     used in the line-drawing algorithm in Chapter 6.


���������������������������������������������������������������������������

     Listing 7-1.  Drawing an ellipse using the equation of the
     ellipse.

���������������������������������������������������������������������������


An Incremental Algorithm

     The derivation of an incremental algorithm for drawing ellipses
     resembles the derivation of Bresenham's line algorithm. The ellipse-
     drawing algorithm draws an ellipse pixel by pixel. After drawing each
     pixel, the algorithm selects the next pixel by determining which of
     the current pixel's two neighbors is closer to the actual ellipse.

     Creating an ellipse-drawing algorithm is easiest for an ellipse
     centered at the origin of the coordinate system, with major and minor
     axes congruent with the x- and y-axes (see Figure 7-3). The equation
     of such an ellipse is

     b^2x^2 + a^2y^2 - a^2b^2 = 0

     Because the ellipse is symmetric in relation to both the x- and y-
     axes, you only need derive an algorithm to draw one of its quadrants.
     Your routine can then determine the pixel coordinates in the other
     three quadrants by symmetry.

       ���ͻ     If you need an algorithm to draw ellipses with axes that
       � T �     are not parallel to the video buffer's x- and y-axes,
       � I �     refer to M. L. V. Pitteway, "Algorithm for Drawing
       � P �     Ellipses or Hyperbolae with a Digital Plotter," Computer
       ���ͼ     Journal vol. 11 no. 3 (November 1967), p. 282.

     The algorithm presented here is known as the "midpoint algorithm." It
     draws an ellipse iteratively, pixel by pixel. For each pixel it draws,
     the algorithm selects which of the pixel's neighbors is closer to the
     ellipse by computing whether the point halfway between the pixels lies
     inside or outside the ellipse (see Figure 7-4). (This algorithm was
     described by J. R. Van Aken in "An Efficient Ellipse-Drawing
     Algorithm," IEEE Computer Graphics and Applications, September 1984,
     p. 24, and improved by M. R. Kappel in "An Ellipse-Drawing Algorithm
     for Raster Displays," Fundamental Algorithms for Computer Graphics,
     R. A. Earnshaw [editor], Springer-Verlag 1985, p. 257.)


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-3 is found on page 225       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-3.  An ellipse centered at the origin of the coordinate
     system.


     To determine which pixel lies closer to the ellipse, the algorithm
     uses the value of the equation of the ellipse at the midpoint between
     the pixels. If the value is 0, the midpoint lies on the ellipse. If
     the value is negative, then the midpoint lies inside the ellipse; if
     the value is positive, the midpoint is outside the ellipse. Thus, the
     algorithm can choose which of the two pixels lies closer to the
     ellipse by examining the value's sign.

     One complication lies in determining which pair of neighboring pixels
     to investigate at each step in the iteration. This depends on dy/dx,
     the slope of the tangent to the ellipse (see Figure 7-5). When dy/dx
     is greater than -1, the algorithm chooses between two vertically
     oriented pixels (see Figure 7-6a). When dy/dx is less than -1, the
     choice is between two horizontally oriented pixels (see Figure 7-6b).

     While dy/dx is greater than -1, the algorithm iteratively determines,
     for each pixel it draws, whether neighboring pixel A or B is
     closer to the ellipse. This is done by deciding whether the mid-
     point between A and B lies inside or outside the exact ellipse. In
     Figure 7-6a, the pixel selected in the previous iteration is at
     (x(sub i-1),y(sub i-1)). The midpoint between A and B is therefore
     (x(sub i-1)+1,y(sub i-1)-1/2).

     The algorithm chooses between pixel A and pixel B by examining the
     sign of the value of the ellipse equation evaluated at the midpoint:

     d = b^2(x(sub i-1)+1)^2 + a^2(y(sub i-1)-1/2)^2 - a^2b^2


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-4 is found on page 226       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-4.  Three iterations of the midpoint algorithm. After drawing
     the black pixel in illustration 7-4a, the algorithm chooses to draw
     either pixel A or pixel B by comparing the midpoint M to the actual
     ellipse. Because M is inside the ellipse, it chooses pixel A.
     Illustrations 7-4b and 7-4c represent the next two iterations.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-5 is found on page 227       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-5.  The slope of the tangent to the ellipse within the first
     quadrant.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-6 is found on page 227       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-6.  The midpoint algorithm chooses between A and B by
     substituting x and y at the midpoint M into the formula for the
     ellipse and testing the sign of the result. If the result is
     positive, pixel B is chosen; if the result is  negative, pixel A is
     chosen.


     The variable d, the value of the function at the midpoint, is the
     algorithm's decision variable. As in Bresenham's line algorithm, the
     key to this algorithm's speed is that it can compute d iteratively on
     the basis of its value at each previous step in the iteration. The
     difference between the current value of d and its previous value is

     d(sub i)-d(sub i-1) = [b^2(x(sub i-1)+1)^2 +
                             a^2(y(sub i-1)-1/2)^2 - a^2b^2] -
                             [b^2(x(sub i-1))^2 + a^2(y(sub i-1)-1/2)^2 -
                             a^2b^2]

                         = b^2(2x(sub i-1)+1)

                         = 2b^2x(sub i-1) + b^2

     Now, finding the difference between d(sub i) and d(sub i-1) (that
     is, dx) still involves multiplying the previous value of x by a
     constant. You can avoid this multiplication, however, by computing dx,
     as well as d, incrementally; that is, by adding 2b^2 to dx at each
     step of the iteration.

     If pixel A is nearer to the ellipse (that is, d(sub i) > 0), the newly
     calculated value of d(sub i) can be used as d(sub i-1) in the next
     iteration. If pixel B is nearer, however, d(sub i) must be adjusted
     for the downward step in the y direction. In this case, the value of
     the equation of the ellipse for the midpoint below pixel B must be
     computed. If (x(sub i-1),y(sub i-1)+1/2) is the midpoint between
     pixels A and B, then (x(sub i-1),y(sub i-1)-1/2) is the midpoint below
     pixel B, and dy is then

     d(sub i)-d(sub i-1) = [b^2(x(sub i-1))^2 +
                             a^2(y(sub i-1)-1/2)^2 - a^2b^2] -
                             [b^2(x(sub i-1))^2 + a^2(y(sub i-1)+1/2)^2 -
                             a^2b^2]

                         = -2a^2y(sub i-1)

     When dy/dx is less than -1, pixels A and B are horizontal rather than
     vertical neighbors (see Figure 7-6b). The values of dy and dx are
     therefore computed somewhat differently. When pixel B is chosen, the
     midpoint at (x(sub i-1)+1/2,y(sub i-1)-1) is used, so the increment
     for d is

     d(sub i)-d(sub i-1) = [b^2(x(sub i-1)+1/2)^2 +
                             a^2(y(sub i-1)-1)^2 - a^2b^2] -
                             [b^2(x(sub i-1)+1/2)^2 + a^2(y(sub i-1))^2 -
                             a^2b^2]

                         = a^2(-2y(sub i-1)+1)

                         = -2a^2y(sub i-1) + a^2

     Also, when pixel A is chosen, d must be adjusted for the step in the
     rightward direction:

     d(sub i)-d(sub i-1) = [b^2(x(sub i-1)-1/2)^2 +
                             a^2(y(sub i-1))^2 - a^2b^2] -
                             [b^2(x(sub i-1)+1/2)^2 + a^2(y(sub i-1))^2 -
                             a^2b^2]

                         = -2b^2x(sub i-1)

     These derivations provide a way to draw an ellipse iteratively, with
     only simple addition and subtraction required within the iterative
     loops. The analysis distinguishes between the case where dy/dx is
     greater than -1 and the case where dy/dx is less than -1. You
     determine when dy/dx has reached -1 by differentiating the equation of
     the ellipse and setting dy/dx to -1.

     d    (b^2x^2 + a^2y^2 - a^2b^2) = 0
     dx


     2b^2x + 2a^2y dy = 0
                   dx


     dy  =  -2b^2x
     dx      2a^2y

     Thus, at the point where dy/dx = -1,

     2b^2x = 2a^2y

     Because the algorithm already keeps track of the quantities 2b^2x and
     2a^2y to compute the differentials dx and dy, these quantities can be
     used to detect where dy/dx reaches -1. The algorithm can then start at
     the point (0,b) on the y-axis and proceed clockwise around the ellipse
     until it reaches (a,0).

     Initially, the quantity dy/dx is greater than -1, and the choice is
     made iteratively between vertically oriented pixels (see Figure
     7-6a). When dy/dx reaches -1, the algorithm chooses between
     horizontally oriented pixels (see Figure 7-6b) and continues to do
     so until it reaches the x-axis.

     The only remaining computation occurs when the algorithm reaches the
     pixel for which dy/dx = -1. At this point, a new value for d will
     already have been computed (M(sub old) in Figure 7-7) under
     the assumption that the next midpoint would have been between two
     vertically oriented pixels. Therefore, the value of d must be adjusted
     to reflect the value of the ellipse function at the midpoint between
     two horizontally oriented pixels (M(sub new) in Figure 7-7). The
     increment for d (from M(sub old) to M(sub new)) in this
     case is

     d(sub i)-d(sub i-1) = [b^2(x(sub i-1)+1/2)^2 +
                             a^2(y(sub i-1)-1)^2 - a^2b^2] -
                             [b^2(x(sub i-1)+1)^2 + a^2(y(sub i-1)-1/2)^2 -
                             a^2b^2]

                         = b^2(-x(sub i-1)-3/4) + a^2(-y(sub i-1)+3/4)

                         = 3(a^2-b^2)/4 - (b^2x(sub i-1) + a^2y(sub i-1))

     Again, since the algorithm already uses the quantities 2b^2x and
     2a^2y, the increment for d at this point can be computed by

     d(sub i)-d(sub i-1) = 3(a^2-b^2)/4 - (2b^2x(sub i-i)+2a^2y(sub i-1))/2

     Adding this value to d at the point where dy/dx = -1 gives the new
     value for d.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-7 is found on page 230       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-7.  When the value of dy/dx  reaches -1, a new
     midpoint (M(sub new)) is selected, and d, which has
     already been computed for M(sub old), is adjusted to reflect the
     value of the equation of the ellipse at M(sub new).


A Typical Implementation

     The C routine in Listing 7-2 is fast and efficient because all
     decision-variable computation within the inner iterative loops has
     been reduced to addition and subtraction. The routine eliminates
     multiplication within the inner loops by precalculating the values of
     a^2, b^2, 2a^2, and 2b^2. The initial values for the decision
     variables are computed assuming that the first pixel to be drawn is at
     (0,b). Thus, the initial value of d is calculated for the midpoint
     between the pixels at (1,b) and (1,b-1); that is, at (1,b-1/2):

     d = b^2(1)^2 + a^2(b-1/2)^2 - a^2b^2

       = b^2 - a^2b + a^2/4

     The initial values for dx and dy are

     dx = 2b^2(x(sub 0)) = 0

     and

     dy = 2a^2(y(sub 0)) = 2a^2b

     The routine Ellipse() follows the algorithm closely. It first draws
     all the pixels between (0,b) and the point where dy/dx becomes -1.
     Then it updates d as in Figure 7-7. Iterative pixel selection
     continues until the routine reaches the x-axis. The routine calls the
     function Set4Pixels() to replicate each pixel in each of the four
     quadrants of the ellipse. Set4Pixels() also translates each pixel's
     coordinates relative to the actual center of the ellipse.


���������������������������������������������������������������������������

     Listing 7-2.  A high-level implementation of the midpoint
     algorithm.

���������������������������������������������������������������������������


Problems and Pitfalls

     One difficult problem you'll encounter is that tiny ellipses appear
     somewhat angular rather than elliptical when they are scan-converted.
     When an ellipse is small and comparatively few pixels are used to
     display it, the best approximation generated by the algorithm can
     appear polygonal.

     Although it is possible to redesign the ellipse-drawing algorithm to
     draw "fatter" or "thinner" ellipses in this situation, a better
     solution is to display the ellipses with higher resolution. Tiny
     ellipses look much better with 640-by-480 resolution than they do with
     320-by-200 resolution.

     A related problem is that very eccentric ellipses may be drawn
     inaccurately at the points where they curve most sharply. This
     happens when the point where  dy/dx = -1 lies nearly adjacent to
     either the x-axis or the y-axis. Again, you can modify the algorithm
     to accommodate this situation, but if your application requires
     accurate representations of very thin ellipses, a better solution is
     to display them at higher resolution.

     A further consideration involves "degenerate" ellipses for which the
     length of either the major or minor axis is 0 (that is, a = 0 or b =
     0). Because either dy or  dx is 0 in this situation, the iterative
     routines do not terminate correctly. In these cases, either test for
     the special condition before executing the loops (and draw the
     appropriate straight line) or modify the termination conditions of the
     loops.


Accuracy

     As does Bresenham's line algorithm, the midpoint algorithm attempts to
     minimize the vertical or horizontal distance to the ellipse from the
     pixels it selects. This is faster than minimizing the distance between
     each pixel and the nearest point to it on the ellipse, but if you
     examine its performance closely, you may find rare occasions when the
     pixel that the midpoint algorithm selects is not the one closest to
     the ellipse. Nevertheless, the accuracy of the midpoint algorithm in
     selecting the best pixels to represent the ellipse is sufficient for
     nearly all applications. (For more discussion of this topic, see Van
     Aken and Novak, "Curve-Drawing Algorithms for Raster Displays," ACM
     Transactions on Graphics, April 1985, p. 147, or Kappel, "An Ellipse-
     Drawing Algorithm for Raster Displays," Fundamental Algorithms for
     Computer Graphics, p. 257.)

     Although the source code in Listing 7-2 is a straightforward
     implementation of the algorithm, you need to remember a few details if
     you plan to modify the code or translate it into another language. It
     is important to compute all decision variables as 32-bit integers.
     Because these values involve the squaring of pixel coordinates, 16
     bits are inadequate to maintain precision.

     Another detail to remember is that this routine can draw the same
     pixels twice. This is an artifact of the ellipse's four-way symmetry.
     For example, the pixels at (�a,0) and (0,�b) are updated twice by
     Set4Pixels() in Listing 7-2. This becomes a problem when you use the
     routine to XOR pixels into the video buffer. If you perform a XOR on
     these pixels twice, they disappear. To avoid this, either test for
     these special cases in Set4Pixels() (Listing 7-3) or modify Ellipse()
     to draw these pixels separately.


���������������������������������������������������������������������������

     Listing 7-3.  A modified version of Set4Pixels that avoids updating
     the same pixel twice.

���������������������������������������������������������������������������


Optimization


     For many applications, a high-level language implementation such as
     the one in Listing 7-2 is fast enough. The slowest part of the high-
     level version of Ellipse() is its repeated calls to the pixel-setting
     routine, which recomputes pixel addresses with every iteration. By
     writing Ellipse() in assembly language, you can calculate the pixel
     addresses much more efficiently. The resulting assembly-language
     routine is about three times faster than the equivalent high-level
     version.

     Listing 7-4 is a typical assembly-language implementation, in this
     case for the EGA. Note how the routine Set4Pixels maintains a set of
     four buffer offsets and bit masks instead of (x,y) coordinates for the
     four pixels it updates. When  Set4Pixels increments a pixel x-
     coordinate, it rotates a bit mask in the proper direction. The y-
     coordinates are incremented by adding the number of bytes in each line
     of pixels to the buffer offset. (This is the same technique used in
     the line routines in Chapter 6.) This method of video buffer
     addressing is much faster than making a call to a SetPixel() function
     for every pixel in the ellipse.


���������������������������������������������������������������������������

     Listing 7-4.  An assembly-language implementation of the midpoint
     algorithm.

���������������������������������������������������������������������������


     One optimization technique used in Chapter 6 is omitted here. In
     practice, minimizing video buffer accesses by setting more than one
     pixel at a time in each byte of the buffer is not worthwhile. The
     overhead involved in keeping track of which bytes contain more than
     one updated pixel is greater than the time saved in reducing video
     buffer accesses. Besides, the code is complicated enough already.


Clipping


     If you clip an ellipse within a rectangular window, the result is an
     arc (see Figure 7-8). The place to perform the clipping is in the
     Set4Pixels() routine. You can clip each pixel's (x,y) coordinates
     against the window boundary before you call SetPixel() to update the
     video buffer.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 7-8 is found on page 241       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 7-8.  Clipping an ellipse produces an arc.


     Implementing clipping in this way slows the ellipse-drawing routine
     somewhat. If your application rarely requires clipping, consider
     implementing two different versions of Set4Pixels(), one that performs
     clipping and one that omits it. Before calling Ellipse(), you can
     compare the maximum and minimum coordinate values of the pixels in the
     ellipse (xc � a,yc � b) with the clipping boundaries to determine
     whether it can be drawn without clipping. Only if clipping is required
     do you need to use the slower, clipping version of Set4Pixels().


True Circles


     After you implement the ellipse routine, you can draw true circles in
     all graphics modes on PC and PS/2 video subsystems. To display a
     circle, draw an ellipse with its major and minor axes scaled in
     proportion to your video display's horizontal and vertical
     resolutions. Listing 7-5 shows how you might do this in a 640-by-350
     graphics mode on an EGA.

     Because the scaling varies with the video mode, the same routine
     cannot draw circles in different video modes unless it accommodates
     the pixel coordinate scaling in each mode. Figure 4-9 in Chapter 4
     is a table of pixel scaling factors for all graphics modes.


���������������������������������������������������������������������������

     Listing 7-5.  Using pixel coordinate scaling to display a circle in
     640-by-350 16-color mode.

���������������������������������������������������������������������������



                           8  Region Fill


                          What Is a Region?
              Interior and Border Pixels � Connectivity

                 Simple Fills with Horizontal Lines

                    Three Region Fill Algorithms
                        Simple Recursive Fill
                         Line-Adjacency Fill
                             Border Fill

                      Comparing the Algorithms



     This chapter describes several methods for filling a region of the
     video buffer with a pattern of pixels. Region fill techniques are used
     in many areas of computer graphics programming, including color
     manipulation, shading, and representation of three-dimensional
     objects, as well as in applications such as image processing, image
     data transmission, and computer animation.

     This chapter contains working source code for three region fill
     algorithms, but the discussion is by no means comprehensive. These
     algorithms and implementations are intended to be working models that
     you can experiment with, modify, and optimize for your own
     applications.


What Is a Region?


     A region is a connected group of pixels in the video buffer that is
     delineated by some sort of boundary. You can think of a region in the
     video buffer as comprising an interior and a border. To understand how
     the algorithms in this chapter are implemented, however, it is worth
     considering how a region can be clearly defined in terms of pixel
     values and pixel geometry in the video buffer.


Interior and Border Pixels

     In this chapter, a region is assumed to be surrounded by pixels whose
     values distinguish them from the pixels in the interior. You could
     assume, for instance, that all interior pixels have the same value, in
     which case a border pixel is simply any pixel whose value differs from
     the values of pixels in the interior (see Figure 8-1a). You could
     also assign a range of allowable pixel values to both interior and
     border pixels. The algorithms in this chapter adhere to the convention
     that all pixels in the border have one specified value and pixels in
     the interior can be of any other value (see Figure 8-1b).

     In many applications, it is practical to use a range of pixel
     coordinates to define all or part of a region's border. The definition
     of a "border pixel" can thus be broadened to include pixels outside a
     predetermined range of (x,y) coordinates. In this way a region can be
     bounded by the limits of the screen buffer or by a software window, as
     well as by pixels of a predetermined value or range of values.


       ��    ��    ��        ��
           ��  ��        ��                      ������������
     ��    �����������۱�                      �������������۱�
       ����������������  ��                    �������������۱�
   ��      ������������                        �������������۱�
       ��  ������������    ��                  �������������۱�
           ��          ��                        ������������
               ��         ��

   a.                                          b.

     Figure 8-1.  In Figure 8-1a, a region is defined by interior pixels of
     a given value. In Figure 8-1b, a region is defined by border pixels of
     a given value.


Connectivity

     To distinguish border pixels from interior pixels, you must also
     specify the way the pixels are connected. If you allow interior pixels
     to be connected diagonally as well as orthogonally (horizontally and
     vertically), you must assume that the border pixels surrounding the
     region are always connected orthogonally (see Figure 8-2a).
     Conversely, if you allow border pixels to be diagonally connected, you
     must constrain interior pixels to orthogonal connections (see Figure
     8-2b). Consider the reason for this constraint: If both border and
     interior pixels could be diagonally connected, then interior pixels
     could be connected to pixels outside the border at places where border
     pixels are diagonally connected.


     �����������������������������������������������Ŀ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  ��۳�۳�۳�۳�۳�۳�۳�۳  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  ��۳����������������� �۳  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  ��۳�������������������۳  �  �  �  �
     ����������������������������������������Ĵ
     �  �  �  �  ��۳�������������������۳  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  ��۳�������������������۳  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  ��۳�۳�۳�۳�۳�۳�۳�۳  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     ����������������������������������

     a.

     �����������������������������������������������Ŀ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  ��۳�۳�۳�۳�۳�۳  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  ��۳�������������������۳  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  ��۳�������������������������۳  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  ��۳�������������������������۳  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  ��۳�������������������������۳  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  ��۳�������������������۳  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  ��۳�۳�۳�۳�۳�۳  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     �����������������������������������������������Ĵ
     �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �  �
     ����������������������������������

     b.

     Figure 8-2.  Connectivity of pixels. In Figure 8-2a, border pixels
     (black) are orthogonally connected, while interior pixels (gray) are
     both orthogonally and diagonally connected. In Figure 8-2b, border
     pixels are both orthogonally and diagonally connected, so interior
     pixels are only connected orthogonally.


Simple Fills with Horizontal Lines


     Before you become involved with the intricacies of region fill
     algorithms, remember that you can fill many regular geometric shapes
     without using a specialized algorithm. A common application of this
     technique is shown in Listing 8-1. This routine fills a rectangular
     region in the video buffer with pixels of a specified value. It is
     fast, because the subroutine that draws horizontal lines is fast.


���������������������������������������������������������������������������

     Listing 8-1.  Filling a rectangle with horizontal lines.

���������������������������������������������������������������������������


     Creating similar routines to draw filled triangles, hexagons, and
     circles is not difficult, because of these objects' regularity and
     symmetry. Writing a general-purpose routine that can fill convex or
     irregular polygons is more difficult; in  this case, you must scan-
     convert each of the polygon's sides (using, for example, Bresenham's
     algorithm from Chapter 6) to create a list of the pixels that define
     the border of the polygon. This list contains pairs of pixels that can
     then be connected with horizontal lines to fill the interior of the
     polygon.

       ���ͻ     Several good textbooks deal with the problem of scan-
       � T �     converting and filling arbitrary polygons. For example,
       � I �     see Fundamentals of Interactive Computer Graphics by J. D.
       � P �     Foley and A. VanDam (Addison-Wesley 1982).
       ���ͼ

     Though polygon fill techniques have many uses, some applications
     require filling a region with completely arbitrary borders, such as a
     map or an irregular shape that was drawn interactively. In this case,
     your fill routine must define the region using only the pixel values
     in the video buffer. The remainder of this chapter presents algorithms
     and working source code for three such routines.


Three Region Fill Algorithms


     The three algorithms described here are all designed with IBM video
     subsystems in mind. They use the pixel manipulation and line-drawing
     subroutines developed in Chapters 4, 5, and 6. Also, all three
     algorithms assume that border pixels can be diagonally connected
     and that interior pixels must be orthogonally connected (as in Figure
     8-2b). You can thus fill regions with boundaries drawn using the
     line-drawing and ellipse-drawing routines in Chapters 6 and 7, since
     those line and ellipse routines draw diagonally connected figures.

     Furthermore, all three algorithms can fill a region that contains a
     hole in its interior (see Figure 8-3). Such holes are collections of
     border pixels that are  not contiguous with the pixels in the region's
     outer border. Each algorithm is designed to detect the presence of
     holes and to properly fill the interior pixels surrounding them.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 8-3 is found on page 247       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 8-3.  A region whose interior (gray pixels) contains two holes.


Simple Recursive Fill

     One way to fill a region is to start by filling a given "seed" pixel
     in its interior, and then to fill each of the seed's immediate
     neighbors, each of the neighbors' neighbors, and so on until the
     entire region is filled. The C routine in Listing 8-2, PixelFill(),
     shows how to do this. In PixelFill(), as in the other algorithms in
     this chapter, pixels in the interior of the region are assumed to be
     connected horizontally and vertically, but not diagonally.
     (PixelFill() can be easily modified to fill diagonally connected
     regions if so desired.)


���������������������������������������������������������������������������

     Listing 8-2.  A simple recursive region fill.

���������������������������������������������������������������������������


     Before it fills a pixel, PixelFill() examines the pixel's value to
     determine whether filling is required. If the pixel is neither a
     border pixel nor a previously filled pixel, the routine updates the
     pixel value and calls itself recursively. Because PixelFill() does not
     fill previously filled pixels, the routine works properly even in
     regions with holes.

     Although simple, PixelFill() is inefficient. One reason is that on
     average only one of the four recursive calls to PixelFill() ever does
     anything. (Each pixel can only be filled once, but each time a pixel
     is filled, four recursive calls are made to the function. The only
     exception is in the case of the seed pixel.) Thus, PixelFill()
     accomplishes nothing about 75 percent of the time, which is not very
     efficient.

       ���ͻ     Another problem with PixelFill() is that the depth of
       � T �     recursion can increase beyond the limits of available
       � I �     stack memory. For example, the default stack space for
       � P �     code generated with the Microsoft C compiler is 2 KB. You
       ���ͼ     can easily exceed this limit by using PixelFill() to fill
                 even relatively small regions.


Line-Adjacency Fill

     A better approach is to regard the interior of the region as a group
     of adjacent line segments that are connected vertically instead of as
     a group of pixels connected both vertically and horizontally. An
     algorithm that fills adjacent line segments tends to be much more
     efficient than a pixel-by-pixel recursive fill, because it inspects
     and fills pixels more efficiently. Also, this conception of the region
     is closer to the physical representation of pixels in the video
     buffer, in which pixels are arranged in horizontal rows to be
     displayed during the raster scan.

     The routine in Listing 8-3, LineAdjFill(), implements a line-adjacency
     algorithm for filling a region. Its general strategy is to locate each
     group of horizontally connected pixels in the interior of the region.
     Like the simple recursive fill, this algorithm also starts at a seed
     pixel known to be in the region's interior. It scans left and right to
     find the ends of the seed pixel's row, then fills the entire row.


���������������������������������������������������������������������������

     Listing 8-3.  A line-adjacency fill routine.

���������������������������������������������������������������������������


     The algorithm proceeds by locating all groups of horizontally
     connected pixels that are vertically adjacent to the group it just
     scanned. Each time it finds an adjacent group of not-yet-filled
     pixels, LineAdjFill() is called recursively to fill them. The
     algorithm terminates when all interior pixels have been filled.

     Figure 8-4 illustrates the order in which LineAdjFill() fills a
     simple region comprising seven line segments. The seed pixel is
     assumed to lie inside line segment 1, and the routine is initially
     called with an upward search direction. The routine first searches the
     row of pixels above the seed (that is, line segment 2) for unfilled
     pixels. Because the row has not yet been filled, the routine is called
     recursively to fill it. Similarly, line segments 3 and 4 are filled by
     subsequent recursive calls to LineAdjFill(). At this point, neither
     line segment 4 nor line segment 3 has any unfilled pixels adjacent to
     it, but when the pixels below line segment 2 are scanned, line segment
     5 is discovered and filled. Finally, line segments 6 and 7 are filled
     recursively.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 8-4 is found on page 250       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 8-4.  Given a seed pixel in line segment 1, LineAdjFill() fills
     the adjacent line segments in this region in numerical order.


       ���ͻ     A line-adjacency graph (LAG) is essentially a diagram of
       � T �     the connections between the adjacent line segments in the
       � I �     interior of a region (see Figure 8-5). The problem of
       � P �     filling a region is equivalent to traversing its LAG in
       ���ͼ     such a way that all nodes in the graph are visited. In
                 practice, traversing the LAG is relatively easy (there are
                 several textbook algorithms for graph traversal) compared
                 to generating the graph given only the pixels in the video
                 buffer (which is essentially what LineAdjFill() does). For
                 more information see "Filling Regions in Binary Raster
                 Images: A Graph-Theoretic Approach" by U. Shani (SIGGRAPH
                 Proceedings 1980, pp. 321-327).


             ������������������������������������������ͻ
             �                                          �
             �    Figure 8-5 is found on page 251       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 8-5.  A simple line-adjacency graph (LAG).


     LineAdjFill() is much more efficient than PixelFill(), because it
     rarely visits a pixel more than once to determine whether it needs to
     be filled. Each time the routine is called, it fills one line segment
     and then inspects the adjacent rows of pixels for unfilled pixels. The
     routine does not examine pixels that were inspected during the
     previous invocation of the routine (that is, pixels between PrevXL and
     PrevXR), nor does it inspect pixels to be filled by subsequent
     invocations (that is, pixels between the current value of x and the
     value returned from a call to LineAdjFill()). The recursive logic
     becomes clear when you trace the execution of the routine as it fills
     a region such as the one diagrammed in Figure 8-4.

       ���ͻ     If you implement a line-adjacency fill algorithm in
       � T �     assembly language, you can improve its efficiency by
       � I �     maintaining a push-down stack of parameters and executing
       � P �     the function iteratively rather than recursively. The
       ���ͼ     skeleton of the algorithm then becomes

                 push ( .. initial parameters on stack .. );
                 while ( .. stack not empty .. )
                   LineAdjFill();

                 The fill routine pops the topmost parameters off the stack
                 and pushes new sets of parameters instead of calling
                 itself recursively.

                 LineAdjFill
                 {
                   pop ( .. current parameters off of stack .. )
                   .
                   .
                   .
                   if ( .. adjacent line needs to be filled .. )
                     push ( .. new parameters .. )
                 }

                 In assembly language, a single machine instruction can
                 perform each push and pop, so the algorithm's performance
                 is greatly improved.

     A line-adjacency algorithm can be adapted to fill a region with a
     pattern of pixels as well as with a single pixel value. For this
     reason, it is used commonly in commercial graphics packages. (IBM
     BASICA and Microsoft GW-BASIC are examples.) Modifying the algorithm
     to do patterned fills requires that the horizontal line-drawing
     routine be replaced with a pattern-drawing routine and that the test
     that determines whether a pixel has been filled take into account the
     pixel values in the fill pattern.

     These modifications may seem innocuous, but they can significantly
     degrade the fill routine's performance. The logic required to detect
     the presence of previously filled pixels can be complicated,
     particularly if you allow the fill pattern to contain pixels with the
     same value as border pixels.


Border Fill

     Because the border of a region defines the extent of its interior, it
     is possible to fill a region by following the connected border pixels
     at the ends of the adjacent line segments that make up the interior.
     (See "Contour Filling in Raster Graphics" by T. Pavlidis, Computer
     Graphics, August 1981, p. 29). As long as you fill the region at the
     same time that you trace the border, however, this kind of border-
     tracing fill algorithm offers no clear advantage over a line-adjacency
     algorithm.

     However, if you separate the problem of tracing the border from that
     of filling the region's interior, the resulting algorithm becomes more
     flexible. The process of filling a region then breaks down into three
     discrete steps:

     1)  Create an ordered list of the border pixels (trace the border).

     2)  Scan the interior of the region for holes.

     3)  "Connect the dots" in the list from left to right with horizontal
         lines, thereby filling the region.

     The routine BorderFill() in Listing 8-4 performs a region fill using
     this three-step method. The algorithm executes the three steps
     iteratively, once for the boundary of the region and once for each
     hole in the interior of the region.


���������������������������������������������������������������������������

     Listing 8-4.  A region fill routine that traces a region's
     border.

���������������������������������������������������������������������������


     The module TraceBorder() creates a table that contains the pixel
     address  of every pixel in the region's border. SortBP() then sorts
     the table of border pixels by increasing y- and x-coordinates. The
     routine ScanRegion() examines the interior line segment between each
     pair of border pixels in the table. If it detects a border pixel
     within the line segment, ScanRegion() assumes it has encountered a
     hole in the region; it then returns the border pixel's (x,y)
     coordinates so that TraceBorder() and SortBP() can update the table
     with the hole's border pixels. This process continues until the entire
     interior of the region has been scanned. Then FillRegion() uses the
     sorted list of border pixels to fill the region by drawing a
     horizontal line between each pair of pixels in the list.

     TraceBorder() starts with a seed pixel on the right-hand border of the
     region. It steps clockwise from pixel to pixel in the border. Because
     the search proceeds clockwise, the interior of the region is always to
     the right of the direction in which the search is moving. If a pixel
     is not adjacent to the interior, the algorithm does not identify it as
     a border pixel. The algorithm ensures that the border pixels it
     detects are indeed adjacent to the interior by always examining pixels
     to the right of the search direction first.

     The algorithm identifies its search direction with one of the eight
     numeric codes shown in Figure 8-6. (This technique is taken from
     "Algorithms for Graphics and Image Processing" by T. Pavlidis
     [Computer Science Press, 1982].) Thus, in Figure 8-7, the algorithm
     moves from pixel b to pixel c in direction 6 (downward). To  find the
     next pixel in the border, the algorithm starts by examining the pixel
     to the right of direction 6; that is, direction 4. This pixel is not a
     border pixel, but the pixel in direction 5 (pixel d) is, so d is added
     to the list. The algorithm continues to trace the border until it
     returns to the starting pixel. (The search terminates immediately in
     the case of a degenerate "border" consisting of only one pixel.)

     TraceBorder() performs another task in addition to identifying the
     pixels in the border. It also indicates whether each border pixel
     defines the left or right endpoint of a horizontal interior line
     segment. (Because FillRegion() draws horizontal lines from left to
     right, TraceBorder() marks each border pixel with a flag indicating
     whether the pixel can be used as a left border.) Furthermore, if a
     pixel can serve as both a left and a right border (see Figure 8-8),
     TraceBorder() adds it to the table twice. The logic in SameDirection()
     and DifferentDirection() accomplishes these tasks.


     �����������������������Ŀ
     �       �   2   �       �
     �   3   �       �   1   �
     �      �      �      �
     �      \�   �   �/      �
     �����������������������Ĵ
     �       �       �       �
     �  4 �ij       ���  0 �
     �       �       �       �
     �����������������������Ĵ
     �      /�   �   �\      �
     �      �      �      �
     �   5   �       �   7   �
     �       �   6   �       �
     �����������������������

     Figure 8-6.  Numeric codes for border pixel trace directions.


                     ���Ŀ                  Direction
                     � a �           ab        6
                    �������Ŀ       bc        6
                     � b �   �       cd        5
                    ���������       de        5
               /     � c �           ef        4
                �������Ĵ           fg        4
               � d �   �
     ���������������������
     � g � f � e �   �
     ����������������
             �   �
             �����

     Figure 8-7.  Border pixel identification in TraceBorder().


         �����������Ŀ                       �������Ŀ
         �   �   �   �                       �   �   �
     �����������������Ŀ               ��������������Ŀ
     �   �           �   �               �   �       �   �
     ���Ĵ           �������������������������       ���Ĵ
     �   �               � a �   �   �   �           �   �
     ���Ĵ               ��������������           ���Ĵ
     �   �                                           �   �
     ���Ĵ                                           ���Ĵ
     �   �                                           �   �
     ���Ĵ                   ���Ŀ                   ���Ĵ
     �   �                   � b �                   �   �
     �������Ŀ           �����������Ŀ           ���������
         �   �           �   �   �   �           �   �
         ���������������������   ���������������������
             �   �   �   �           �   �   �   �
             �����������           �����������

     Figure 8-8.  Pixels may border the interior on the left, right, or
     both directions: Pixel a is a border pixel on the right of a
     row of interior pixels; it is blocked to its right by other border
     pixels. Pixel b serves as both a left and a right border.


     TraceBorder() may seem complex, but it is a relatively fast routine.
     The slowest steps in BorderFill() are actually SortBP(), which sorts
     the table of border pixels, and ScanRegion(), which searches for
     border pixels in the interior of the region. If SortBP() and
     ScanRegion() are slow, BorderFill() will be slow, because these
     routines are executed iteratively, once for each hole in the region.

     You can significantly improve BorderFill()'s performance by modifying
     TraceBorder() so that it builds its list of border pixels in the
     proper order to begin with, avoiding the sort altogether. You can
     build the ordered list efficiently using any of several data
     structures, including a linked list, a heap, or a fixed-size table.
     This type of modification is particularly effective when the algorithm
     is used to fill regions that contain one or more holes. Instead of
     sorting the list each time it detects a hole, the modified algorithm
     simply inserts the hole's border pixels into the list.

     Writing ScanRegion() in a high-level language is relatively easy, but
     because the routine examines all pixels in the interior of the region,
     you should write it in assembly language so it will execute rapidly.
     Furthermore, using assembly language on the EGA, the VGA, and the
     InColor Card offers a distinct advantage, because the graphics control
     hardware in these subsystems can examine eight pixels at a time and
     indicate which, if any, match the border pixel value. The assembly-
     language routine ScanRight() in Listing 8-5, which can be used in EGA
     and VGA 16-color graphics modes, runs 50 times faster than the C
     version in Listing 8-4.


���������������������������������������������������������������������������

     Listing 8-5.  An assembly-language version of ScanRight().

���������������������������������������������������������������������������


     The fastest step in BorderFill() is the fill itself, because
     horizontal lines can be drawn rapidly. Thus, if you need to fill the
     same region repeatedly or to copy the same filled region several
     times, you can preserve the list of border pixels generated the first
     time you execute BorderFill(). This greatly accelerates subsequent
     fills, because you can skip the border-tracing and sorting steps.


Comparing the Algorithms


     Which region fill algorithm is best? Each algorithm described in this
     chapter has its pros and cons. You can compare them in several ways. A
     valid comparison considers the simplicity of the algorithm, the speed
     of the compiled code, and the suitability of each algorithm for
     particular types of region fills.

     The recursive, pixel-by-pixel algorithm implemented as PixelFill() is
     about as simple as you can get. The source code is short and easy to
     implement in assembly language as well as in a high-level language.
     However, PixelFill() is too inefficient and too highly recursive to be
     generally useful.

     The line-adjacency fill algorithm LineAdjFill() is more complicated
     than PixelFill(). Nevertheless, LineAdjFill() improves on the
     performance of PixelFill() because it examines pixel groups instead of
     individual pixels. LineAdjFill() also runs faster when it is written
     to access the video buffer in one-byte increments instead of one-pixel
     increments. LineAdjFill() is also much less recursive than
     PixelFill(), so its runtime memory requirements are smaller than those
     of PixelFill().

     The three-step algorithm implemented in BorderFill() is more
     complicated and somewhat slower than the other two algorithms. The
     advantage of using BorderFill() is its generality. Its modules can be
     readily adapted to alternate types of region fills, including pattern
     fills and fills of regions defined as numeric lists of (x,y)
     coordinates.

     The performance of BorderFill() depends on the number of holes in the
     region. It is as fast as LineAdjFill() in filling a region without
     holes. However, when the region to be filled looks like Swiss cheese,
     BorderFill() slows down because it must update the sorted list of
     border pixels whenever it fills around a hole.

     Nevertheless, BorderFill() can do several things that the other
     algorithms cannot. For example, it can reliably fill regions that
     contain previously filled pixels. Unlike BorderFill(), both
     PixelFill() and LineAdjFill() rely on the implicit assumption tht no
     interior pixels have the same value as the fill value. Thus,
     BorderFill() correctly fills the region shown in Figure 8-9, but both
     of the other routines fail.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 8-9 is found on page 264       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 8-9.  A test case for fill algorithms. Neither PixelFill() nor
     LineAdjFill() can correctly fill this region with gray pixels, because
     the "holes" are treated as if they have been already filled.


       ���ͻ     You could modify a routine such as LineAdjFill() so that
       � T �     its detection of holes in the region does not depend on
       � I �     the presence of previously filled pixels. This means the
       � P �     algorithm must somehow keep track of pixels it has already
       ���ͼ     filled. One way to do this is to keep track of points
                 where the border reaches a local minimum or maximum (see
                 Figure 8-10). These locations can identify the top and
                 bottom of a hole in the region, enabling the fill
                 algorithm to determine when to stop working its way around
                 the hole.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 8-10 is found on page 265      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 8-10.  An algorithm can detect the presence of a hole in a
     region by locating the border's local maximum and minimum. Pixels
     marked a identify a local maximum. Pixels marked b identify a
     local minimum.


     For some applications, BorderFill() has a strong advantage over the
     other algorithms, because its border-tracing and sorting steps
     generate a list of numeric pixel coordinates. This list completely
     defines a two-dimensional region of pixels. You can translate or
     change the scale of the region by applying the appropriate conversions
     to the list of border pixels. As long as you preserve the pixels'
     order in the list, you can use the FillRegion() routine in
     BorderFill() to fill the region the list defines. For this reason, the
     BorderFill() algorithm is best suited for applications that must copy
     arbitrary regions, change their scale or size, or draw them repeatedly
     into the video buffer.

     Furthermore, by modifying the horizontal line routine in BorderFill()
     you can easily fill a region with an arbitrary pattern or allow pixel
     AND, OR, and XOR functions. Although you can augment PixelFill() or
     LineAdjFill() in this way, the source code can become complicated
     because these algorithms inspect pixels to determine whether they have
     been filled.

     The trade-offs in complexity and performance in these algorithms leave
     a great deal to your programming judgment. No single region fill
     algorithm is best for all possible graphics applications. Your choice
     of implementation should depend on your performance demands, the
     requirements of the application itself, the capabilities of your video
     display hardware, and the effort you can afford to expend in
     integrating and optimizing the code.



                         9  Graphics Text


                     Character Definition Tables
                         Video BIOS Support
                Creating a Character Definition Table

                    Software Character Generators
                 Video BIOS Support � Pixel Handling

              Designing a Software Character Generator
                        Horizontal Alignment
                 Variable Character Sizes � Clipping
                        Character Orientation
                   Cooperating with the Video BIOS
                     More Power, More Complexity

             Implementing a Software Character Generator
                      CGA � HGC and HCG+ � MCGA
                     EGA and VGA � InColor Card



     Few programs are complete without some sort of text display. Most
     graphics applications incorporate text with graphics images. In
     graphics modes, the software that draws characters requires the same
     thoughtful design and construction as do routines that draw geometric
     figures such as lines and ellipses.

     In alphanumeric video modes, of course, displaying text is easy. You
     simply place a character code and attribute in the video buffer and
     let the hardware character generator put pixels on the screen. In
     graphics modes, however, your program must store every pixel of every
     character in the video buffer.

     This chapter discusses how to translate character codes into the pixel
     patterns that form characters in graphics modes. The programming
     examples are hardware-specific, of course, but you can adapt the
     table-driven character generator described here for use with other
     computers and in other graphics applications.


Character Definition Tables


     Every character that an IBM video subsystem displays is made up of a
     pattern of contiguous pixels. The pixels are arranged to appear as
     coherent, recognizable characters on the screen. The pixel pattern
     that represents a character is the same no matter where in the buffer
     or on the screen the character is located.

     The most convenient way to describe the pixel patterns that represent
     the characters in a character set is to create a table in which bit
     patterns represent the pixel patterns. Such a character definition
     table contains a bit pattern for every displayable character (see
     Figure 9-1). Each character's bit pattern is defined within a
     rectangular matrix. When the character matrix is the same size for all
     characters in the table, and the definitions in the table are
     organized by character code, converting a character code to an offset
     into the table is easy.

     You can use a character definition table formatted in this way in
     alphanumeric as well as graphics modes in video subsystems that
     support RAM-based alphanumeric character definitions. Chapter 10
     covers this topic in detail.


                                           Hex    Binary  ���������������Ŀ
                                       ����7E    01111110 � �۳۳۳۳۳۳ �
                                       �                  ���������������Ĵ
                                       �   81    10000001 �۳ � � � � � �۳
FOOO:FA6E  00 00 00 00 00 00 00 00     �                  ���������������Ĵ
          ������������������������������   A5    10100101 �۳ �۳ � �۳ �۳
FOOO:FA76 �7E 81 A5 81 BD 99 81 7E                        ���������������Ĵ
          ����������������������������Ŀ   81    10000001 �۳ � � � � � �۳
FOOO:FA7E  7E FF DB FF C3 E7 FF 7E     �                  ���������������Ĵ
FOOO:FA86  6C FE FE FE 7C 38 10 00     �   B0    10111101 �۳ �۳۳۳۳ �۳
FOOO:FA8E  10 38 7C FE 7C 38 10 00     �                  ���������������Ĵ
FOOO:FA96  38 7C 38 FE FE 7C 38 7C     �   99    10011001 �۳ � �۳۳ � �۳
FOOO:FA9E  10 10 38 7C FE 7C 38 7C     �                  ���������������Ĵ
FOOO:FAA6  00 00 18 3C 3C 18 00 00     �   81    10000001 �۳ � � � � � �۳
FOOO:FAAE  FF FF E7 C3 C3 E7 FF FF     �                  ���������������Ĵ
                    .                  ����7E    01111110 � �۳۳۳۳۳۳ �
                    .                                     ����������
                    .

     Figure 9-1.  The beginning of the bit patterns that define IBM's ROM
     BIOS 8-by-8 character definitions.


Video BIOS Support

     The PC and PS/2 ROM BIOS contains default character definition tables
     for use in graphics modes. The size of the characters in the table
     depends on the vertical resolution of the video mode. In 200-line,
     CGA-compatible video modes, the default character matrix is 8 pixels
     wide and 8 pixels high; in 350-line graphics modes, it is 8 wide by 14
     high; in 400-line and 480-line modes, it is 8 by 16. In all graphics
     modes, the default characters are 8 pixels wide simply because there
     are 8 bits in a byte. Because each byte in a character definition
     table represents 8 horizontal pixels, defining characters as a
     multiple of 8 pixels in width makes the table easy to manipulate in
     software.

     No equivalent constraint applies to the height of characters defined
     in a character definition table. In practice, however, the character
     matrix used with IBM video subsystems should rarely be smaller than 8
     by 6 pixels or larger than 8 by 16 pixels. With a character matrix
     outside this range, the displayed height and width of the characters
     become disproportionate and the characters tend to appear too short or
     too elongated to be easily read.


     Default CGA Characters
     Figure 9-1 shows the beginning of the character definition table for
     the default character set in CGA graphics modes. The table contains an
     8-byte definition for each of the first 128 ASCII characters (0
     through 7FH). The first eight bytes of the table correspond to
     character code 0, the second eight bytes to character code 1, and so
     forth. The bit pattern in each group of eight bytes represents the
     pixel pattern displayed for the corresponding row of pixels in the
     character. The first of the eight bytes in each group corresponds to
     the topmost row of eight pixels.

     This table of 8-by-8 character definitions is located at F000:FA6E in
     the motherboard ROM on all PCs and PS/2s. However, the table defines
     only the first 128 ASCII characters. Character definitions for the
     second group of 128 ASCII codes (80H through 0FFH) are found in a
     table whose address is stored in interrupt vector 1FH (0000:007C).
     Because the motherboard BIOS contains no definitions for these
     characters, the address is initialized to 0000:0000. If you use the
     ROM BIOS to display ASCII characters between 80H and 0FFH in CGA
     graphics modes without pointing this interrupt vector to a character
     definition table, the "characters" you see on the screen are whatever
     binary patterns happen to lie in the first 1024 bytes of RAM.

       ���ͻ     The MS-DOS utility GRAFTABL leaves a table of definitions
       � T �     for characters 80H through 0FFH resident in RAM and
       � I �     updates the interrupt 1FH vector to point to it. The
       � P �     characters defined in GRAFTABL are the same as those the
       ���ͼ     alphanumeric character generator displays for ASCII codes
                 80H through 0FFH.


     Default EGA, VGA, and MCGA Characters
     The ROM BIOS in the EGA, VGA, and MCGA subsystems contains definitions
     for all 256 ASCII codes for all graphics modes. (You can access these
     tables directly; their addresses may be obtained by calling INT 10H
     function 11H with AL = 30H.) When you select a graphics mode with INT
     10H function 0, the video BIOS loads the address of the appropriate
     character definition table for the graphics mode into interrupt vector
     43H (0000:010C). In CGA-compatible 200-line graphics modes, the BIOS
     also points the interrupt 1FH vector to the definitions for characters
     80H through  0FFH.


Creating a Character Definition Table

     The easiest way to obtain a character definition table is to use one
     of the default BIOS tables. If the staid, placid characters in those
     tables aren't to your liking, you can find many others commercially
     available or in the public domain.

       ���ͻ     Several standard character sets are defined and registered
       � T �     with the International Standards Organization (ISO). IBM
       � I �     refers to these character sets as code pages and has
       � P �     assigned arbitrary identification numbers to them. For
       ���ͼ     example, the standard IBM PC ASCII character set is
                 designated by code page 437; the Canadian French code page
                 is 863; and code page 850 is the general-purpose
                 "multilingual" character set devised by IBM for languages
                 that use a Latin alphabet.

                 Both MS-DOS (starting in version 3.3) and OS/2 allow
                 applications to switch between code pages on an EGA or
                 VGA. When a program displays characters with operating
                 system function calls, the operating system uses the
                 character definitions in the currently selected code page.
                 Applications that use foreign language character sets
                 should, whenever possible, exploit the code pages
                 supported by the operating system.


     When you define your own character set, you can select among several
     alternative methods. The ugly alternative is to build your character
     definition table by specifying every byte in source code. Figure 9-2
     shows the beginning of such a table. A more elegant alternative is to
     use a character-set editing program. With such editors, you use
     cursor-control keys or a pointing device such as a light pen or mouse
     to specify the bit patterns in the table. Character-set editors are
     also available both commercially and in the public domain. (You can
     even write your own, using the routines in this book.)

     Another approach is to start with one of the BIOS character sets and
     transform the bit patterns in a regular way. For example, you could
     reverse the bit patterns in a table by converting 0s to 1s and 1s to
     0s (that is, apply a bitwise logical NOT to each byte in the table),
     thus creating a "reverse" character set.


     CharDefs    db   000h,000h,000h,000h,000h,000h,000h,000h ; character 0
                 db   03Ch,066h,0C0h,0C0h,0C0h,066h,03Ch,000h ; character 1
                 db   0FCh,066h,066h,07Ch,06Ch,066h,0E6h,000h ; character 2
                 db   0FEh,062h,068h,078h,068h,062h,0FEh,000h ; character 3
                 db   078h,0CCh,0CCh,078h,0CCh,0CCh,078h,000h ; character 4
                 db   078h,030h,030h,030h,030h,030h,078h,000h ; character 5
                 db   0CCh,0CCh,0CCh,0CCh,0CCh,078h,030h,000h ; character 6
                 db   0FEh,062h,068h,078h,068h,062h,0FEh,000h ; character 7
                                      .
                                      .
                                      .

     Figure 9-2.  A hand-coded character definition table.


Software Character Generators


     A software routine that uses the bit patterns in a character
     definition table to draw characters in the video buffer is called a
     software character generator. A software character generator performs
     several functions. It locates the bit pattern for a given character
     code, translates the bit pattern into a corresponding pattern of
     pixels, and updates pixels at a specified location in the video
     buffer.


Video BIOS Support

     The video BIOS provides a software character generator that is used
     whenever INT 10H functions 09H, 0AH, 0EH, and 13H are called in
     graphics modes. The software character generator in the IBM PC and AT
     uses only the 8-by-8 characters defined at F000:FA6E and at the
     address indicated by interrupt vector 1FH. The version in the EGA and
     PS/2 BIOS uses the table to which interrupt vector 43H points; this
     version determines the height of displayed characters from the BIOS
     variable POINTS at 0040:0085.

     You can use the BIOS software character generator to display
     characters from any character definition table by updating the
     appropriate interrupt vectors with the address of the table. On the
     EGA and PS/2s, use INT 10H function 11H to do this.

     The BIOS character generator is convenient to use, but it is somewhat
     limited in its capabilities. In particular, it can only store byte-
     aligned characters in the video buffer. If you are willing to
     sacrifice compatibility with the INT 10H interface, you can write a
     faster software character generator that is more powerful than the
     default video BIOS version.


Pixel Handling

     You store characters in the video buffer by changing the values of the
     appropriate pixel groups. You can update the video buffer simply by
     replacing old pixel values with new ones. You can also perform bitwise
     logical operations (AND, OR, or XOR) to update the pixels.

     Your routine to display text in graphics modes can handle the
     background pixels in the character matrix in one of two ways. One is
     to preserve the contents of the video buffer as much as possible by
     updating only foreground pixels; that is, by updating only those
     pixels that represent the character itself (see Figure 9-3a). The
     other is to update all foreground and background pixels within the
     bounds of the rectangular character matrix (see Figure 9-3b).


             ������������������������������������������ͻ
             �                                          �
             �    Figure 9-3 is found on page 272       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 9-3.  Characters written without background pixels (a.) and
     with background pixels (b.).


     Updating only the character's foreground pixels preserves as many
     pixels in the video buffer as possible. This may be the best way to
     display text in front of a detailed or patterned graphics image.
     However, reading the displayed characters can be difficult if the
     graphics image in some way blends with the character. For example,
     text is invisible against a region filled with pixels having the same
     value as the character's foreground pixels.

     To avoid such problems, you can update all foreground and background
     pixels in the character matrix each time you store a character in the
     buffer. This avoids a background pattern inadvertently masking the
     characters. The trade-off is that each time you store a character in
     the buffer you must replace the previous contents of the buffer with a
     rectangular blot.

     The source code for the two types of graphics text routines is
     similar. The examples in this chapter demonstrate the second type,
     which makes them more complicated than routines that draw only
     foreground pixels. You can convert the routines to draw only the
     foreground pixels by eliminating the code for incorporating the
     background pixels.


Designing a Software Character Generator


     Software character generators for IBM PC video subsystems have a
     number of design considerations in common. Because the performance of
     your character generator strongly influences the overall performance
     of many graphics applications, always consider the trade-offs between
     function and simplicity in your character generator routines.


Horizontal Alignment

     In graphics modes, the left edge of a character is not necessarily
     byte-aligned. When a character is written so that its leftmost pixels
     fall somewhere in the middle of a byte in the video buffer (see Figure
     9-4a), the character generator must shift and mask the character
     matrix so that only pixels that are part of the character are updated.

     Usually, however, characters are written into the video buffer at
     byte-aligned pixel addresses (see Figure 9-4b). This is the case, for
     example, whenever the display is used in a "teletype mode"; that is,
     when each line of characters starts at the left edge of the display.
     Generating byte-aligned characters requires no rotation or masking of
     pixels, so using a separate routine for byte-aligned characters
     improves the character generator's performance.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 9-4 is found on page 273       �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 9-4.  Alignment of characters in the video buffer. In Figure
     9-4a, characters are not aligned; in Figure 9-4b, characters are byte-
     aligned.


Variable Character Sizes

     Writing a character generator that accommodates characters of
     different heights is relatively easy. The height of a character
     corresponds to the number of bytes in its definition in the character
     definition table. You can thus use the height of your characters as a
     loop limit inside the character generator routine without
     significantly affecting the complexity of the routine.

     Handling characters of different widths is more difficult. If the
     width of a character does not fit exactly into an integer number of
     bytes, you must mask each row of pixels in the character as you store
     it in the video buffer. Again, the extra overhead of forming the
     appropriate bit mask and masking pixels in the video buffer
     complicates and slows the character generator routine.


Clipping

     You can clip characters in several ways. The simplest is to clip the
     entire character before you store it in the video buffer; if any
     portion of the character matrix would lie outside the clipping area,
     don't write the character.

     Clipping a character so that only a portion of it is stored in the
     video buffer is more difficult. One way to do this is to modify the
     character generator so that any clipped portion of a character is not
     written to the buffer. Another approach is to write the entire
     character into an auxiliary buffer and then copy the clipped character
     into the video buffer with a pixel block copy routine (see Chapter
     11).


Character Orientation

     Usually, characters are displayed so that they can be read from left
     to right and from the top down. To change this orientation, apply the
     appropriate transformation to the bit patterns in the character
     definition table. For example, the subroutine in Listing 9-1 rotates
     the 8-byte bit pattern that represents an 8-by-8 character so that the
     displayed characters read upward. With this transformation, you can
     use the same character generator to display vertically or horizontally
     oriented characters. Only the bit patterns differ.


���������������������������������������������������������������������������

     Listing 9-1.  A routine that rotates an 8-by-8 character
     definition by 90 degrees.

���������������������������������������������������������������������������


Cooperating with the Video BIOS

     Even if your character definition tables and character generator
     software avoid using video BIOS functions, you should nevertheless try
     to preserve compatibility by cooperating with the BIOS routines when
     possible. In 200-line graphics modes, you should update the address in
     interrupt vector 1FH whenever you use an 8-by-8 character definition
     table that includes the second 128 ASCII characters. On the EGA, VGA,
     and MCGA, you should generally use INT 10H function 11H to keep the
     BIOS interrupt vectors and Video Display Data Area variables up to
     date.


More Power, More Complexity

     You can add functionality to a software character generator in several
     ways. You might, for example, write a character generator that refers
     to a table of relative character widths to display proportionally
     spaced characters. As your routine reads bit patterns from the
     character definition table, you might have it shift them to the right
     by a predetermined number of pixels to generate bold or italic
     character sets. You might apply a pattern of pixel values to the
     foreground pixels you update. You might allow a character definition
     table to extend beyond the usual range of 256 characters; the more
     characters you define, the wider range of characters you can display
     at one time. Any of these possibilities adds power and flexibility to
     your software character generator, but all of them complicate your
     source code and ultimately slow it down.


Implementing a Software Character Generator


     All software character generator examples in this chapter require that
     you specify the x- and y-coordinates of the pixel in the upper left
     corner of the displayed character matrix. Each routine detects the
     special case where the character matrix is byte-aligned in the video
     buffer, but the routines do not validate pixel coordinates or perform
     any clipping. All the routines except DisplayChar10() update pixels in
     the video buffer by replacing their values. To perform a bitwise AND,
     OR, or XOR operation, you must modify the routines (see Chapter 5).


CGA

     In 640-by-200 2-color mode on the CGA, the software character
     generator applies the bit patterns in the character definition table
     directly to the pixels in the video buffer (see Listing 9-2). When
     the character is byte-aligned in the video buffer, the routine copies
     pixel values directly from the character definition table. Otherwise,
     for each row of eight pixels in the character, a rotated 16-bit mask
     is used to zero the proper eight pixels in the buffer. Then the pixels
     from the character definition table are rotated into position and
     stored in the buffer using a bitwise OR operation.


���������������������������������������������������������������������������

     Listing 9-2.  A software character generator for 640-by-200 2-color
     mode.

���������������������������������������������������������������������������


     The routine for 320-by-200 4-color mode in Listing 9-3 is more
     complicated because each bit in the character definition must be
     expanded into the appropriate 2-bit pixel value. A 0 bit in the
     character definition table becomes a 2-bit background pixel value; a
     1 bit in the table is expanded into a 2-bit foreground pixel value.
     Thus, each byte in the table is transformed into a word of pixels.


���������������������������������������������������������������������������

     Listing 9-3.  A software character generator for 640-by-200
     4-color mode.

���������������������������������������������������������������������������


     In Listing 9-3, when the character is byte-aligned in the video
     buffer, the routine moves the 16-bit word of pixels directly into the
     buffer. A character that is not byte-aligned spans three bytes in the
     buffer. In this case, the routine must rotate the eight pixels in each
     row of the character into position. Then the first two bytes of the
     character in the buffer are masked and updated, followed by the third
     (rightmost) byte of the character.


HGC and HGC+

     A routine for the 720-by-348 monochrome graphics mode on the HGC and
     the HGC+ can use the same bit-masking technique that the CGA 640-by-
     200 2-color routine uses. You could convert DisplayChar06() into a
     Hercules-compatible routine by revising the call to PixelAddr06() and
     by changing video buffer addressing to accommodate the different
     buffer interleaves on the two adapters.

     It is worthwhile, however, to exploit the HGC's 720-pixel horizontal
     resolution by displaying characters in a matrix that is 9 pixels wide,
     so that each row on the screen contains 80 evenly spaced characters.
     The routine in Listing 9-4 does this by appending a ninth bit to each
     8-bit pattern it reads from the character definition table. The
     ninth bit is 0 except for box-drawing characters (ASCII 0C0-0DFH).
     For these characters, the ninth bit is a copy of the rightmost
     bit in the bit pattern. (This mimics the function of the hardware
     character generator in alphanumeric modes. See Chapter 10.)


���������������������������������������������������������������������������

     Listing 9-4.  A software character generator for Hercules monochrome
     graphics mode.

���������������������������������������������������������������������������


       ���ͻ     Note how the CGA and Hercules routines use interrupt
       � T �     vector 43H to point to the start of the current character
       � I �     definition table. This is the interrupt vector the EGA and
       � P �     VGA ROM BIOS uses for this purpose. Also, the routines
       ���ͼ     determine the size of the displayed character matrix by
                 inspecting the variables POINTS (0040:0085) and CRT_COLS
                 (0040:004A) in the BIOS Video Display Data Area. If you
                 are not using an EGA, MCGA, or VGA, the BIOS won't keep
                 the interrupt vector and POINTS up to date; in this case,
                 your program should either update these values explicitly
                 or maintain equivalent values elsewhere.


MCGA

     In 640-by-480 2-color mode on the MCGA, pixels are stored eight to a
     byte, so you can adapt the 640-by-200 2-color character generator for
     use in this mode by modifying its video buffer addressing. A character
     generator for 320-by-200 256-color mode is a little different, because
     each bit in the character definition table expands into a byte in the
     video buffer (see Listing 9-5).


���������������������������������������������������������������������������

     Listing 9-5.  A character generator for MCGA and VGA 320-by-200 256-
     color mode.

���������������������������������������������������������������������������


EGA and VGA

     The routine for the EGA and VGA in Listing 9-6 uses the Graphics
     Controller to update pixels in the video buffer. The routine is
     similar in some ways to the routine for the CGA's 640-by-200 2-color
     mode, because each byte of the video buffer represents eight pixels.
     Of course, the code is complicated by the need to program the Graphics
     Controller to handle the foreground and background pixel values.

     The routine writes each row of pixels in the character by latching the
     bit planes, updating the foreground pixels, updating the background
     pixels, and then writing the latches back to the bit planes. The
     Graphics Controller cannot conveniently update both foreground and
     background pixels at the same time, so the routine must perform these
     operations separately.


���������������������������������������������������������������������������

     Listing 9-6.  A software character generator for native EGA and VGA
     graphics modes.

���������������������������������������������������������������������������


InColor Card

     The technique for storing characters in the video buffer on the
     Hercules InColor Card, shown in Listing 9-7, is different from that
     on the EGA or VGA because you can use the InColor Card's Read/Write
     Color register (1AH) and write mode 0 to update both foreground and
     background pixel values in one operation. Thus, the actual process of
     updating the bit planes collapses into relatively few machine
     instructions.

     However, the InColor Card cannot perform pixel AND, OR, or XOR
     operations in hardware. To do this, you must write additional
     subroutines that use the Plane Mask register to map logical operations
     onto the bit planes (see Chapter 5).


���������������������������������������������������������������������������

     Listing 9-7.  A software character generator for Hercules InColor
     graphics modes.

���������������������������������������������������������������������������



                   10  Alphanumeric Character Sets


                     Character Definition Tables
              Alphanumeric Character Definitions in ROM
              Alphanumeric Character Definitions in RAM

                  Updating Character Generator RAM
              EGA and VGA � HGC+ � InColor Card � MCGA

                   Using RAM-based Character Sets
                        ASCII Character Sets
                       Extended Character Sets
        Compatibility Problems with Extended Character Codes

               Changing the Displayed Character Matrix
              EGA � VGA � MCGA � HGC+ and InColor Card

               Graphics Windows in Alphanumeric Modes
             HGC+ and InColor Card � EGA and VGA � MCGA



     One of the easiest ways to speed up a program's video interface is to
     use an alphanumeric video mode. To gain this speed advantage, however,
     you must accept the limitations of the video subsystem's alphanumeric
     character generator.

     On the original MDA and CGA, the only characters you could display in
     alphanumeric mode were those defined in a table located in ROM on the
     adapter. The hardware character generator on these adapters was not
     designed to use a character definition table located in RAM. However,
     the EGA, the MCGA, the VGA, the HGC+, and the InColor Card can all
     display alphanumeric characters defined in RAM.

     This chapter shows you how to exploit RAM-based alphanumeric character
     sets on these subsystems. It describes how to format character
     definition tables and where to place them in RAM to be used in
     alphanumeric modes. It discusses the pros and cons of using extended
     character sets that contain more than the usual 256 ASCII characters.
     The chapter concludes with techniques for displaying true graphics
     images in an alphanumeric video mode.


Character Definition Tables


     Like the software graphics character generators described in Chapter
     9, the hardware alphanumeric character generator in all IBM video
     subsystems references a memory-resident character definition table
     that contains bit-pattern representations of the pixels in each
     displayable character. Unlike the graphics-mode tables, whose location
     in memory may vary, the alphanumeric tables must lie in a
     predesignated portion of memory to allow the alphanumeric character
     generator to access them.


Alphanumeric Character Definitions in ROM

     The MDA, the CGA, and the Hercules adapters have an alphanumeric
     character definition table located in ROM that is not within the CPU's
     address space. Only the character generator hardware can access it.
     The character set that these adapters display in alphanumeric modes is
     therefore not controlled by software.

     On the EGA, the MCGA, and the VGA, the alphanumeric character
     generator uses a table of bit patterns stored in RAM rather than in
     dedicated ROM. The video ROM BIOS contains tables with which it
     initializes character generator RAM whenever it establishes an
     alphanumeric video mode. Because these video subsystems can set up
     alphanumeric modes with different vertical resolutions, the sizes of
     the default alphanumeric characters vary (see Figure 10-1).


     200-Line Modes
     The CGA's 200-line alphanumeric modes use an 8-by-8 character matrix.
     In  80-by-25 alphanumeric mode, the screen is thus 640 pixels wide; in
     40-by-25 alphanumeric mode, the screen is 320 pixels wide. Although
     the CGA uses the same character set and font in its alphanumeric and
     graphics modes, the character definitions for alphanumeric modes
     reside in dedicated ROM, accessible only to the hardware character
     generator. (As described in Chapter 9, the graphics-mode definitions
     are found in the ROM BIOS and in a table in RAM addressed by the
     vector for interrupt 1FH.)


����������������������������������������������������������������������������ķ
                                    Character Matrix
Adapter      Video Mode             (width by height in pixels)
��������������������������������������������������������������������������
MDA, HGC     Monochrome             9-by-14
CGA          40-by-25 16-color      8-by-8
             80-by-25 16-color      8-by-8
EGA          80-by-25 16-color      8-by-8  (200-line resolution)
                                    8-by-14 (350-line resolution)
             80-by-25 monochrome    9-by-14
MCGA         40-by-25 16-color      8-by-16
             80-by-25 16-color      8-by-16
VGA          40-by-25 16-color      8-by-8  (200-line resolution)
                                    8-by-14 (350-line resolution)
                                    9-by-16 (400-line resolution)
             80-by-25 16-color      8-by-8  (200-line resolution)
                                    8-by-14 (350-line resolution)
                                    Character Matrix
Adapter      Video Mode             (width by height in pixels)
                                    8-by-14 (350-line resolution)
                                    9-by-16 (400-line resolution)
             80-by-25 monochrome    9-by-14 (350-line resolution)
                                    9-by-16 (400-line resolution)
HGC+         80-by-25 monochrome    9-by-14
InColor Card 80-by-25 16-color      9-by-14

     Figure 10-1.  The default alphanumeric character matrix in various
     video modes.


       ���ͻ     The CGA comes with two tables of 8-by-8 characters in the
       � T �     alphanumeric character generator's ROM. A jumper on the
       � I �     adapter selects which table the alphanumeric character
       � P �     generator uses. By default, jumper P3 on the CGA is not
       ���ͼ     connected, and the usual "double-dot" 8-by-8 characters
                 are displayed. If you connect jumper P3, the CGA's
                 alphanumeric character generator uses a "single-dot" font
                 (see Figure 10-2). The "single-dot" characters appear
                 sharper on some monitors because their vertical strokes
                 are only one pixel wide.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 10-2 is found on page 299      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 10-2.  Double-dot and single-dot alphanumeric character sets on
     the CGA.


     350-Line Modes
     In 350-line alphanumeric modes on the MDA and the Hercules adapters,
     the characters are defined in an 8-by-14 matrix. Again, the character
     definition table resides in ROM outside the CPU address space that is
     dedicated to the hardware character generator. Because the horizontal
     resolution is 720 pixels on these adapters, each 8-by-14 character
     actually is displayed in a matrix 9 pixels wide. Thus, each row on the
     screen contains 720/9, or 80, characters.

     If characters are defined in ROM in an 8-by-14 matrix but displayed in
     a 9-by-14 matrix, where does the extra pixel come from? The hardware
     character generator in the MDA, the Hercules cards, the EGA, and the
     VGA (in monochrome mode) adds an extra pixel to the right of each row
     of eight pixels in each character. For the block graphics characters
     (ASCII 0C0H through 0DFH), the value of the rightmost pixel is
     replicated in each row. For all remaining character codes, the extra
     pixel is displayed with the character's background attribute.

     Since the ninth (rightmost) pixel in block graphics characters is a
     copy of the eighth, these characters abut and can be used to draw
     horizontal lines. All other displayable characters are separated from
     each other by that ninth pixel. The resulting display appears less
     crowded than it would be without the extra space.

       ���ͻ     With the EGA and the VGA, you can control whether or not
       � T �     the alphanumeric character generator replicates the eighth
       � I �     pixel of block graphics characters. When bit 2 of the
       � P �     Attribute Controller's Mode Control register (10H) is set
       ���ͼ     to 1, the ninth pixel is the same as the eighth. When bit
                 2 is set to 0, the ninth pixel is a background pixel.


     400-Line Modes
     The default alphanumeric modes of both the MCGA and the VGA have 400-
     line vertical resolution. The characters used in these modes are
     defined in an 8-by-16 matrix. On the VGA, the 8-by-16 characters are
     displayed in a 9-by-16 matrix, just as on an MDA or an EGA with a
     monochrome display.


Alphanumeric Character Definitions in RAM

     The EGA, the VGA, the MCGA, the HGC+, and the InColor Card all have
     alphanumeric character generators that use character definition tables
     located in predesignated areas of RAM. In all these subsystems, this
     RAM lies within the address space of the video buffer. If you know how
     character generator RAM is mapped, you can write programs that read or
     update the alphanumeric character definition tables and thereby change
     the displayed alphanumeric character set.


     EGA and VGA
     In alphanumeric modes on the EGA and the VGA, the video buffer is
     organized as four parallel memory maps, just as in graphics modes. In
     alphanumeric modes, however, only maps 0 and 1 contain displayable
     data (see Figure 10-3). Even-numbered bytes (character codes) in the
     CPU's address space are located in map 0, and odd-numbered bytes
     (attribute bytes) are located in map 1. This mapping is invisible to
     the CPU; the CRTC internally translates odd addresses to offsets into
     map 1 and even addresses into references to map 0.


                           �����������������������Ŀ
                           �                       �
                           �                       �
                       ����������������������Ŀ   �
                       � Character definitions �   �
                       �                       �   �
                   ����������������������Ŀ   �   �
                   �    Attribute bytes    �   �   �
                   �                       �   �   �
               ����������������������Ŀ   �   �   �
               �    Character codes    �   �   �   �
               �                       �   �   ����� Map 3
               �                       �   �   �
               �                       �   �   �
               �                       �   ����� Map 2
               �                       �   �
               �                       �   �
               �                       ����� Map 1
     B800:0000 �                       �
        or     �                       �
     B000:0000 ������������������������� Map 0

     Figure 10-3.  Video RAM layout in EGA and VGA alphanumeric modes.


     The alphanumeric character generator uses a set of 256-character
     tables stored in map 2. The EGA supports four such tables (see Figure
     10-4); the VGA supports eight (see Figure 10-5). Each table consists
     of 256 32-byte bit patterns, so the maximum height of the character
     matrix is 32 scan lines. When the displayed character matrix contains
     fewer than 32 lines, the character generator ignores the extra bytes
     in each character definition.

     On the EGA, each of the four alphanumeric character definition tables
     starts at a 16 KB boundary. Since only 8 KB (256 characters * 32 bytes
     per character) are used, 8 KB of unused RAM follows each table. On the
     VGA, these unused areas in map 2 can contain additional character
     definitions. Of course, in writing an application that must run on
     both the EGA and the VGA, you should avoid using these extra tables
     because the EGA does not support them.

       ���ͻ     On the IBM EGA, which may be equipped with less than 256
       � T �     KB of video RAM, the number of character definition tables
       � I �     you can load into video RAM depends on the amount of RAM
       � P �     installed on the card. For example, without IBM's Graphics
       ���ͼ     Memory Expansion Card, an IBM EGA has only 64 KB of video
                 RAM, so each video memory map in alphanumeric modes
                 contains only 16 KB, and only one character definition
                 table will fit in map 2.


                   �����������������������������Ŀ
                   �          (Unused)           �
                   �����������������������������Ĵ
                   �  256 character definitions  �
             C000H �����������������������������Ĵ
                   �          (Unused)           �
                   �����������������������������Ĵ
                   �  256 character definitions  �
     Offset  8000H �����������������������������Ĵ
                   �          (Unused)           �
                   �����������������������������Ĵ
                   �  256 character definitions  �
             4000H �����������������������������Ĵ
                   �          (Unused)           �
                   �����������������������������Ĵ
                   �  256 character definitions  �
             0000H �������������������������������

     Figure 10-4.  Character generator RAM in EGA video memory map 2.


                   �����������������������������Ŀ
                   �  256 character definitions  �
             E000H �����������������������������Ĵ
                   �  256 character definitions  �
             C000H �����������������������������Ĵ
                   �  256 character definitions  �
             A000H �����������������������������Ĵ
                   �  256 character definitions  �
     Offset  8000H �����������������������������Ĵ
                   �  256 character definitions  �
             6000H �����������������������������Ĵ
                   �  256 character definitions  �
             4000H �����������������������������Ĵ
                   �  256 character definitions  �
             2000H �����������������������������Ĵ
                   �  256 character definitions  �
             0000H �������������������������������

     Figure 10-5.  Character generator RAM in VGA video memory map 2.


     HGC+
     Character generator RAM on the HGC+ starts at B000:4000 and extends to
     the end of available video RAM at B000:FFFF (see Figure 10-6). You can
     fill this entire 48 KB area with character definitions. Each character
     definition is 16 bytes long, so a table that defines 256 characters
     occupies 4 KB. Thus, this RAM can hold 3072 character definitions.


     B000:FFFF ��������������������������������Ŀ
               �                                �
               �      Character definitions     �
               �                                �
               �                                �
     B000:4000 ��������������������������������Ĵ
               � Character codes and attributes �
     B000:0000 ����������������������������������

     Figure 10-6.  Video RAM layout in alphanumeric modes on the HGC+.


       ���ͻ     If the HGC+ is configured so that video RAM above
       � T �     B000:8000 is masked out of the CPU address space (that is,
       � I �     bit 1 of the Configuration Switch at 3BFH is set to 0),
       � P �     then only the 16 KB of RAM between B000:4000 and B000:7FFF
       ���ͼ     can be used for character definitions.


     InColor Card
     Character generator RAM occupies the same range of addresses on the
     InColor Card as on the HGC+, that is, B000:4000 through B000:FFFF.
     Also, each InColor character definition is 16 bytes long. Unlike the
     HGC+, however, the 16-color InColor Card uses all four bit planes in
     this range of addresses for character definitions (see Figure 10-7).

     Because of this, you can control the value of each pixel in each
     character you define. You can also program the InColor Card so that
     different bit planes define different characters; when the characters
     are displayed, their attribute bytes select which bit plane is used.
     By loading each of the four bit planes with different character
     definitions, you can maintain as many as 12,288 (3072 x 4) character
     definitions in RAM. Or, to preserve compatibility with the HGC+, you
     can load all four bit planes with the same bit patterns.

       ���ͻ     In using both the EGA and the Hercules cards, be careful
       � T �     in changing from an alphanumeric mode that uses a RAM-
       � I �     based character definition table to a graphics mode. The
       � P �     same RAM that contains pixel data in graphics modes is
       ���ͼ     used to store character definitions in alphanumeric modes.
                 You can corrupt or erase your character definition tables
                 by updating the video buffer in a graphics mode and then
                 returning to an alphanumeric mode.


     MCGA
     Unlike the EGA and VGA, the MCGA has no parallel memory maps in which
     to store character definitions. Instead, alphanumeric character
     definitions are maintained in the 32 KB of video RAM between A000:0000
     and A000:7FFF. You can store as many as four 8 KB character definition
     tables at A000:0000, A000:2000, A000:4000, and A000:6000 (see Figure
     10-8).


                           �����������������������Ŀ
                           �                       �
                           � Character definitions �
                       ����������������������Ŀ   �
                       �                       �   �
                       � Character definitions �   �
                   ����������������������Ŀ   ���Ĵ
                   �                       �   �   �
                   � Character definitions �   �   �
               ����������������������Ŀ   ���Ĵ   �
               �                       �   �   ����� Bit plane 3
               � Character definitions �   �   �
               �                       ���Ĵ   �
               �                       �   ����� Bit plane 2
               �                       �   �
     B800:4000 �����������������������ij   �
               �                       ����� Bit plane 1
               �    Character codes    �
               �    and attributes     �
     B000:0000 ������������������������� Bit plane 0

     Figure 10-7.  Video RAM layout in alphanumeric modes on the Hercules
     InColor Card. Character definitions start at B000:4000 in all four bit
     planes.


                ��������������������������������Ŀ
                �                                �
                � Character codes and attributes �
                �                                �
       A000:8000�                                �
     (B800:0000)��������������������������������Ĵ
                �    256 character definitions   � 30H
       A000:6000��������������������������������Ĵ
                �    256 character definitions   � 20H
       A000:4000��������������������������������Ĵ
                �    256 character definitions   � 10H
       A000:2000��������������������������������Ĵ
                �    256 character definitions   � 00H
       A000:0000���������������������������������� ���
                                                    �
                           Value in Character Font Pointer register

     Figure 10-8.  Layout of video RAM in MCGA alphanumeric modes.


     The format of the MCGA's character definition tables is very different
     from that of any other tables discussed thus far. Each 8 KB table is
     divided into 16 512-byte lists of character codes and bit patterns
     (see Figure 10-9). Each list corresponds to one scan line of the
     characters being defined; the first list represents the bit patterns
     in the topmost scan line of each character, the second list
     corresponds to the second scan line, and so on (see Figure 10-10).
     Since there are 16 lists, the maximum height of a character is 16
     lines.


           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 0123456789ABCDEF
A000:0400 00 00 01 7E 02 7E 03 00 04 00 05 00 06 00 07 00 ...~.~..........
A000:0410 08 FF 09 00 0A FF 0B IE 0C 3C 0D 3F 0E 7F 0F 00 .........<.?....
A000:0420 10 C0 11 06 12 18 13 66 14 7F 15 C6 16 00 17 18 .......f........
A000:0430 18 18 19 18 1A 00 1B 00 1C 00 1D 00 1E 00 1F 00 ................
A000:0440 20 00 21 18 22 66 23 00 24 7C 25 00 26 38 27 30  .!."F#.$|%.&8'0
A000:0450 28 0C 29 30 2A 00 2B 00 2C 00 2D 00 2E 00 2F 00 (.)0*.+.,.-.../.
A000:0460 30 7C 31 18 32 7C 33 7C 34 0C 35 FE 36 38 37 FE 0|1.2|3|4.5.687.
A000:0470 38 7C 39 7C 3A 00 3B 00 3C 00 3D 00 3E 00 3F 7C 8|9|:.;.<.=.>.?|
A000:0480 40 00 41 10 42 FC 43 3C 44 F8 45 FE 46 FE 47 3C @.A.B.C<D.E.F.G<
A000:0490 48 C6 49 3C 4A 1E 4B E6 4C F0 4D C6 4E C6 4F 38 H.I<J.K.L.M.N.O8
A000:04A0 50 FC 51 7C 52 FC 53 7C 54 7E 55 C6 56 C6 57 C6 P.Q|R.S|T~U.V.W.
A000:04B0 58 C6 59 66 5A FE 5B 3C 5C 00 5D 3C 5E 6C 5F 00 X.YfZ.{<\.}<^1_.
A000:04C0 60 18 61 00 62 E0 63 00 64 1C 65 00 66 38 67 00 `.a.b.c.d.e.f8g.
A000:04D0 68 E0 69 18 6A 06 6B E0 6C 38 6D 00 6E 00 6F 00 h.i.j.k.l9m.n.o.
A000:04E0 70 00 71 00 72 00 73 00 74 10 75 00 76 00 77 00 p.q.r.s.t.u.v.w.
A000:04F0 78 00 79 00 7A 00 7B 0E 7C 18 7D 70 7E 76 7F 00 x.y.z.{.|.}p~v..
A000:0500 80 3C 81 CC 82 18 83 38 84 CC 85 30 86 6C 87 00 .<.....8...0.1..
A000:0510 88 38 89 CC 8A 30 8B 66 8C 3C 8D 30 8E C6 8F 38 .8...0.f.<.0...8
A000:0520 90 60 91 00 92 3E 93 38 94 C6 95 30 96 78 97 30 .`...>.8...0.X.0
A000:0530 98 C6 99 C6 9A C6 9B 18 9C 6C 9D 66 9E CC 9F 1B .........l.f....
A000:0540 A0 30 A1 18 A2 30 A3 30 A4 76 A5 00 A6 6C A7 6C .0...0.0.v...l.l
A000:0550 A8 30 A9 00 AA 00 AB C0 AC C0 AD 18 AE 00 AF 00 .0..............

     Figure 10-9.  One of 16 lists of character codes and bit patterns in
     MCGA character generator RAM. This table defines the bit patterns for
     the third scan line of each character. Character codes are in the
     even-numbered bytes. The odd-numbered bytes contain the corresponding
     bit patterns.


           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 0123456789ABCDEF
A000:0000 00 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 ................
A000:0010 08 FF 09 00 0A FF 0B 00 0C 00 0D 00 0E 00 0F 00 ................

A000:0200 00 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 ................
A000:0210 08 FF 09 00 0A FF 0B 00 0C 00 0D 00 0E 00 0F 00 ................

A000:0400 00 00 01 7E 02 7E 03 00 04 00 05 00 06 00 07 00 ...~ ~..........
A000:0410 08 FF 09 00 0A FF 0B 1E 0C 3C 0D 3F 0E 7F 0F 00 .........<.?....

A000:0600 00 00 01 81 02 FF 03 00 04 00 05 18 06 18 07 00 ................
A000:0610 08 FF 09 00 0A FF 0B 0E 0C 66 0D 33 0E 63 0F 18 ................

A000:0800 00 00 01 A5 02 DB 03 6C 04 10 05 3C 06 3C 07 00 .......l...<.<..
A000:0810 08 FF 09 00 0A FF 0B 1A 0C 66 0D 3F 0E 7F 0F 18 .........f.?....

A000:0A00 00 00 01 81 02 FF 03 FE 04 38 05 3C 06 7E 07 00 .........8.<.~..
A000:0A10 08 FF 09 3C 0A C3 0B 32 0C 66 0D 30 0E 63 0F DB ...<...2.f.0.c..

A000:0C00 00 00 01 81 02 FF 03 FE 04 7C 05 E7 06 FF 07 18 .........|......
A000:0C10 08 E7 09 66 0A 99 0B 78 0C 66 0D 30 0E 63 0F 3C ...f...x.f.0.c.<

A000:0E00 00 00 01 BD 02 C3 03 FE 04 FE 05 E7 06 FF 07 3C ...............<
A000:0E10 08 C3 09 42 0A BD 0B CC 0C 3C 0D 30 0E 63 0F E7 ...B.....<.0.c..

A000:1000 00 00 01 99 02 E7 03 FE 04 7C 05 E7 06 7E 07 3C .........|...~.<
A000:1010 08 C3 09 42 0A BD 0B CC 0C 18 0D 30 OE 63 0F 3C ...B.......0.c.<

A000:1200 00 00 01 81 02 FF 03 7C 04 38 05 18 06 18 07 18 .......|.8......
A000:1210 08 E7 09 66 0A 99 0B CC 0C 7E 0D 70 0E 67 0F DB ...f.....~.p.g..

A000:1400 00 00 01 81 02 FF 03 38 04 10 05 18 06 18 07 00 .......8........
A000:1410 08 FF 09 3C 0A C3 0B CC 0C 18 0D F0 0E E7 0F 18 ...<............

A000:1600 00 00 01 7E 02 7E 03 10 04 00 05 3C 06 3C 07 00 ...~.~.....<.<..
A000:1610 08 FF 09 00 0A FF 0B 78 0C 18 0D E0 0E E6 0F 18 .......x........

     Figure 10-10.  MCGA character definitions for the first 12 scan lines
     of the first 16 characters. The top scan line for each character is
     defined starting at A000:0000, the second scan line starting at
     A000:2000, and so on. (Only the first 32 bytes of each 512-byte list
     are shown.)


Updating Character Generator RAM


     After you create a table of character definitions (discussed in
     Chapter 9), you must make the table accessible to the hardware
     character generator by properly locating it in the video buffer. One
     way to do this is to create the table in RAM (outside the video
     buffer) and then copy it to character generator RAM. You can also read
     the table directly from a disk file into character generator RAM.
     Either technique works on any of the video subsystems discussed here.


EGA and VGA

     To copy a character definition table into video memory map 2, you must
     program both the Sequencer's Memory Mode register and its Map Mask
     register, as well as the Graphics Controller's Mode and Miscellaneous
     registers, to make memory map 2 directly addressable. You can then
     copy character definitions to any of the available table locations in
     map 2. After you update map 2, restore the Sequencer and Graphics
     Controller registers to values appropriate for the alphanumeric video
     mode you are using.

     Listing 10-1a demonstrates how the Sequencer and Graphics Controller
     are programmed on both the EGA and the VGA to make character generator
     RAM in map 2 accessible. Listing 10-1b is the converse routine; it
     restores the Sequencer and Graphics Controller registers to their
     alphanumeric mode default values. You can use the routines in Listings
     10-1a and 10-1b in a program that copies character definitions
     directly from a file into character generator RAM (as shown in
     Listings 10-2a and 10-2b).


���������������������������������������������������������������������������

     Listing 10-1a.  Using character generator RAM on the EGA and
     VGA.

���������������������������������������������������������������������������


��������������������������������������������������������������������������

     Listing 10-1b.  Restoring character generator RAM on the EGA and
     VGA.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 10-2a.  Loading character definitions on an EGA or
     VGA.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 10-2b.  Calling CGenRead1 from a C program.

���������������������������������������������������������������������������


     A faster and more portable way to load character definitions into RAM
     is to use INT 10H function 11H with AL = 0 (see Listings 10-3a and 10-
     3b). When you use the INT 10H function, you can selectively update any
     portion of a table in map 2 by choosing appropriate values for DX (the
     character offset into the table) and CX (the number of character
     definitions to update). To use this video BIOS function, you must
     first store the character definition table in an intermediate buffer.
     This technique consumes more memory than reading character definitions
     directly from disk, but it results in faster code.


���������������������������������������������������������������������������

     Listing 10-3a.  Using the BIOS to load character definitions.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 10-3b.  Calling CGenRead2 from a C
     program.

���������������������������������������������������������������������������


     The INT 10H function 11H services can also update character generator
     RAM from the character tables in the ROM BIOS. To use one of the ROM
     BIOS character definition tables, call INT 10H function 11H with AL =
     1 (for 8-by-14 character definitions) or AL = 2 (for 8-by-8
     definitions). (See Listing 10-4.)


���������������������������������������������������������������������������

     Listing 10-4.  Using a ROM BIOS character definition table.

���������������������������������������������������������������������������


HGC+

     Moving a character definition table into RAM is easier on the HGC+,
     because memory addressing is simpler. Character generator RAM is
     mapped linearly, starting at B000:4000. Since each 256-character table
     occupies 4 KB (256 x 16), subsequent 256-character tables start at
     B000:5000, B000:6000, and so on.

     Because HGC+ memory has no bit planes, you can access character
     generator RAM as easily as any other system RAM. You can, for example,
     use a single REP MOVSB instruction to move bit patterns into character
     generator RAM  from elsewhere in system RAM, or you can read a
     character definition table directly into RAM from a disk file. For
     example, you can modify Listing 10-2a to read a file directly into
     HGC+ character generator RAM by changing the  values of CGenRAMSeg to
     B000H, CGenStartOffset to 4000H, and CGenDefSize to 16.


InColor Card

     Although the InColor Card uses all four bit planes to store character
     definitions, you can use virtually the same routine to copy bit
     patterns into its character generator RAM that you use on the HGC+.
     The only difference is that you can select which of the four bit
     planes to update. Do this by setting bits 4 through 7  of the Plane
     Mask register (18H) to write-protect one or more of the bit planes.
     For compatibility with the HGC+, set these four bits to 0 so that all
     four bit planes contain the same bit patterns.


MCGA

     As on the Hercules adapters, character generator RAM on the MCGA is
     mapped linearly in the video buffer. Thus, you can update MCGA
     character definitions simply by writing the bit patterns in the
     appropriate format in the character definition tables.

     If you update the MCGA character definition tables directly, however,
     your program must store bit patterns and character codes in the format
     expected by the MCGA character generator. It is usually better to use
     INT 10H function 11H to copy character definitions into MCGA character
     generator RAM. This video BIOS function translates character
     definition tables from the linear format used on the EGA and VGA into
     the formatted lists used on the MCGA.

     The MCGA is different from the other video subsystems discussed here
     in that its alphanumeric character generator does not fetch bit
     patterns from the tables at A000:0000 as it generates characters.
     Instead, the character generator uses two internal character
     definition tables, called font pages. To display the characters from
     one of the four tables in video RAM, you must load the table into one
     of the character generator's font pages. Listing 10-5 shows how this
     is done.


���������������������������������������������������������������������������

     Listing 10-5.  Loading font pages on an MCGA.

���������������������������������������������������������������������������


     Thus, displaying a new alphanumeric character set on the MCGA is a
     two-step process. First, you store character definition tables in one
     or more of the four 8 KB blocks of video RAM reserved for this
     purpose. Then you update the character generator's font pages to
     display the characters.


Using RAM-based Character Sets


     When you use characters defined in a RAM-based table, you must choose
     how the alphanumeric character generator is to decode the character
     codes and attributes stored in the displayed portion of the video
     buffer. Using the usual 256-character ASCII set, with 8-bit
     character codes and 8-bit attributes, is simplest. However,
     to display more than 256 different characters at once or to switch
     rapidly between character sets, you must use a wider range of
     "extended" character codes and a different set of attributes.


ASCII Character Sets

     The simplest way to customize alphanumeric characters is to use 8-bit
     ASCII character codes and attributes with a RAM-based character
     definition table. Because there are only 256 ASCII character codes,
     you can display only one 256-character set at a time. However, the
     character codes and attribute bytes stored in the displayed portion of
     the video buffer retain their usual format, so software that knows
     nothing about the RAM-based character definitions can run unchanged
     while displaying the RAM-based character set.


     EGA, VGA, and MCGA
     Whenever you select an alphanumeric video mode using the video BIOS,
     the alphanumeric character generator is configured to display the
     characters defined in the first table in character generator RAM.
     Thus, to display a different set of ASCII characters, all you need do
     is update the table. As described above, INT 10H function 11H provides
     a convenient mechanism for doing this. This same BIOS function also
     lets you display the 256 characters defined in any of the other
     character definition tables as described later in this chapter.


     HGC+ and InColor Card
     When you power up an HGC+ or an InColor Card, the alphanumeric
     character generator uses the ROM-based character definition table by
     default. To display a different ASCII character set, configure the
     alphanumeric character generator to use the RAM-based table (see
     Listing 10-6) and then load a character definition table into video
     RAM at B000:4000.

     To do this, set bit 0 of the adapter's xMode register (14H) to 1. This
     causes the adapter to display the characters defined in the table in
     RAM at B000:4000. Also, set bit 0 of the Configuration Switch register
     (3BFH) to 1 to make character generator RAM addressable at B000:4000.
     (This configuration is called "4K RamFont mode" in Hercules
     documentation.) After you update character generator RAM, you can
     protect it from subsequent modification by resetting bit 0 of the
     Configuration Switch register.


���������������������������������������������������������������������������

     Listing 10-6.  Configuring an HGC+ or InColor Card for updating
     character generator RAM.

���������������������������������������������������������������������������


     Updating character generator RAM is more complicated on the InColor
     Card because all four bit planes are used for character definitions.
     The complexity lies in the way colors are displayed for characters
     defined in the bit planes. A character's color is determined not only
     by its foreground and background attributes, but also by the bit
     planes used to define its pixel pattern.

     The InColor Card combines the pixel values in a character definition
     (in character generator RAM) with the character's foreground and
     background attributes (in the displayed portion of the video buffer)
     to produce a 4-bit attribute for every pixel in the character. The
     logic used is:

     (pixel_value AND foreground_attribute) OR

     (NOT pixel_value AND background_attribute)

     In the example in Figure 10-11, one of the pixels in a character has a
     value of 2 (0010B) in the character definition table. The character's
     attribute byte in the video buffer specifies a foreground value of 0
     and a background value of 7 (0111B). The InColor Card thus displays
     this pixel with an attribute of (2 AND 0) OR (NOT 2 AND 7), or 5.


                                 AND
      ��������������������������������� 0101 �Ŀ
      � Background                �              �
      � attribute               1101             �
 ����������                        NOT          �
�����������������������Ŀ   ����������Ŀ        �                 6-bit
�0  1  1  1  0  0  0  0 �   �0  0  1  0 �     OR ��0101�Palette�digital
�������������������������   �������������        �                 output
             ����������           � Character definition pixel
       Foreground �               �              �
        attribute ��������������������� 0000 ���
                                 AND

     Figure 10-11.  InColor foreground color attribute decoding using RAM-
     based character definitions (8-bit character codes). The pixel value
     in the character definition and both attributes in the character's
     attribute byte all contribute to foreground attribute decoding.


     Using colors on the InColor Card is simpler if you load all four bit
     planes with identical bit patterns so that all pixels in the character
     definitions have the value 0FH (1111B). Then a character's foreground
     and background attributes depend solely on the values in its attribute
     byte. Alternatively, you can specify a foreground attribute of 0FH
     (1111B) and a background attribute of 0 for every character in the
     video buffer. In this case, the displayed colors depend solely on the
     pixel values in the character definitions.

     A more practical use of the InColor Card's character definition RAM is
     to load each bit plane with a different character definition table.
     Then each bit in a character's foreground attribute acts as a mask to
     select a different character set. Of course, a 4-bit foreground
     attribute is still generated, as in Figure 10-11, so in effect each
     character set is associated with the color that corresponds to its bit
     plane. You can, of course, display the character sets in any colors
     you want by programming the palette registers.

     To load the bit planes separately, use the high-order nibble in the
     Plane Mask register (18H) to write-protect the bit planes each time
     you load a different character set. This permits you to use different
     foreground attributes to display the different character sets. For
     example, if all four bit planes contain different character sets, you
     can select each of the four character sets by using the foreground
     attributes 1, 2, 4, and 8.


Extended Character Sets

     All of the video subsystems discussed in this chapter have enough
     character generator RAM to store definitions for more than 256
     characters, so they all provide a way for the character generator to
     recognize extended character codes larger than the usual eight bits.


     EGA and VGA
     On the EGA and the VGA, the usual range of 256 ASCII codes is doubled
     by using bit 3 of a character's attribute byte to designate one of the
     character definition tables in map 2 (see Figure 10-12). In this way,
     512 different characters can be displayed in an alphanumeric mode.

     Normally, the value of bit 3 of a character's attribute byte does not
     affect the character set displayed. This is why: The value of this bit
     selects one of two bit fields in the Sequencer Character Map Select
     register. In turn, the value in each of these two bit fields
     designates one of the available character definition tables in RAM.
     When the video BIOS establishes a video mode, it loads a default set
     of character definitions into the first character definition table in
     map 2 and clears both bit fields in the Character Map Select register.
     Thus, default alphanumeric characters are defined by the bit patterns
     in the first table in map 2, regardless of the value of bit 3 of the
     attribute bytes of the characters displayed.

     Changing the value in the Character Map Select register, however,
     changes the character definition tables associated with bit 3 of each
     character's attribute byte. If two different values appear in the bit
     fields in the Character Map Select register, the value of bit 3
     designates one of two different character definition tables.


     7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
    �������������������������������Ŀ
    �High-order byte�Low-order byte �
    ������������������
     � � � � � � � � � � � � � � � �
     � � � � � � � � �������������8-bit character code
     � � � � ����������������������������4-bit foreground attribute
     ������������������������������������4-bit background attribute
   a.

     7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
    �������������������������������Ŀ
    �High-order byte�Low-order byte �
    ������������������
     � � � � ��� � � � � � � � � � �
     � � � � ��������������������9-bit extended character code
     � � � � ����������������������������4-bit foreground attribute
     ������������������������������������4-bit background attribute
   b.

     7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
    �������������������������������Ŀ
    �High-order byte�Low-order byte �
    ������������������
     � � � � � � � � � � � � � � � �
     � � � � �����������������12-bit extended character code
     ������������������������������������4-bit attribute
   c.

     Figure 10-12.  Character codes and attributes. Figure 10-12a shows the
     usual 8-bit format. Figure 10-12b shows the extended 9-bit format used
     on the EGA, VGA, and MCGA. Figure 10-12c shows the extended 12-bit
     format used on the HGC+ and InColor Card.


     For example, in Figure 10-13, bit 3 is set to 1, so bits 2, 3, and 5
     of the Character Map Select register designate which character
     definition table to use. (This example pertains to the VGA; on the
     EGA, only bits 2 and 3 of the Character Map Select value would be
     meaningful.)


               ������������Ŀ
 �����������������������Ŀ �
 � x  x  x  x  1  x  x  x � �
 �������������������������� �                                      Offset
       Attribute byte       �               3-bit value            in map 2
                                                �����������������Ŀ
               Character Map Select register  111�                 �
                ������������������������Ŀ       �����������������ĴE000H
                � 0   x   1   0    x   x �    011�                 �
                ��������������������������       �����������������ĴC000H
                  �������������               110�                 �
                        �                        �����������������ĴA000H
                        ���������������������010�                 �
                                                 �����������������Ĵ8000H
                                              101�                 �
                                                 �����������������Ĵ6000H
                                              001�                 �
                                                 �����������������Ĵ4000H
                                              100�                 �
                                                 �����������������Ĵ2000H
                                              000�                 �
                                                 �������������������0

     Figure 10-13.  Function of the VGA Character Map Select register.


     Listing 10-7 illustrates two methods of updating this register.
     Although the technique of using an INT 10H function call generally
     requires less code and is more portable, you might prefer to program
     the Sequencer directly in applications that require rapid switching
     between character sets.


���������������������������������������������������������������������������

     Listing 10-7.  Programming the Sequencer Character Map Select
     register on the EGA and VGA.

���������������������������������������������������������������������������


     If both bit fields in the Character Map Select register contain the
     same value, the value of bit 3 of a character's attribute byte does
     not affect which character set is used. If the bit fields designate
     different character definition tables, then the value of bit 3 of each
     character's attribute byte selects between two different character
     sets. Keep in mind, however, that bit 3 is also part of each
     character's 4-bit foreground attribute. When bit 3 of a character's
     foreground attribute is set to 0, the character's displayed color is
     taken from one of the first eight palette registers (0000B through
     0111B). When bit 3 is set to 1, the color derives from one of the
     second eight palette registers (1000B through 1111B).

     Thus, the two 256-character sets selected by bit 3 are displayed with
     two different sets of eight palette register values. This is handy if
     you want to associate a particular set of colors with a character set.
     Otherwise, you might prefer to load the second eight palette registers
     with the same set of values as the first eight so that the value of
     bit 3 of a character's attribute byte has no effect on its displayed
     color. Another technique is to mask bit 3 of the foreground attribute
     by zeroing bit 3 of the Attribute Controller's Color Plane Enable
     register, as in Listing 10-8. Because the value in the Color Plane
     Enable register masks the 4-bit attribute value, zeroing bit 3 in this
     register allows only the first eight palette registers to be
     referenced, regardless of the value of bit 3 in a character's
     attribute byte.


���������������������������������������������������������������������������

     Listing 10-8.  Zeroing bit 3 of the Color Plane Enable register. This
     causes bit 3 of a character's attribute byte to have no effect on its
     displayed attribute.

���������������������������������������������������������������������������


     MCGA
     The MCGA supports 8-bit and 9-bit character codes with the same BIOS
     interface as the EGA and VGA, although the hardware implementation is
     different. On the MCGA, the two character definition tables selected
     by bit 3 of a character's attribute byte are the ones in the MCGA's
     two internal font pages. Although you can load the font pages by
     programming the MCGA's Character Generator Interface register (12H),
     Character Font Pointer register (13H), and Number of Characters to
     Load register (14H), it is easier to use INT 10H function 11H with
     AL = 3.

     As on the EGA and VGA, bit 3 of a character's attribute byte does
     double duty as part of the 9-bit character code as well as the high-
     order bit of the character's foreground attribute. If you want to use
     the same colors for both 256-character sets, you can call INT 10H
     function 10H to store the same set of color values in the second eight
     video DAC color registers as you do in the first eight. You can also
     call INT 10H function 10H to mask bit 3 out of alphanumeric attribute
     decoding (see Listing 10-8).


     HGC+ and InColor Card
     On the HGC+ and the InColor Cards, you can configure the character
     generator to regard the four low-order bits of each character's
     attribute byte as part of the character code. Do this by setting both
     bit 2 and bit 1 of the xMode register to 1. (Hercules calls this
     configuration "48K RamFont mode.")

     By using 12-bit character codes, you can display all characters
     defined anywhere in the Hercules adapter's 48 KB of character
     generator RAM. In practice, you can regard all 48 KB of character
     generator RAM as one continuous character definition table. However,
     in some applications, you might find it more convenient to think of
     character generator RAM as a set of twelve 256-character tables, where
     the four high-order bits of the character code designate one of the
     tables, and the eight low-order bits designate a character definition
     within a table.

     When 12 bits are used as an extended character code, only bits 4
     through 7 of the high-order byte specify a character's attribute (see
     Figure 10-12c). The attributes that Hercules assigned to these bits
     differ somewhat from the usual monochrome display attributes (see
     Figure 10-14).


����������������������������������������������������������������������������ķ
               Enable Blink Bit = 1  Enable Blink Bit = 0
Attribute Bit  (blink enabled)       (blink disabled)
��������������������������������������������������������������������������
7              High-intensity        Boldface
6              Blink                 Reverse
5              Overstrike            No overstrike
4              Underline             No underline

     Figure 10-14.  Extended attribute set on the HGC+ and the InColor
     Card.


       ���ͻ     When using 12-bit character codes on the HGC+ and the
       � T �     InColor Card, you can specify the scan line on which the
       � I �     overstrike and underscore attributes appear. Bits 0
       � P �     through 3 of the Underscore register (15H) control the
       ���ͼ     position of the underscore. Bits 0 through 3 of the
                 Overstrike register (16H) control the position of the
                 overstrike. On the InColor Card, you can also control the
                 displayed color of the underscore and overstrike by
                 storing a value between 1 and 0FH in bits 4 through 7 of
                 the corresponding control register.

     As on the HGC+, the 12-bit character codes on the InColor Card
     designate locations in the character definition tables. Attribute
     decoding is more complicated on the InColor Card, however (see Figure
     10-15). The 4-bit foreground attribute generated for each pixel in a
     character is derived by combining the character's 4-bit attribute with
     the pixel's value in the character definition table.


���������������������������������������������������������������������������
MDA-compatible Attributes (Exception register bit 5 = 1)
              Enable Blink On                 Enable Blink Off
���������������������������������������������������������������������������
Foreground    (pixel value) OR (background)   (pixel value) XOR
                                              (background)
Background    0 if bit 7 of attribute = 0     0 if bit 6 of attribute = 0
              8 if bit 7 of attribute = 1     0FH if bit 6 of attribute = 1
���������������������������������������������������������������������������
Color Attributes (Exception register bit 5 = 0)
���������������������������������������������������������������������������
Foreground    (pixel value) AND (NOT attribute)
Background    0

     Figure 10-15.  InColor Card color attribute decoding using 12-bit
     character codes.


     As was the case when using 8-bit character codes, the peculiar
     interaction of character attributes with the pixel values in the
     character definition table makes controlling colors difficult. To
     simplify matters, you can store the same character definitions in all
     four bit planes when using color attribute decoding; this allows each
     character's 4-bit attribute to specify all 16 colors. When using MDA-
     compatible attributes, you can store the same bit patterns in bit
     planes 0 through 2 and zero bit plane 3. Again, this allows each
     character's 4-bit attribute to completely control the displayed
     attributes.

     If you elect to store different character definition tables in each
     bit plane, each of a character's attribute bits can select one of the
     bit planes. Again, you should program the palette registers carefully
     so that characters from different bit planes are displayed with
     appropriate colors.


Compatibility Problems with Extended Character Codes

     Most PC and PS/2 programs, including the BIOS, MS-DOS, and most
     commercially available applications, expect you to use 8-bit ASCII
     character codes. This means you can update character generator RAM
     with an 8-bit ASCII character set in a different font, but you cannot
     take advantage of the extended 9-bit or 12-bit character codes
     supported by IBM and Hercules.

     If you use the INT 10H interface to display characters with extended
     character codes, you must be careful when you use certain ROM BIOS
     functions. For example, INT 10H function 0AH, which stores an 8-bit
     character code in the video buffer, is not very useful for writing
     characters with a 9-bit or a 12-bit extended character code. On the
     other hand, you can use INT 10H function 9, which handles a 16-bit
     character code and attribute combination, to process extended
     character codes and attributes.

     When you run an application that uses extended character codes, you
     can encounter problems when your application interacts inadvertently
     with software that doesn't recognize the different character-attribute
     format. Consider what might happen if a RAM-resident utility program
     popped up in the middle of your application without being "aware" that
     you were using extended character codes. When the utility program
     placed 8-bit character codes and attributes in the buffer, the
     alphanumeric character generator would interpret them as extended
     character codes and attributes. The results would probably be
     unusable.


Changing the Displayed Character Matrix


     There is another dimension to customizing a RAM-based character
     definition table: You can control the height of the character matrix
     in which characters are displayed. The height of the displayed
     character matrix determines how many rows of characters appear on the
     screen. For example, a 350-line display accommodates 43 rows of 8-by-8
     characters but only 25 rows of 8-by-14 characters.

     With all of the subsystems discussed in this chapter, you can vary the
     displayed height of alphanumeric characters by programming the CRT
     Controller to display characters the same size as the characters
     defined in character generator RAM. Thus, to display 8-by-8 characters
     on a 350-line display, you place 8-by-8 character definitions into
     character generator RAM and then program the CRTC to display
     characters that are 8 pixels high.

     On the EGA and the VGA, you can perform both these tasks by calling
     INT 10H function 11H, although in some situations you may prefer to
     update the character definitions or program the CRTC explicitly.
     Hercules adapters, of course, have no ROM BIOS, so you must do the
     work yourself.


EGA

     Consider how you would display 43 rows of 8-by-8 characters in an EGA
     alphanumeric mode with 350-line vertical resolution, as in Listing 10-
     9. In this example, the call to INT 10H function 11H with AL = 12H
     copies the ROM's 8-by-8 character set (normally used in 200-line video
     modes) into the first of the four tables in map 2 and then calculates
     the proper CRTC register values based on the values of POINTS and ROWS
     in the BIOS Video Display Data Area.


���������������������������������������������������������������������������

     Listing 10-9.  Establishing an 80-by-43 alphanumeric mode on an EGA.

���������������������������������������������������������������������������


     INT 10H function 11H calls INT 10H function 1 to set the position of
     the alphanumeric cursor in the displayed character matrix. As
     described in Chapter 3, the EGA BIOS version of INT 10H function 1
     computes this cursor position incorrectly, leading to an improperly
     displayed cursor. Therefore, the routine in Listing 10-9 updates the
     CRTC Cursor Start and Cursor End registers directly.

       ���ͻ     If your program changes the number of displayed character
       � T �     rows, it should also call INT 10H function 12H to select
       � I �     the EGA BIOS's alternate print screen routine. This
       � P �     routine functions identically to the one in the
       ���ͼ     motherboard BIOS except that it uses the Video Display
                 Data Area value ROWS to determine how many lines to print.
                 (The motherboard BIOS routine disregards ROWS and always
                 prints 25 lines.)


VGA

     You can also use INT 10H function 11H on the VGA to establish an
     alphanumeric mode with a nondefault character matrix (see Listing
     10-10). On the VGA, you set the vertical resolution of the video mode
     using INT 10H function 12H (with BL = 30H) before calling function
     11H. Also, the cursor emulation computations are performed properly in
     the VGA BIOS, so no extra code is required to avoid cursor emulation
     on the VGA.


���������������������������������������������������������������������������

     Listing 10-10.  Establishing an 80-by-50 alphanumeric mode on a VGA.

���������������������������������������������������������������������������


MCGA

     The MCGA can only display characters with 2, 4, 6, 8, 10, 12, 14, or
     16 scan lines. (This is a limitation of the MCGA's Memory Controller.)
     To change the displayed character matrix, use INT 10H function 11H to
     load a new character set into the character generator. Then program
     the Scan Lines per Character register (09H) with a value from 0
     through 7; if the value is n, the number of scan lines displayed in
     the character matrix is (n + 1) * 2. Listing 10-11 shows how to set up
     an 8-by-10 character matrix using the MCGA's 400-line vertical
     resolution to produce 40 rows of 80 characters.


���������������������������������������������������������������������������

     Listing 10-11.  Establishing an 80-by-40 alphanumeric mode on an
     MCGA.

���������������������������������������������������������������������������


     For some values in the Scan Lines per Character register, the MCGA
     incorrectly displays the bottommost scan line of the screen.
     Specifically, when the value in the Scan Lines per Character register
     is 1, 3, 5, or 6, the MCGA replicates part of the topmost scan line on
     the screen at the bottom of the screen. Thus, you should generally
     avoid using these values for the Scan Lines per Character register.


HGC+ and InColor Card

     You must program the HGC+ CRTC explicitly to change the number of
     displayed lines in alphanumeric characters. The subroutine SetHercCRTC
     in Listing 10-14 illustrates a table-driven technique for setting up
     the CRTC's vertical timing parameters for a variety of character
     sizes. Figure 10-16 summarizes the CRTC timing parameters recommended
     by Hercules for any character matrix between 4 and 16 scan lines high
     as well as for characters that are either 8 or 9 pixels wide.


���������������������������������������������������������������������������
                    Width of Character Matrix
CRTC register      8 Pixels           9 Pixels
���������������������������������������������������������������������������
00H                6DH                61H
01H                5AH                50H
02H                5CH                52H
03H                0FH                0FH

���������������������������������������������������������������������������
CRTC                  Height of Character Matrix (in pixels)
register  4    5    6    7    8    9    10   11   12   13   14   15   16
���������������������������������������������������������������������������
04H       5CH  4Ah  3DH  34H  2DH  28H  24H  20H  1DH  1BH  19H  17H  16H
05H       02H  00H  04H  06H  02H  01H  00H  07H  0AH  06H  06H  0AH  02H
06H       58H  46H  3AH  32H  2BH  26H  23H  1FH  1DH  1AH  19H  17H  15H
07H       59H  46H  3BH  33H  2CH  27H  23H  20H  1DH  1BH  19H  17H  16H

     Figure 10-16.  CRTC timing parameters for height and width of the
     alphanumeric character matrix (HGC+ and InColor Card).


     On the InColor Card, the techniques for changing the displayed
     character matrix parallel those used on the HGC+. The values you place
     in the CRTC registers for each possible character matrix are also the
     same.


     Programming Examples
     The routines on the following pages unify the programming techniques
     for changing the displayed character matrix on the EGA (see Listing
     10-12), on the VGA (see Listing 10-13), and on the HGC+ and InColor
     Card (see Listing 10-14). In each case, the function AlphaModeSet()
     programs the alphanumeric character generator and the CRTC to
     accommodate the dimensions of the specified character matrix and
     character code size.


���������������������������������������������������������������������������

     Listing 10-12.  Programming the EGA alphanumeric character
     size.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 10-13.  Programming the VGA alphanumeric character
     size.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 10-14.  Programming the alphanumeric character size on the
     HGC+ and InColor Card.

���������������������������������������������������������������������������


Graphics Windows in Alphanumeric Modes


     When you update a RAM-resident character definition table, you alter
     the appearance of any characters displayed using those definitions.
     The contents of the displayed portion of the video buffer need not be
     updated. You can exploit this characteristic of RAM-based character
     definitions to display pixel-addressable graphics images in an
     alphanumeric mode, thereby displaying text with maximum speed while
     including pixel-by-pixel graphics images on the same screen.

     The technique is similar on both IBM and Hercules subsystems. Tile an
     area of the screen with a sequence of characters whose attribute
     selects a character definition table that contains the graphics image
     (see Figure 10-17). The graphics image is created and modified by
     updating the appropriate character definitions in the table. You can
     regard the character definition table as a sort of virtual graphics
     buffer and access individual pixels within it just as you do in the
     usual graphics modes.

     On the InColor Card, you can specify the value of each individual
     pixel you store in the character definition table as though you were
     using 720-by-348 16-color graphics mode. On other subsystems, however,
     only one memory map is used for character definitions, so you do not
     have pixel-by-pixel attribute control. Instead, pixels in the
     character definition table have a value of 0 or 1; the attributes with
     which the character codes are stored in the video buffer determine the
     appearance of the pixels.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 10-17 is found on page 338     �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 10-17.  A tiled graphics window in an alphanumeric mode.


     Listing 10-15 illustrates the technique for producing a tiled graphics
     window in 80-column alphanumeric mode on the EGA and VGA. The first
     part of the program creates the tiled window by storing the second 128
     ASCII characters in four rows of 32 at the start of the video buffer
     (that is, in the upper left corner of the screen). Then the program
     clears the window by setting the second 128 character definitions to
     0.

     To update a pixel in the window, the subroutine SetPixel() computes a
     byte offset in the character definition table that corresponds to the
     pixel's location in the tiled window. As in graphics modes, the
     routine accesses each individual pixel with a bit mask.


���������������������������������������������������������������������������

     Listing 10-15.  Creating a tiled graphics window on the EGA or
     VGA.

���������������������������������������������������������������������������


HGC+ and InColor Card

     Clearly, the size of a tiled graphics window is restricted if you use
     8-bit character codes because the 8-bit ASCII character set contains
     only 256 characters. If you configure a Hercules adapter for 12-bit
     character codes, however, you can create much larger tiled windows
     without running out of character codes. Also, you can create larger
     windows by displaying taller characters (that is, by increasing the
     height of the displayed character matrix). Of course, if you use
     taller characters you decrease the number of rows of text that you can
     display at the same time; this can be a drawback in some applications.

     You can use similar programming techniques for alphanumeric graphics
     on Hercules adapters and on IBM subsystems. For example, Listing
     10-15 can be modified for use with the HGC+ and InColor Card by
     changing the values of CGenDefSize and CharDefTable and removing the
     calls to the functions CGenModeSet() and CGenModeClear().

       ���ͻ     In establishing a graphics window on a Hercules card,
       � T �     avoid using a character matrix that is 9 pixels wide.
       � I �     Because the ninth (rightmost) pixel in each character is
       � P �     actually a hardware-generated copy of the eighth dot, you
       ���ͼ     cannot control it independently by updating the character
                 definition table.


EGA and VGA

     On the EGA and VGA, you can create larger tiled graphics windows if
     you use 9-bit extended character codes. For instance, you could
     dedicate one 256-character definition table to text characters and a
     second character definition table to graphics tiling characters.
     Nevertheless, the EGA and VGA are still limited to displaying no more
     than 512 different characters at a time, so the largest tiled graphics
     window is much smaller than it can be on a Hercules adapter.

       ���ͻ     When you update pixels in the tiled window, you should
       � T �     minimize  the number of times your program resets the
       � I �     Sequencer (for example, in the routines CGenModeSet() and
       � P �     CGenModeClear()). If you reset the Sequencer each time
       ���ͼ     you update a pixel, you might create screen interference.
                 (Synchronizing Sequencer resets with the vertical retrace
                 interval can eliminate this interference but can also
                 greatly decrease the speed of a program.) If you draw a
                 complicated graphics figure containing many pixels, draw
                 the entire figure at one time as in Listing 10-15.


MCGA

     Character definition tables in MCGA character generator RAM are
     formatted differently than those on the EGA and VGA, so a routine that
     manipulates pixels in character generator RAM must address the tables
     differently (see Listing 10-16). Also, remember that the screen does
     not reflect changes to the MCGA's character definition tables until
     you load the character generator's font pages (see Listing 10-5).


���������������������������������������������������������������������������

     Listing 10-16.  A routine to set pixels in a tiled graphics window on
     the MCGA.

���������������������������������������������������������������������������



                    11  Bit Blocks and Animation


                           Bit Block Move
           CGA and MCGA � EGA and VGA � HGC � InColor Card

                      Bitwise Pixel Operations
                        XOR � NOT � AND � OR

                          Bit Block Tiling

                              Animation
                            XOR Animation
                     Overlapping Bit Block Moves

                       A Graphics-Mode Cursor
                        XOR � Bit Block Move



     This chapter is about moving things around in the video buffer and on
     the screen. Some of the most useful and entertaining graphics-mode
     programs create the appearance of on-screen motion. Objects as mundane
     as a cursor or as unusual as an alien spaceship can appear to move
     across the screen if you erase them and then immediately redraw them
     in successive locations. PC and PS/2 video subsystems are not
     particularly well equipped to support this kind of real-time
     animation, but the techniques in this chapter should help you fully
     exploit their capabilities.

     You might think of video animation in the same context as video games,
     but animation has other uses in computer graphics. For instance, all
     interactive graphics programs require a moving cursor that allows the
     user to point to screen locations. Many drawing or design programs let
     the user move shapes and images around the screen. Robotic control
     programs indicate the status of a robot arm with an animated
     representation of its position. You can create such animation effects
     using the techniques in this chapter.


Bit Block Move


     The basic software tool for many animation techniques is the bit block
     move--a routine that copies a rectangular block of pixels into, out
     of, or within the video buffer. The name "bit block move" describes
     this routine well. After all, a rectangle of pixels is in essence
     nothing more than a block of bits. Still, a bit block move routine can
     do more than simply copy pixel values. As can other video graphics
     drawing routines, a bit block move routine can update pixel values
     using the bitwise logical operations AND, OR, and XOR. These
     operations can create attractive effects when used as part of bit
     block moves.

     To copy a bit block from one location to another within the video
     buffer in PC and PS/2 video subsystems, it is usually more efficient
     to use an intermediate buffer in system RAM. You first copy pixel
     values from the video buffer into the intermediate buffer, then copy
     the values from this buffer to the desired position in the video
     buffer.

     Creating an intermediate copy of the pixels in a bit block might seem
     superfluous, but in most situations it is preferable to trying to move
     the bit block entirely within the video buffer. For example, neither
     the EGA nor the InColor Card supports direct logical operations (AND,
     OR, and XOR) between pixels in the bit planes. Also, CPU accesses to
     video RAM are slower than equivalent accesses to system RAM. Thus,
     when multiple copies of the same bit block are to be stored in the
     video buffer, making a single copy in system RAM and then making
     multiple copies from system RAM to video RAM is more efficient.


CGA and MCGA

     Listing 11-1 is a bit block move routine for the CGA. The routine
     GetBitBlock() copies a block of pixels from the video buffer to a
     buffer in system RAM. The complementary routine StoreBitBlock(), in
     Listing 11-2, copies pixels from system RAM to the video buffer.
     StoreBitBlock()contains subroutines to perform AND, OR, or XOR
     operations on the pixels in system RAM using the previous contents of
     the video buffer.


���������������������������������������������������������������������������

     Listing 11-1.  A routine to copy a block of pixels from the CGA video
     buffer to system RAM.

��������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 11-2.  A routine to copy a block of pixels from system RAM to
     the CGA video buffer.

���������������������������������������������������������������������������


     In the MCGA's 640-by-480 2-color and 320-by-200 256-color modes, pixel
     addressing is different than in the two CGA-compatible modes.
     Otherwise, versions of GetBitBlock() and StoreBitBlock() are similar
     in all MCGA modes.


EGA and VGA

     In native EGA and VGA graphics modes, the bit block move routine must
     move the contents of all four bit planes to system RAM. The
     GetBitBlock() routine in Listing 11-3 extracts bytes from each
     bit plane using read mode 0 and selecting each bit plane in turn with
     the Graphics Controller's Read Map Mask register. StoreBitBlock(), in
     Listing 11-4, then uses write mode 0 to copy data into the bit
     planes. The bit planes are isolated in write mode 0 by programming the
     Sequencer's Map Mask register.

     Do not use the routines in Listings 11-3 and 11-4 on an EGA with
     only 64 KB of video RAM. Because the memory maps are chained together
     to form the two bit planes used in 640-by-350 graphics modes, these
     routines will not work properly in this situation. (Chapter 4
     discusses this in greater detail.)


���������������������������������������������������������������������������

     Listing 11-3.  A routine to copy a block of pixels from the EGA or VGA
     video buffer to system RAM in native graphics modes.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 11-4.  A routine to copy a block of pixels from system RAM
     to the EGA or VGA video buffer in native graphics mode.

���������������������������������������������������������������������������


HGC

     Bit block move routines for HGC and HGC+ 720-by-348 monochrome
     graphics mode are similar routines for CGA 640-by-200 2-color mode.
     The differences  are in how they calculate pixel addresses and in the
     way the video buffer is interleaved.


InColor Card

     The routines for the InColor Card's 720-by-348 16-color mode resemble
     the EGA routines in Listings 11-3 and 11-4, because both adapters'
     video buffers are mapped in parallel bit planes. Differences between
     the routines lie in the way pixel addresses are computed, in how the
     video buffer is interleaved, and in how individual bit planes are
     accessed. On the InColor Card, you can use the same technique as
     ReadPixelInC() (discussed in Chapter 5) to program the Read/Write
     Control and Color registers and isolate the contents of each bit
     plane. Similarly, a bit block store routine for the InColor Card
     follows  StorePixelInC() in its use of the Plane Mask register and the
     Read/Write Control and Color registers.


Bitwise Pixel Operations


     If you experimented with the pixel-programming and line-drawing
     examples in previous chapters, you probably know why the bitwise
     logical operations--XOR, AND, and OR--are useful in video graphics
     programming. In this case, you can skip the next few paragraphs.
     Otherwise, read on to see how video graphics programs can exploit the
     ability to perform XOR, AND, and OR on pixel values.


XOR

     The XOR operation is useful because it is reversible. When you change
     a pixel's value in the video buffer using the XOR function, you can
     restore its original value by repeating the operation. For example, if
     a pixel in the video buffer has the value 9, setting its value by
     XORing it with a value of 5 results in a pixel value of 0CH. XORing
     the resulting pixel value (0CH) with a value of 5 restores the
     original pixel value of 9.

     This implies that you can XOR objects into the video buffer and onto
     the screen, and then erase them, without worrying about saving and
     restoring the contents of the video buffer. The use of XOR has
     limitations, however. One is that an image containing zero-value
     pixels cannot be XORed into the video buffer. Because XORing a pixel
     with 0 leaves the pixel's value unchanged, only nonzero pixels in the
     image affect the video buffer.

     Another more serious limitation is that a patterned background can
     obscure the image you are trying to XOR into the video buffer.
     Consider Figure 11-1, in which a text string is XORed against
     progressively distracting backgrounds. The text is perfectly readable
     against a solid background, but a striped background significantly
     obscures the letters. In the worst case, XORing a single-color image
     into a pattern of random pixels results only in another pattern of
     random pixels.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 11-1 is found on page 361      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 11-1.  Effects of XORing a text string against various
     backgrounds.


NOT

     A bitwise NOT operation on a pixel value toggles all 1 bits to 0 and
     all 0 bits to 1. Obviously, two sequential NOT operations will leave
     the pixel value unchanged. A common programming practice in monochrome
     graphics modes is to use NOT to toggle a reverse video state. For
     instance, a black-on-white character can be reversed to white-on-black
     by performing NOT operations on its pixels.

     The effect of NOT on multibit pixel values is less clear. In this
     situation, the NOT operation converts one pixel value into some other
     pixel value, but the colors corresponding to these two values may be
     unrelated. Thus, in a color graphics mode, performing a NOT operation
     on all pixels in a character matrix changes both the foreground and
     background values, but the resulting color combination may not be
     particularly attractive or even readable. In manipulating pixels in
     color graphics, use NOT with caution.

       ���ͻ     A bitwise NOT is equivalent to performing a bitwise XOR
       � T �     using a binary value of all 1 bits. This means you can use
       � I �     any of the pixel XOR routines developed in this book to
       � P �     perform NOT operations as well. Thus, little can be gained
       ���ͼ     by writing special-purpose NOT routines for pixel
                 manipulation.


AND

     The bitwise logical operation AND is also useful in manipulating
     graphics images. Consider, for instance, how you might go about
     drawing the striped circle in Figure 11-2b. You could do it the hard
     way, by intersecting a set of parallel lines with the circle. This
     procedure would be laborious, however, because of the extra
     programming and increased computational overhead involved in
     determining the intersection points.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 11-2 is found on page 362      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 11-2.  Using AND to draw a striped circle. The circle in Figure
     11-2a consists of pixels of the maximum possible value. The lines are
     drawn across the circle using a pixel AND operation to produce the
     striped circle in Figure 11-2b.


     It is much easier to draw a filled circle (see Figure 11-2a) with
     pixels of the maximum possible value (that is, all bits set to 1)
     against a background of zero-value pixels. This circle is used as a
     mask against which you AND the pixels in the parallel lines. When
     pixels in each line are ANDed with pixels inside the circle, their
     original values are stored intact in the video buffer. Outside the
     circle, the result of ANDing the line pixels with the zero background
     always results in zero-value pixels being stored in the buffer. The
     result: a striped circle.

     You can apply this technique to any graphics form, but it is
     particularly attractive in conjunction with a bit block move routine.
     You can superimpose patterned images with a short sequence of bit
     block moves using pixel AND and OR operations. In Figure 11-3, a
     circular chunk of pattern B is superimposed on pattern A by using a
     mask to isolate a "hole" in pattern A. The inverse of the same mask
     extracts the congruent piece of pattern B. The two masked patterns are
     then superimposed by a third bit block move that uses OR (or XOR) to
     update pixels.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 11-3 is found on page 362      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 11-3.  Masking patterned images with pixel AND operations.


OR

     The bitwise OR operator is less frequently used than XOR for
     manipulating pixel values. The OR operation, unlike XOR or NOT, is not
     reversible. The result of ORing pixels always depends on their
     previous values in the video buffer.

     One typical use of the pixel OR operation is to accentuate
     intersections of forms in the video buffer. Consider what happens when
     you OR two different-colored areas into a 16-color video buffer (see
     Figure 11-4). If one rectangle is filled with pixels of value 3 and
     the other rectangle with pixels of value 5, the pixels at the
     intersection points have the value 7 (3 OR 5). With the usual default
     color palette, the upper rectangle appears cyan, the lower rectangle
     is violet, and the intersection is white.


     ��������������������Ŀ
     ����������������������
     ��Cyan����������������
     ����������������������
     ������������������������������Ŀ
     ������������         �����������
     ������������ White   �����������
     ������������         �����������
     ���������������������ٱ���������
                ���������������������
                �������������Violet��
                ���������������������
                ���������������������

     Figure 11-4.  ORing two colored areas into a 16-bit video buffer.


Bit Block Tiling


     You can use bit block move routines to fill an area of the video
     buffer with any arbitrary pattern. Do this by tiling the buffer
     through bit block moves to adjoining rectangular areas of the buffer
     (see Figure 11-5). Using the AND mask technique, you can fill any
     arbitrary form, such as the circle in Figure 11-6, with a pattern
     contained in a bit block.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 11-5 is found on page 363      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 11-5.  Bit block tiling.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 11-6 is found on page 364      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 11-6.  Tiling with AND mask.


       ���ͻ     You can use a variation of bit block tiling as a sort of
       � T �     software character generator. If you define a group of bit
       � I �     blocks, each of which represents a character in a
       � P �     character set, you can tile the screen with characters.
       ���ͼ     This is one technique for displaying proportionally
                 spaced characters.


Animation


     PC and PS/2 video subsystems have no built-in hardware to support
     animation. Consequently, moving images across the screen is a task
     relegated to software. (This is a good reason to make your video
     graphics routines as efficient as possible.) Several software
     techniques can produce real-time video animation. Each technique is
     best suited to a particular type of animation.


XOR Animation

     You can take advantage of the reversibility of the logical XOR
     operation to make any pixel or set of pixels appear to move across the
     display. To make an object appear to move, XOR it into the video
     buffer twice. The object flickers onto the screen the first time it is
     drawn. It immediately disappears the second time it is drawn. If you
     repeatedly redraw the object in a slightly different position, it
     appears to move across the screen.


     Outlining
     Consider the C fragment in Listing 11-5. This bit of code makes a
     circle appear to grow outward from its center by repeatedly XORing it
     into the video buffer with a gradually increasing radius.


���������������������������������������������������������������������������

     Listing 11-5.  XORing a circle into the video buffer.

���������������������������������������������������������������������������


     This technique is frequently used interactively to outline a
     rectangular area of the display. The outline is rapidly XORed into and
     out of the video buffer as the user moves a pointing device such as a
     mouse. Just as the circle created by the routine in Listing 11-5
     appears to grow, a rectangular outline can appear to move, grow, or
     shrink in response to the user's actions.

     The routine in Listing 11-6 slides a rectangle across the screen. At
     each iteration, the rectangle is drawn and then erased using lines
     that are XORed into the video buffer. In this example, the rectangle's
     onscreen location is changed within an iterative loop. In practice,
     however, the rectangle's size and location could be changed in
     response to input from the keyboard or from a pointing device. In this
     case, the rectangle would be erased and redrawn whenever the input
     indicated a change in position.


���������������������������������������������������������������������������

     Listing 11-6.  XORing a rectangle into the video buffer.

���������������������������������������������������������������������������


     Rubberbanding
     A related technique based on the XOR operation is rubberbanding, in
     which a moving object remains attached to a stationary object by a
     straight line. The technique is called rubberbanding because the line
     that connects the two objects appears to stretch as it moves. Listing
     11-7 is similar to Listing 11-6, but moves a rubberbanded line around
     the point at (150,100).


���������������������������������������������������������������������������

     Listing 11-7.  XORing a line into the video buffer.

���������������������������������������������������������������������������


     Bit Block Moves
     You can use XOR with a bit block move to animate any arbitrary group
     of pixels. But use this technique only with a relatively small bit
     block, since generally a bit block contains many more pixels to be
     drawn and redrawn than does a line or a rectangle. The longer it takes
     to maneuver the bit block around the screen, the slower your video
     routine performs.


     Problems with XOR Animation
     Objects that are animated by XOR operations always flicker. The reason
     is obvious: An object is visible only after you first XOR it into the
     buffer. The second XOR makes it disappear. The resulting flicker draws
     attention to the animated object, and may be desirable, particularly
     if the object is repeatedly XORed even when you aren't moving it. On
     the other hand, the flickering can be distracting, particularly on
     color displays where the XORed object alternates between two garish
     colors.

     You can sometimes alleviate flickering during XOR animation by
     inserting a software "pause" between the first and second XOR
     operations. This pause can be an empty loop, a call to some short
     subroutine, or perhaps a wait for the next vertical blanking interval.
     In any case, because the XORed object remains on the screen slightly
     longer, it may flicker less.

     The animated image can disappear if the loop that performs the XOR
     operations inadvertently becomes synchronized with the display refresh
     cycle. In this situation, the animated object is never visible if both
     XOR operations occur outside the relatively brief time interval when
     the raster is displaying the relevant portion of the video buffer.
     Solving this sort of problem is tricky because it involves both the
     speed of your program and the size of the animated image.


Overlapping Bit Block Moves

     In some applications, you can avoid XOR animation problems by rapidly
     redrawing a block of pixels in overlapping locations in the video
     buffer (see Figure 11-7 and Listing 11-8). The bit block in Figure
     11-7 has a margin of background pixels along its left edge. Each time
     you store the bit block in the video buffer, this margin overlaps the
     foreground pixels in the previously drawn block. Without this margin,
     unexpected streaks of foreground pixels trail the bit block as it
     moves to the right across the screen.

     Although they are fast enough for most purposes, the bit block move
     routines in this chapter are too slow for such performance-intensive
     applications as arcade-style video games. You can tailor the code in
     several ways to increase the animation speed if you're willing to
     sacrifice their general-purpose approach.


     ���������������������Ŀ        ���������������������Ŀ
     ����                  �        �������            |  �
     |���                  �        |������            |  �
     ����                  �        �������            |  �
     |���                  �        |������            |  �
     ����                  ���������������            |  �
     |���                  �        |������            |  �
     ����                  �        �������            |  �
     |���                  �        |������            |  �
     ����������������������        ��������������������
                                      
      �                              �  � Margin
      � Margin of                    �
      � background pixels            � Previous margin
    a.            b.

     Figure 11-7.  Overlapping bit block moves. The bit block is drawn
     (Figure 11-7a), then drawn again slightly to the right (Figure
     11-7b). The margin of background pixels restores the background as
     the bit block is "moved" to the right.


���������������������������������������������������������������������������

     Listing 11-8.  A program to move a block of pixels using the
     overlapping technique.

���������������������������������������������������������������������������


     One technique is to limit the bit block routines to byte-aligned (or,
     on the CGA and the HGC, word-aligned) blocks of pixels. This
     eliminates much of the bit-mask logic and lets you make full use of
     the 80x86 MOVs instruction. Another approach is to write routines that
     handle bit blocks of a fixed, predetermined size. This lets you
     replace some iterative loops in the routines with repetitive sequences
     of in-line code. Unfortunately, even highly optimized CGA and EGA
     animation routines rarely come close to the speed you can expect from
     arcade-style video display hardware.


A Graphics-Mode Cursor


     In alphanumeric modes, the on-screen cursor indicates the location
     where your program expects the user's next input. Most alphanumeric-
     mode programs rely on the hardware-generated blinking cursor to
     indicate the current input location. In graphics modes, on the other
     hand, hardware does not support a cursor; your software must generate
     one.

     Implementing a cursor in a graphics mode is somewhat complicated,
     because you must draw the form that represents the cursor directly
     into the video buffer, while preserving the pixels that the operation
     overwrites. You can do this in two ways: by using XOR to display the
     cursor, or by saving and restoring the bit block that is overlaid by
     the cursor.


XOR

     The simplest way to display a graphics cursor is to XOR it into and
     then out of the video buffer. This technique is the same one used to
     animate graphics images, and the same pros and cons apply.

     Probably the worst side effect of XORing a graphics cursor into the
     video buffer is that the color displayed for the XORed cursor can
     change with the background. The cursor can all but disappear on a
     patterned background or on a background with a displayed color near
     that of the XORed cursor.

     Palette programming can prevent this problem. For example, the EGA
     palette in Figure 11-8 is set up assuming that all pixels in the
     cursor shape have the value 8 (1000B) and that all preexisting pixels
     in the video buffer have a value from 0 through 7. With this
     arrangement, XORing the cursor into the video buffer causes it always
     to be displayed with color value 3FH (high-intensity white). The
     obvious drawback is that this technique halves the number of colors
     you can display.


����������������������������������������������������������������������������ķ
Palette Register   Color Value
��������������������������������������������������������������������������
00H                0
01H                1
02H                2
03H                3
04H                4
05H                5
06H                6
07H                7
08H                3FH
09H                3FH
0AH                3FH
0BH                3FH
0CH                3FH
0DH                3FH
0EH                3FH
0FH                3FH

     Figure 11-8.  EGA palette values for a high-intensity white XOR
     graphics cursor.


Bit Block Move

     Another approach is to make a copy of the bit block of pixels that the
     cursor replaces. You can then erase the cursor by restoring the pixels
     in the video buffer from the copy. This technique is attractive
     because it lets you use any means you choose to draw the cursor.

     A good way to draw the cursor, once you have made a copy of the
     underlying pixels in the video buffer, is to copy the cursor shape
     into the buffer with a bit block move. Obviously, this technique works
     best with a rectangular cursor. To draw a cursor of any arbitrary
     shape, use a two-step process (see Figure 11-9). First, zero a group
     of pixels in the shape of the cursor in the video buffer with a bit
     block AND operation. Then draw the cursor with a bit block OR or XOR
     operation.

       ���ͻ     Whenever you use a graphics-mode cursor, you must ensure
       � T �     that the cursor is erased before updating the video
       � I �     buffer. If you do not, your program may inadvertently
       � P �     update the portion of the video buffer that contains the
       ���ͼ     cursor image. The next cursor move will restore the
                 contents of the buffer to what they were before the cursor
                 was drawn, leaving a "hole" where the cursor was (see
                 Figure 11-10).


     ���������������                         ���������������������������
     ���������������                         ���������������������������
     �����     �����                         �����������     �����������
     �����     �����           ���           ����������� ��� �����������
     ���         ���           ���           ���������   ���   ���������
     ���         ���         �������         ��������� ������� ���������
     ���         ���         �������         ��������� ������� ���������
     ���         ���           ���           ���������   ���   ���������
     �����     �����           ���           ����������� ��� �����������
     �����     �����                         �����������     �����������
     ���������������                         ���������������������������
     ���������������                         ���������������������������
     a.                    b.                c.

     Figure 11-9.  Drawing a graphics cursor with a 2-step mask-and-replace
     technique: First, a mask (Figure 11-9a) is ANDed into the video
     buffer. Then the cursor shape (Figure 11-9b) is ORed into the buffer
     to give the result in Figure 11-9c.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 11-10 is found on page 371     �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 11-10.  If a graphics cursor is accidentally overwritten
     (Figure 11-10a), a "hole" appears when the cursor is erased (Figure
     11-10b).



           12  Some Advanced Video Programming Techniques


                    A Vertical Interrupt Handler
                         EGA and VGA � MCGA

                     Panning on the EGA and VGA
               Positioning the Screen Window � Panning
                      Resizing the Video Buffer

                         Bit-Plane Layering

                      EGA and VGA Split Screen

                       The Light Pen Interface
                Light Pen Position � Light Pen Switch
                  Determining Hercules Video Modes



     This chapter deals with some of the less frequently exploited
     capabilities of PC and PS/2 video subsystems. Most programmers do not
     concern themselves with these hardware features, because they are
     infrequently used in most video software. Still, each of these
     hardware features lends itself to programming techniques that can be
     used in certain applications where nothing else is as effective.

     Nothing in this chapter requires "gonzo programming" or any magical
     knowledge of the hardware. You should nevertheless be comfortable with
     80x86 assembly-language programming before tackling the details of
     this material. Most of the chapter describes programming techniques
     for the EGA and the VGA, but the discussions of the light pen
     interface and bit-plane layering are pertinent to Hercules adapters as
     well.


A Vertical Interrupt Handler


     It's neither the interrupt nor the handler that's vertical--it's the
     fact that the CRTC on the EGA, the VGA, and the MCGA can generate a
     hardware interrupt at the start of the vertical blanking interval,
     that is, at the start of the scan line after the bottom line of
     displayed video buffer data. An interrupt handler for this Vertical
     Interrupt can thus update the video buffer or program the video
     hardware without interfering with the display.

     The interrupt is generated on interrupt request line 2 (IRQ2). The
     computer's programmable interrupt controller (PIC) is set up during
     the ROM BIOS coldstart to map IRQ2 to interrupt vector 0AH, so a
     Vertical Interrupt handler should be designed to handle interrupt 0AH.

       ���ͻ     The programmable interrupt controller used in the IBM PC,
       � T �     PC/AT, and PS/2 Models 50, 60, and 80 is the Intel 8259A;
       � I �     in the PS/2 Model 30, the same functions are supported in
       � P �     a proprietary VLSI chip, the I/O Support Gate Array. In
       ���ͼ     all cases, however, the programming interface to the PIC
                 for managing Vertical Interrupts is the same.


EGA and VGA

     The scan line number at which the interrupt is issued is 1 greater
     than the value in the CRTC's Vertical Display Enable End register
     (12H). The value in this register specifies the number of scan lines
     of video buffer data that are displayed, so the CRTC generates
     Vertical Interrupts at the start of the vertical blanking interval.

     Bits 4 and 5 of CRTC's Vertical Retrace End register (11H) control
     whether and when the CRTC  signals a Vertical Interrupt. You set bit 5
     to 1 to enable the CRTC to generate the interrupt. Bit 4 controls a 1-
     bit latch whose status appears in bit 7 of Input Status Register Zero
     (3C2H). You must zero bit 4 to clear the status latch. When you set
     bit 4 to 1, the latch status bit changes from 0 to 1 when the next
     vertical interrupt occurs, and remains set to 1 until you again clear
     the latch.

     To use the Vertical Interrupt feature, you must perform the following
     actions:

     �  Point the interrupt 0AH vector to a Vertical Interrupt handler.

     �  Enable IRQ2.

     �  Enable the Vertical Interrupt.

     The routine in Listing 12-1 shows how to do this. Note how this
     routine is coordinated with the interrupt handler itself. The routine
     preserves the interrupt 0AH vector so the interrupt handler can chain
     to the previous handler if necessary, and so the routine can
     eventually restore the previous interrupt vector when the interrupt
     handler is no longer needed.


���������������������������������������������������������������������������

     Listing 12-1.  Handling Vertical Interrupts on the EGA and VGA.

���������������������������������������������������������������������������


     The handler itself, in procedure ISR0A, gains control whenever
     interrupt 0AH occurs. To distinguish between the hardware Vertical
     Interrupt on IRQ2 and a possible software interrupt 0AH, the handler
     examines bit 7 of Input Status Register Zero. If this bit is 1, a
     Vertical Interrupt has occurred, and the handler continues about its
     business. If the bit is 0, no Vertical Interrupt has occurred, so the
     handler chains to the previous interrupt 0AH handler.

       ���ͻ     A drawback to using the Vertical Interrupt is that any
       � T �     hardware interrupt on IRQ2 causes the status bit in Input
       � I �     Status Register Zero to be set. Thus, although the status
       � P �     bit can be used to detect software interrupt 0AH, an
       ���ͼ     interrupt handler cannot distinguish between EGA Vertical
                 Interrupts and IRQ2 interrupts generated by other hardware
                 unless the other hardware can be reliably interrogated.
                 Since some other IBM PC adapters can use IRQ2 (for
                 example, the bus version of the Microsoft Mouse), you can
                 reliably use the Vertical Interrupt only when certain
                 about the exact hardware configuration of the PC on which
                 your program is running.

     Once the handler detects a Vertical Interrupt (that is, bit 7 of
     Input  Status Register Zero is 1), it issues a nonspecific end-of-
     interrupt (EOI) instruction to the interrupt controller so that
     subsequent IRQ2 interrupts can be processed. Reentrance is not a
     problem, because additional Vertical Interrupts will not be signalled
     until the handler itself clears and reenables the status latch. Once
     the EOI has been issued, the handler is free to perform some useful
     action. In this example, it simply increments a counter. Just before
     exiting, the handler reprograms the Vertical Retrace End register to
     enable the next Vertical Interrupt.

     The example in Listing 12-2 shows how you can integrate a Vertical
     Interrupt handler into a high-level program. The example is
     intentionally simple. It does nothing but count a designated number of
     Vertical Interrupts and display a message. Of course, your own
     Vertical Interrupt handler might perform more complicated actions than
     simply updating a variable. For instance, you could perform animation
     by updating the video buffer each time the interrupt occurs. You might
     also update the CRT and Attribute controllers to produce a panning
     effect using techniques described later in this chapter.


���������������������������������������������������������������������������

     Listing 12-2.  Using a Vertical Interrupt handler in a C program.

���������������������������������������������������������������������������


       ���ͻ     Hardware support for the Vertical Interrupt feature can
       � T �     vary. IBM's VGA adapter, for example, does not support
       � I �     Vertical Interrupts at all. On some EGA clones, the
       � P �     polarity of bit 7 in Input Status Register Zero is
       ���ͼ     opposite to that of the equivalent EGA bit; that is, a
                 Vertical Interrupt has occurred when bit 7 is 0. (Second-
                 source manufacturers of EGA-compatible adapters do not
                 always emulate every detail of the EGA's occasionally
                 inscrutable hardware design.) To ensure that your Vertical
                 Interrupt handler works correctly on EGA clones, determine
                 the status bit's polarity when the bit is in a known state
                 and devise your test for the Vertical Interrupt
                 accordingly.


MCGA

     A Vertical Interrupt handler for the MCGA, such as the one in Listing
     12-3, is similar to the handler for the EGA and the VGA. On the MCGA,
     the Interrupt Control register (11H) contains the control and status
     bits used to set up and detect a Vertical Interrupt. Zeroing bit 5 of
     the Interrupt Control register enables the MCGA to generate a Vertical
     Interrupt. Zeroing bit 4 clears the interrupt status latch. Setting
     bit 4 to 1 allows the MCGA to detect subsequent interrupts. Bit 6 is
     the interrupt status bit. The MCGA sets this bit to 1 to indicate that
     a Vertical Interrupt has occurred.


���������������������������������������������������������������������������

     Listing 12-3.  Handling Vertical Interrupts on the MCGA.

���������������������������������������������������������������������������


       ���ͻ     On the EGA and MCGA, if a Vertical Interrupt handler gains
       � T �     control while a video BIOS (INT 10H) function is
       � I �     executing, the interrupt handler may inadvertently disrupt
       � P �     BIOS CRTC programming. The reason can be traced to a
       ���ͼ     subroutine buried in the IBM BIOS in these video
                 subsystems. This subroutine is called by several video
                 BIOS routines to perform I/O port output to video hardware
                 registers, including CRT Controller, Sequencer, Graphics
                 Controller, and Attribute Controller registers.

                 Unfortunately, this subroutine is not impervious to
                 interrupts. It contains a sequence of two 8-bit port
                 writes (OUT DX,AL). The first OUT loads the designated
                 address register. The second OUT writes a data byte to the
                 corresponding data register. If an interrupt occurs
                 between the two port writes, and if the interrupt handler
                 itself writes to the same port, the BIOS subroutine's
                 second port write may be invalid.

                 To avoid this situation on the EGA and MCGA, the Vertical
                 Interrupt handlers in Listings 12-1 and 12-3 read the
                 value of the CRTC Address register at port 3D4H (3B4H on
                 an EGA with a monochrome display). On the EGA, this value
                 is only readable for about 15 milliseconds after the port
                 has been written, but this is enough time for the Vertical
                 Interrupt handler to read and preserve the value of the
                 CRTC Address register. The handler can thus restore the
                 value before it returns from the interrupt.


Panning on the EGA and VGA


     The 256 KB video buffer of the EGA and the VGA can store several
     screens of data. Thus, in a sense, what is displayed represents a
     "screen window," a sort of hardware window into the contents of the
     video buffer.


Positioning the Screen Window

     On an adapter such as the MDA or the CGA, the CRT Controller's Start
     Address registers control which portion of the video buffer is
     displayed. Because these registers contain a byte offset into the
     video buffer, you can control the position of the screen window only
     to the nearest byte. On the other hand, the CRT Controller on the EGA
     and the VGA can position the start of the screen window at any given
     pixel position.

     In graphics modes, the contents of the CRTC's Start Address High and
     Start Address Low registers (0CH and 0DH) locate the screen window to
     the nearest byte offset in the video buffer. The contents of the
     CRTC's Preset Row Scan register (08H) and the Attribute Controller's
     Horizontal Pel Pan register (13H) "fine-tune" the screen window's
     position pixel by pixel (see Figure 12-1).

     When you change the screen window's position smoothly, pixel by pixel,
     the displayed image appears to pan across the screen. A convenient way
     to do this is to write a routine that locates the screen window at a
     specified pixel position and then call the routine iteratively from
     within a loop. This routine, as demonstrated in Listing 12-4, must
     distinguish between alphanumeric and graphics modes. It must also
     handle a 9-pixel-wide character matrix in VGA and EGA monochrome
     alphanumeric modes.


              ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ
     �������0�   � �   � �   � ����� ����� �   � �   � �   �
     �        ����� ����� ����� ����� ����� ����� ����� �����
Start Address ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ
registers    1�   � �   � ����� �   � �   � ����� �   � �   �
specify this  ����� ����� ����� ����� ����� ����� ����� �����
character     ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ
             2�   � ����� �   � �   � �   � �   � ����� �   � Origin
              ����� ����� ����� ����� ����� ����� ����� ����� of screen
              ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ window
             3�   � ����� �   � �   � �   � �   � ����� �   �   �
              ����� ����� ����� ����� �������������������������ͼ
              ���Ŀ ���Ŀ ���Ŀ ���Ŀ ��Ŀ ���Ŀ ���Ŀ ���Ŀ
       �����4�   � ����� ����� ����� ���۳ ����� ����� �   ������ͻ
       �      ����� ����� ����� ����� ����� ����� ����� �����       �
       �      ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ       �
       �     5�   � ����� �   � �   � �   � �   � ����� �   �       �
       �      ����� ����� ����� ����� ����� ����� ����� �����       �
       �      ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ       �
       �     6�   � ����� �   � �   � �   � �   � ����� �   �       �
       �      ����� ����� ����� ����� ����� ����� ����� �����       �
       �      ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ ���Ŀ       �
       �     7�   � �   � �   � �   � �   � �   � �   � �   �       �
       �      ����� ����� ����� ����� ����� ����� ����� �����       �
       �        0     1     2     3     4     5     6     7         �
       �                                                           �
       �                                �                           �
Value for Preset               Value for Horizontal                 �
Row Scan register              Pel Pan register                     �
                                            �����������������������ͼ
                                         �������������������������Ŀ
                                         �  ABCDEFG���������Ŀ     �
                                         �  HIJKLMN          �     �
                                         �  OPQR             �     �
                                         �  ������������������     �
                                         �  Displayed portion      �
                                         �  of buffer              �
                                         ���������������������������
                                                Video buffer

     Figure 12-1.  Control of the displayed portion of the video buffer in
     alphanumeric modes.


���������������������������������������������������������������������������

     Listing 12-4.  Setting the screen origin on the EGA and VGA.

���������������������������������������������������������������������������


     ScreenOrigin() accepts as input the x- and y-coordinates of the pixel
     that identifies the origin (the upper left corner) of the screen. The
     routine first updates the CRTC's Start Address registers. In effect,
     this positions the screen at the upper left pixel of the character
     that contains the origin in alphanumeric modes, or at the leftmost
     pixel in the byte that contains the origin in graphics modes. Then
     ScreenOrigin() positions the virtual screen exactly by updating
     the Horizontal Pel Panning and Preset Row Scan registers.

     The content of the Attribute Controller Horizontal Pel Panning
     register corresponds to the bit offset of the pixel in the screen's
     upper left corner. The value to store in this register is thus

     x MOD 8

     In the case of 9-pixel characters in VGA alphanumeric modes and in 80-
     by-25 monochrome mode on the EGA, the value is

     (x + 8) MOD 9

     The Horizontal Pel Panning register is programmed the same way in both
     alphanumeric and graphics modes. This is not the case, however, for
     the CRTC's Preset Row Scan register, which controls the vertical
     position of the start of the screen.

     In alphanumeric modes, the number of rows of pixels displayed for each
     row of characters in the video buffer depends on the height of the
     displayed character matrix. This is the value stored as POINTS in the
     ROM BIOS Video Display Data Area. The Start Address registers position
     the virtual screen to the particular character in the video buffer,
     and the Preset Row Scan register indicates which line in the character
     matrix contains the origin of the virtual screen. The Preset Row Scan
     register thus contains a value between 0 (the top line of the
     character) and POINTS-1 (the bottom line). In graphics modes, the
     pixels in each byte in the video buffer correspond one-to-one with
     pixels on the screen, so the Preset Row Scan register always contains
     0.

     To avoid interference with the display, updates to the Horizontal Pel
     Panning, Preset Row Scan, and Start Address registers should be
     synchronized with the display refresh cycle. The Horizontal Pel
     Panning register must be updated during the vertical blanking
     interval. On the other hand, the CRTC samples the values in the Start
     Address and Preset Row Scan registers at the beginning of vertical
     retrace, so these registers should be updated when vertical retrace is
     not active.


Panning

     The routine in Listing 12-5 shows how you can call ScreenOrigin() to
     pan the screen up and down or across the video buffer. Because the
     position of the virtual screen always changes during a vertical
     blanking interval, the panning effect is smooth, with no interference
     on the screen.


���������������������������������������������������������������������������

     Listing 12-5.  A routine to perform smooth pixel-by-pixel panning on
     an EGA or VGA.

���������������������������������������������������������������������������


Resizing the Video Buffer

     Horizontal panning introduces a problem. The way the video buffer is
     normally mapped, the first byte of each line of data in the buffer
     immediately follows the last byte of the previous line. If you try to
     pan horizontally with this map, each line appears to wrap around the
     screen as the screen window moves across the video buffer. To perform
     horizontal panning usefully, you should resize the video buffer so
     each line of data in it is wider than the screen window.

     The value in the CRT Controller's Offset register (13H) controls the
     way the CRTC maps lines in the video buffer. As it scans the raster,
     the CRTC uses the value in this register to locate the start of each
     line in the video buffer map. Normally, lines in the video buffer are
     the same width as displayed lines. Increasing the value in the Offset
     register widens the lines in the video buffer map so only part of each
     line can be displayed at one time. This lets you pan horizontally
     without wraparound.

     For example, consider how you could double the logical width of the
     video buffer in 80-by-25 alphanumeric mode. By default, the video BIOS
     stores the value 28H in the CRTC's Offset register, so the CRTC
     regards each line in the buffer as being 40 words (80 bytes) wide.
     Although each logical line in the buffer contains 160 bytes of data
     (80 character codes and 80 attribute bytes), character codes and
     attributes are stored in different video memory maps (see Figure 10-3
     in Chapter 10). Thus, to double the logical line width, store 50H (80
     decimal) in the CRTC's Offset register. The CRTC will still display 80
     characters in each row on the screen, but it skips 160 characters of
     data between rows of characters in the  video buffer.

     When you resize the video buffer by programming the CRTC's Offset
     register, be careful not to exceed the bounds of the 256 KB video
     buffer. For example, in 640-by-350 16-color graphics mode, one
     screen's worth of pixels occupies 28,000 bytes (80 bytes per line *
     350 lines) in each of the 64 KB video memory maps. If you resize the
     video buffer by increasing the value stored in the CRTC Offset
     register, you cannot go beyond 187 bytes per line in this video mode
     without exceeding the 64 KB limit.

     The routine BufferDims() in Listing 12-6a can be called to
     redimension the video buffer in either graphics or alphanumeric modes.
     It accepts as parameters the desired horizontal and vertical
     dimensions of the buffer in pixels. The routine updates the relevant
     variables in the video BIOS data area and then programs the CRTC
     Offset register with the appropriate value. The example in Listing 12-
     6b shows how BufferDims() could be called to transform a default
     80-by-25 alphanumeric mode into a 160-by-102 mode in which the Pan()
     routine in Listing 12-5 can be used.


���������������������������������������������������������������������������

     Listing 12-6a.  Redimensioning the video buffer.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 12-6b.  Creating a 160-by-102 alphanumeric mode.

���������������������������������������������������������������������������


Bit-Plane Layering


     In EGA and VGA 16-color graphics modes and in the InColor Card's 720-
     by-348 16-color mode, you can display any combination of the four bit
     planes. On the EGA and VGA, the four low-order bits of the Attribute
     Controller's Color Plane Enable register (12H) control which bit
     planes are displayed. Similarly, on the InColor Card, the four low-
     order bits of the Plane Mask register (18H) determine which bit planes
     are displayed. In all three subsystems, all four bits are set to 1 to
     enable the display of all four bit planes. You can zero any
     combination of these bits to prevent display of the corresponding bit
     planes.

     When you disable a bit plane in this way, pixel values are interpreted
     as though the corresponding bit in each pixel were set to 0. The
     contents of a disabled bit plane are unaffected. This means you can
     draw different images into different bit planes and display them
     selectively. When bit planes containing different images are displayed
     together, the images appear to overlap, as if the bit planes were
     transparent and layered one above the other.

     Consider the example in Figure 12-2. The grid is drawn in bit plane 3
     and the cylinder in bit planes 0 through 2. (A quick way to draw both
     figures into the bit planes is to OR the appropriate pixel values into
     the video buffer.) If you use a default 16-color palette, the grid
     appears gray, and the cylinder can have any of the usual eight
     unintensified colors.


             ������������������������������������������ͻ
             �                                          �
             �    Figure 12-2 is found on page 395      �
             �    in the printed version of the book.   �
             �                                          �
             ������������������������������������������ͼ

     Figure 12-2.  Bit-plane layering. The cylinder's pixels have values
     between 0 and 7 (bit planes 0 through 2); the grid's pixels have the
     value 8 (bit plane 3 only). Selectively enabling or disabling bit
     planes 0 through 2 and bit plane 3 displays the cylinder, the grid, or
     both.


     If all four bit planes are displayed, both grid and cylinder appear on
     the screen. If you disable bit plane 3, the grid disappears. If you
     disable bit planes 0 through 2, displaying only bit plane 3, the
     cylinder disappears and only the grid is visible. In all three cases,
     the contents of the bit planes remain intact.

     In using the default palette register values with the grid and
     cylinder, you'll find the pixels at which the grid and cylinder
     intersect are displayed with intensified colors. You can avoid this by
     updating the palette so that the colors displayed for the intersection
     points (pixel values 9 through 0FH) are the same as the corresponding
     unintensified colors (1 through 7). Then, when both grid and cylinder
     are displayed, the cylinder appears in front of the grid.


EGA and VGA Split Screen


     You can configure the CRT Controller on the EGA and the VGA to display
     two different portions of the video buffer on the same screen (see
     Figure 12-3). To do this, program the CRTC's Line Compare register
     (18H) with the raster line at which you want to split the screen, as
     shown in Listing 12-7a and Listing 12-7b.


                   �����������������������������������������������������Ŀ
CRTC Start������� � �������������������������������������������������Ŀ �
Address registers  � �  ������                                         � �
                   � �  �������������                                  � �
                   � �  ��������  ������                               � �
                   � �  ����� �������� ��                              � �
                   � �                                                 � �
Start of video����� �������������������������������������������������Ĵ �
buffer             � �  �����  �����                                   � �
                   � �  ����  ����  ����  ����  ����                   � �
                   � �  ����  ����  ����  ����  ����                   � �
                   � �  ����  ����  ����  ����  ����                   � �
                   � �  ����  ����  ����  ����  ����                   � �
                   � �                                                 � �
                   � ��������������������������������������������������� �
                   �������������������������������������������������������

     Figure 12-3.  Appearance of an EGA or VGA split screen. The top part
     of the screen displays data from the location in the video buffer
     specified by the CRTC Start Address registers. The bottom part of the
     screen displays data from the start of the video buffer.


     The contents of the CRTC Start Address registers determine which
     portion of the video buffer is displayed in the top part of the
     screen. As the raster is drawn during each display refresh cycle, the
     CRTC compares the current scan line with the value in the Line Compare
     register. When the values are equal, the CRTC resets its internal
     address counter so that the remaining scan lines in the raster are
     drawn using data from the start of the video buffer. Thus, the top of
     the video buffer is always displayed in the bottom part of the split
     screen.

     Both the EGA and the VGA accommodate Line Compare values larger than
     eight bits (0FFH or 255 scan lines) by using other CRTC registers to
     contain additional high-order bits. Thus, bit 8 of the Line Compare
     value is represented in bit 4 of the CRTC Overflow register (07H).
     On the VGA, a ninth bit must also be specified for the Line Compare
     value; this bit is represented in bit 6 of the the Maximum Scan Line
     register (09H). Programming the CRTC with a Line Compare value thus
     requires you to update two different registers on the EGA and three
     different registers on the VGA.


���������������������������������������������������������������������������

     Listing 12-7a.  Splitting the screen on the EGA.

��������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 12-7b.  Splitting the screen on the VGA.

���������������������������������������������������������������������������


       ���ͻ     Because the CRTC uses the Line Compare value while it is
       � T �     actively updating the raster, the best time to change this
       � I �     value is during a vertical retrace interval as in Listings
       � P �     12-7a and 12-7b.
       ���ͼ

                 The video BIOS default Line Compare value is the maximum
                 possible value (1FFH on the EGA, 3FFH on the VGA). Use
                 this default value to "unsplit" the screen. There are also
                 certain values that the CRTC does not handle in a useful
                 manner. On both the EGA and VGA, do not specify a Line
                 Compare value that is between the Vertical Retrace Start
                 and Vertical Total values. Also, in 200-line modes on the
                 VGA, the Line Compare register value should be an even
                 number.

                 In native graphics modes in the IBM EGA, the CRTC
                 duplicates the scan line at which the screen is split.
                 This anomaly is also found in some EGA clones.

     You might find it convenient to regard the bottom portion of the split
     screen as a sort of window superimposed on the top portion. Use the
     first portion of the video buffer for the window foreground (the lower
     part of the split screen) and some other portion of the buffer for the
     background.

     One attractive way to use the split-screen feature is to scroll the
     split smoothly up or down the screen. Do this by incrementing or
     decrementing the value in the Line Compare register within a loop, as
     is done by the routine in Listing 12-8.


���������������������������������������������������������������������������

     Listing 12-8.  Smooth vertical scrolling of a split screen on the EGA
     or VGA.

���������������������������������������������������������������������������


The Light Pen Interface


     On most video subsystems covered in this book, the CRT Controller can
     return the position of a light pen. When you trigger a light pen, it
     sends a signal to the CRTC at the moment the video display's electron
     beam sweeps past the pen's light sensor. The CRTC responds by storing
     the current value of its internal address counter into its Light Pen
     High and Light Pen Low registers. This value corresponds to the offset
     in the video buffer of the data displayed in the raster at the point
     where the light pen was triggered. Thus, the value in the Light Pen
     High and Low registers can be translated into row and column
     coordinates for screen locations.

       ���ͻ     You can't attach a light pen to IBM's MDA, but Hercules
       � T �     monochrome adapters can support one. However, a light pen
       � I �     used with a monochrome display must be capable of
       � P �     operating with the high-persistence P39 phosphor used in
       ���ͼ     green monochrome displays.


Light Pen Position

     The light pen position that the CRTC returns is not an exact pixel
     location. One reason is simply that the value returned in the CRTC's
     Light Pen registers is a byte offset into the video buffer, so the
     light pen's horizontal position can be determined only to the nearest
     byte of pixels. Another source of inaccuracy is that the CRTC chip
     itself introduces a small amount of delay between the time it receives
     a signal from the light pen and the time it stores a value in its
     Light Pen registers. The value returned in the Light Pen registers
     thus can be as much as 5 bytes too large; the actual amount of error
     must be determined empirically.

     The light pen programming interface, shown in Figure 12-4, is similar
     on all IBM and Hercules adapters. To determine a light pen's position,
     your program must first reset the CRTC's light pen latch by writing a
     0 to I/O port 3DBH (3BBH on an MDA, a Hercules adapter, or an EGA with
     a monochrome display). Then it must poll the Status Port at 3DAH (3BAH
     in monochrome modes). When bit 1 of the Status Port value changes from
     0 to 1, the light pen has been triggered and the routine can obtain
     its location from the CRTC (see Listing 12-9a).

     After reading the light pen location from the Light Pen registers, you
     must apply an empirical correction for the intrinsic delay in the
     CRTC. The routine in Listing 12-9b, for the EGA's 80-by-25
     alphanumeric mode, subtracts 7 from the value that the CRTC returns.
     To convert the result into a pixel location, subtract the value in the
     Start Address High and Start Address Low registers from the corrected
     CRTC value. (You can get the Start Address value by dividing the value
     in CRT_START in the Video Display Data Area by 2. You can also read it
     from the Start Address High and Start Address Low registers on the
     EGA, the HGC+, and the InColor Card.) Then divide the difference by
     the number of characters in each row of the video buffer. (This value
     is represented in the CRTC's Horizontal Displayed register, or in
     CRT_COLS on the EGA.) The quotient is the y-coordinate of the light
     pen location. The remainder is the character column corresponding to
     the position of the light pen.


����������������������������������������������������������������������������ķ
I/O Port                         Function
���������������������������������������������������������������������������
MDA, HGC, HGC+, and InColor Card
3B9H                             Set light pen latch
3BAH bit 1                       Light pen trigger
3BAH bit 2                       Light pen switch (IBM adapters only)
3BBH                             Reset light pen latch

CGA, EGA
3DAH bit 1                       Light pen trigger
3DAH bit 2                       Light pen switch (IBM adapters only)
3DBH                             Reset light pen latch
I/O Port                         Function
3DBH                             Reset light pen latch
3DCH                             Set light pen latch

     Figure 12-4.  Light pen programming interface. Note: In EGA
     monochrome modes, read light pen trigger and switch status from 3BAH
     instead of 3DAH.


���������������������������������������������������������������������������

     Listing 12-9a.  Getting the light pen's location from the
     CRTC.

��������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 12-9b.  Using GetLightPen in a C program.

���������������������������������������������������������������������������


     If this seems like more trouble than it's worth, you're probably
     right. On IBM video adapters, as well as in Hercules adapters'
     alphanumeric modes, you can call INT 10H function 4 to return the
     light pen location. If you plan to use a light pen in Hercules
     graphics modes, however, you're on your own.


Light Pen Switch

     On IBM adapters, you can determine whether the light pen switch is
     depressed by examining bit 2 of the Status Port value returned from
     port 3DAH (3BAH in monochrome modes). This bit is set to 1 while the
     switch is closed. It returns to 0 when the switch is opened. You
     should usually test the status of the light pen switch before
     attempting to read the CRTC's Light Pen registers.


Determining Hercules Video Modes

     The Light Pen registers can also be used to determine video modes on
     Hercules adapters. In most applications, determining the current video
     mode is not a problem, because the application itself establishes the
     mode. Sometimes, however, a program may not know the video mode a
     priori. For example, a screen dump program (see Appendix B) may need
     to determine the video mode to correctly interpret the contents of the
     video buffer. Similarly, a RAM-resident "pop up" program should save
     and then restore the video mode into which it "pops."

     You can easily determine the current ROM BIOS video mode by calling
     INT 10H function 0FH. The task is more difficult for the Hercules
     adapters, because the BIOS does not keep track of the video mode. You
     can sometimes infer the video mode from the Video Display Data Area
     variables CRT_COLS, CRT_LEN, and POINTS, but not everybody who writes
     programs for Hercules adapters keeps these variables updated.

     Moreover, there is no direct way to interrogate the hardware to
     determine the video mode. For example, the Mode Control register
     (3B8H), used to select the video mode, is unfortunately a write-only
     register. Nevertheless, you can infer a Hercules adapter's video mode
     by latching the 6845's Light Pen High and Low registers (10H and 11H)
     at the start of vertical retrace, as shown in Listing 12-10.


���������������������������������������������������������������������������

     Listing 12-10.  Identifying the current video mode on a Hercules
     adapter.

���������������������������������������������������������������������������


     The routine in Listing 12-10 waits for the start of vertical retrace
     and triggers the light pen at this point with an OUT instruction to
     port 3B9H. The Light Pen registers reflect the value of the CRTC's
     internal address counter at the point where vertical retrace begins.
     (This value is the product of the values in the CRTC Horizontal
     Displayed and Vertical Sync registers.) You can expect the Light Pen
     registers to contain at least 7D0H (80 words per character row x 25
     rows) in 80-by-25 alphanumeric mode and 0F4BH (45 words per character
     row x 87 rows) in 720-by-348 graphics mode. Inspecting the Light Pen
     value thus reveals whether the HGC is in alphanumeric or graphics
     mode.

       ���ͻ     In practice, the Light Pen value returned is somewhat
       � T �     larger than these expected values because of the delay in
       � I �     the CRTC timing. This imprecision makes the technique
       � P �     somewhat less useful on the HGC+ and InColor cards, where
       ���ͼ     you must distinguish among all the different character
                 sizes that can be displayed by the CRTC in alphanumeric
                 mode. For example, the value returned by GetHercMode()
                 when 9-by-8 characters are displayed is near 0DC0H (80 x
                 44)  and near 0DB6H (90 * 39) when 8-by-9 characters are
                 displayed. Because the Light Pen value is inexact, you may
                 not be able to distinguish these two different CRTC
                 configurations.



          13  Graphics Subroutines in High-Level Languages


                    Linking Graphics Subroutines
                          Subroutine Calls
                Interrupts to a Memory-Resident Driver
                             Inline Code

                          Global Data Areas

                     Layered Graphics Interfaces
                     Direct Hardware Programming
                       Extended BIOS Interface
                        High-Level Interface



     Most programming examples in this book are written in assembly
     language, the language of choice for programs that need to control
     hardware precisely and to run as fast as possible. Nevertheless, most
     IBM PC programmers prefer not to write large applications entirely in
     assembly language because they can write, debug, and maintain a
     program in a high-level language much more effectively.

     As you write the code for a program that produces video output, you
     must balance the convenience and conceptual clarity a high-level
     language provides against the speed and exact control provided by
     assembly language. A good rule of thumb is to use assembly language
     whenever you directly access the video buffer or the video subsystem's
     control registers. The rest of the time, you can generally obtain
     satisfactory performance using any compiled high-level language.

     This chapter focuses on the interface between programs written in
     high-level languages and the low-level, assembly-language drivers that
     actually access the video hardware. You can implement the interface in
     several ways. The method you select should depend on the language you
     are using, your familiarity with the memory models and parameter-
     passing techniques that your compiler uses, and (as always) your own
     good judgment in evaluating the alternatives.

     The last part of the chapter introduces several different high-level
     video programming interfaces. The focus is on the reasons why high-
     level programming interfaces are used and the programming approach
     involved in using them.


Linking Graphics Subroutines


     You can tie low-level graphics subroutines to high-level applications
     in several ways. The three techniques discussed here--subroutine
     calls, calling a set of memory-resident routines, and using inline
     code in a high-level-language program--have all been proved in various
     graphics applications. As usual, the "best" method to use in any given
     application is a matter of judgment.


Subroutine Calls

     This book contains numerous subroutines that are designed to be called
     from within a high-level-language program. Most are to be linked to
     programs compiled with the Microsoft C compiler. However, you can link
     these subroutines to any high-level-language program if you know the
     proper protocol for structuring executable code, and for passing
     parameters to a subroutine and returning values from it. The routines
     in Listings 13-1 through 13-4 show how to call the same assembly-
     language subroutine from Microsoft C, Microsoft FORTRAN, Turbo Pascal,
     and interpreted BASIC.


��������������������������������������������������������������������������

     Listing 13-1a.  The SetPixel subroutine (Microsoft C small-model
     calling conventions).

��������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 13-1b.  Calling SetPixel() from a C program.

��������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 13-2a.  The SETPEL subroutine (Microsoft FORTRAN calling
     conventions).

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 13-2b.  Calling SETPEL() from a FORTRAN program.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 13-3a.  The SETPEL subroutine (Turbo Pascal calling
     conventions).

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 13-3b.  Calling SETPEL() from a Turbo Pascal program.

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 13-4a.  The SETPEL subroutine (BASICA calling convention).

���������������������������������������������������������������������������


���������������������������������������������������������������������������

     Listing 13-4b.  Calling SETPEL from a BASICA program.

���������������������������������������������������������������������������


     One of the ways these assembly-language subroutines differ is that
     they use different memory models. A memory model describes the segment
     organization of a program--whether executable code is separated from
     program data, and whether segments are accessed with 16-bit (near) or
     32-bit (far) addresses. For example, a small-model program has one
     near code and one near data segment; a large-model program can have
     multiple far code and far data segments. The subroutines in Listings
     13-1 through 13-4 conform to the default memory models used by the
     different language translators.

     The protocol for passing parameters also varies among compilers and
     programming languages. In Pascal, for example, parameters are pushed
     on the stack in the order they appear in the PROCEDURE statement,
     while in C, parameters are pushed in reverse order. Also, either the
     actual value of a parameter or its address may be passed; this depends
     on the programming language you use as well as on the type of data
     involved. Each compiler's reference manual contains details on its
     parameter-passing protocol.


     Microsoft C
     Source code examples in previous chapters that can be called from a C
     program are all designed to be linked with small- or compact-model
     programs. To call them from a medium- or large-model program, you must
     make three modifications to the source code to make it conform to
     these memory models' subroutine-calling conventions.

     �  Change the name of the executable code segment.

     �  Use the far keyword in assembler PROC directives.

     �  Modify the stack frame addressing to accommodate the calling
        routine's 32-bit return address.

     For example, to call SetPixel10() within a medium-model C program,
     change the name of the _TEXT segment in SetPixel10()'s source
     code to a name of the form module_TEXT and use the far keyword
     in the routine's PROC directive. Also, adjust the stack frame
     addresses by two bytes to account for the 32-bit return address.


     Microsoft FORTRAN
     Microsoft's FORTRAN compiler does not generate small- or compact-model
     programs, so the far addressing conventions applicable to medium- and
     large-model programs apply to FORTRAN-callable assembly-language
     graphics subroutines. The C-callable version in Listing 13-1a and the
     FORTRAN equivalent in Listing 13-2a differ in several ways. These
     differences relate to the way parameters are passed on the stack to
     the subroutine.

     The C compiler passes the current values of each subroutine argument
     in reverse order, so the first argument is on top of the stack. The
     FORTRAN compiler passes the 32-bit address of each argument's value in
     the order in which the arguments appear in the subroutine's
     argument list. The C subroutine obtains the argument values directly
     from the stack; the FORTRAN routine must obtain the arguments'
     addresses from the stack, then use the addresses to obtain the values.
     Also, in C, the routine that called the subroutine discards the
     arguments on the stack. In contrast, in FORTRAN the called subroutine
     cleans up the stack when it exits.

       ���ͻ     The Microsoft C, FORTRAN, and Pascal compilers let you
       � T �     specify the parameter-passing protocol used to call a
       � I �     particular subroutine. For  example, you can write a C-
       � P �     callable subroutine and then access it using the
       ���ͼ     appropriate compiler directive in your FORTRAN or Pascal
                 program. This interlanguage linking capability became
                 available in MS C version 3.00, MS Pascal version 3.3, and
                 MS FORTRAN version 3.3.

                 Including a compiler directive in your high-level source
                 code can be more convenient than modifying an assembly-
                 language subroutine. For example, a C subroutine can be
                 called from a FORTRAN program by declaring the subroutine
                 in a FORTRAN INTERFACE unit:

                 interface to subroutine SP10[C](x,y,n)
                 integer*2 x,y,n
                 end

                 This INTERFACE unit instructs the FORTRAN compiler to
                 generate code that calls the subroutine _sp10() using C's
                 parameter-passing protocol. However, this technique does
                 not affect the memory model used; the C-callable routine
                 is called with a far call, because it lies in a different
                 segment from the FORTRAN caller. Thus, _sp10() must still
                 be declared with the far keyword, and the stack frame must
                 be addressed with the assumption that a 32-bit far return
                 address lies on top of the stack when the procedure is
                 called.

                 If you intend to write graphics routines that can be
                 called from either Microsoft C, Pascal, or FORTRAN, you
                 should use a medium or large memory model, so the routine
                 can be called as a far procedure. You can use any
                 parameter-passing protocol; the Microsoft language
                 translators can generate code for all of them.


     Turbo Pascal
     Turbo Pascal links EXTERNAL assembly-language subroutines dynamically.
     However, Turbo Pascal's dynamic linker does not perform address
     relocation or resolve symbolic references between the main program and
     the subroutine. Thus, the assembly-language subroutine has a very
     simple structure. Listing 13-3a is an example of this type of
     subroutine. Note how the subroutine performs "self-relocation" by
     initializing a register with the starting offset of the subroutine
     (using a CALL L01 followed by a POP), then adding this value to all
     references to labels within the subroutine.


     BASIC
     IBM BASICA and Microsoft GWBASIC have their own intrinsic video output
     routines. However, you can use assembly-language subroutines to
     customize your BASIC programs for video modes or hardware not
     supported by these BASIC interpreters. Listings 13-4a and 13-4b
     show how to do this.

     Like Turbo Pascal, BASICA requires you to link your subroutine
     dynamically. In Listing 13-4a, the subroutine is assembled in the
     form of a binary file that can be loaded with the BASIC BLOAD command,
     as in lines 220-250 of Listing 13-4b. In BASICA, as in Pascal,
     parameters are passed to the subroutine in the order they are
     specified in the high-level source code. Unlike the Turbo Pascal
     subroutine, however, the BASIC subroutine is a far procedure. Also, in
     BASIC the addresses of parameters are passed instead of the values of
     the parameters themselves.


Interrupts to a Memory-Resident Driver

     Another way to implement the interface between high-level-language
     programs and machine-language graphics routines is to make the
     graphics routines resident in memory. When they are, programs can
     access the graphics routines by executing a software interrupt. This
     is the design of the interface used by all video BIOS routines in the
     PC and PS/2 families. The routines reside at a fixed address in ROM.
     Interrupt vector 10H is initialized at bootup to point to a service
     routine that calls the BIOS routines.

     Your own video output routines can be accessed in a similar manner if
     you make them resident in RAM and set an interrupt vector to point to
     them. (On the PC and PS/2s, interrupt numbers 60H through 67H are
     reserved for such user-defined interrupts.) Listing 13-5 is an
     example of a simple RAM-resident routine that stores pixels in the
     EGA's 640-by-350 16-color mode. The source for this routine assembles
     to a .EXE file that installs the routine in RAM and sets interrupt
     vector 60H to point to the code that sets the pixel value. After the
     interrupt vector is initialized, any program can access the routine by
     loading the CPU registers with the pixel location and value and then
     executing interrupt 60H.


���������������������������������������������������������������������������

     Listing 13-5.  A RAM-resident routine to write pixels in 640-by-350
     graphics mode.

���������������������������������������������������������������������������


Inline Code

     A technique familiar to many C, Modula-2, and Turbo Pascal programmers
     is to implement low-level subroutines as inline machine instructions
     in high-level source code. Doing so can simplify the problem of using
     consistent memory-model and parameter-passing protocols, because the
     high-level-language compiler handles these implicitly. However, inline
     code is rarely portable and can be difficult to adapt for use with
     other languages.


Global Data Areas


     When you link video output subroutines to a high-level program, you
     face the problem of transferring information about the current state
     of the video hardware between the high-level program and the
     subroutines. Although you can pass such information to subroutines
     using argument lists, a better approach is to use a global data
     structure that both the high-level program and the low-level
     subroutines can access. Information contained in a global data area
     can include:

     �  Hardware identification ("EGA with 350-line color display")

     �  Hardware coordinate system (orientation of x- and y-axes,
        maximum x- and y-coordinates)

     �  Video buffer status, including video mode, buffer dimensions
        (maximum x- and y-coordinates), and currently displayed
        portion of the buffer

     �  Foreground and background pixel values for text and graphics
        output

     �  Color values for palette registers

     �  Current pixel operation (replace, XOR, AND, OR, or NOT)

     �  Current region fill pattern

     �  Current line-drawing style (thick or thin line, dashed or broken
        line)

     In many applications it is better to maintain several global areas
     instead of just one. Because almost all PC and PS/2 video hardware
     supports more than one display mode, you can create a separate global
     data block for each mode and make an entire block "current" when you
     select a video mode. In a windowing environment, a block of global
     data can apply to each displayable window. In addition to the above
     information, such a block can also describe the way graphics images
     and text are mapped into a window. This can include clipping
     boundaries, vertical and horizontal scaling, or window visibility
     (whether a window is on or off the screen, overlapping another window,
     and so on).

     Using a global data area has several advantages. Because both high-
     level and low-level routines can determine output hardware status, you
     can write hardware-independent programs that examine the descriptive
     information in the global data area to determine how to format their
     output. This information is relatively static, so maintaining it in a
     global area helps minimize redundant parameter passing between
     graphics routines. Moreover, global data areas can be used
     contextually: the contents of a global data area can be saved,
     modified transiently, and restored.

       ���ͻ     Of course, the information in a global data area can
       � T �     pertain to output devices other than video adapters and
       � I �     displays. A graphics interface that accommodates printers
       � P �     or plotters can also incorporate information about their
       ���ͼ     status in a global data area.


Layered Graphics Interfaces


     After implementing an interface between your low-level video output
     routines and your high-level program, you may still find that a
     certain amount of high-level source code is concerned with low-level
     hardware-dependent manipulations such as pixel coordinate scaling and
     clipping. You can insulate high-level application code from
     considerations about hardware capabilities by creating one or more
     intermediate layers of functionality between the high-level
     application and the hardware drivers.

     A simple layered graphics interface is depicted schematically in
     Figure 13-1. The bottom layer comprises a set of hardware driver
     routines like the ones in this book. The top layer provides a set of
     subroutines that can be called by a high-level application.The
     routines in the top layer may call the hardware drivers in the bottom
     layer directly, or there may be one or more intermediate binding
     layers interposed between the high-level routines and the hardware
     drivers. In any case, the top-level subroutines present a consistent,
     hardware-independent software interface to the programmer who uses a
     high-level language, and thereby insulate high-level programs from the
     vagaries of video hardware programming.

     The ROM video BIOS provides an example of this sort of layering. The
     set of routines that you invoke by issuing INT 10H serves as an
     intermediate layer between assembly-language applications and the low-
     level routines that actually program the hardware. From the
     application's point of view, the INT 10H interface is relatively
     hardware-independent; the video BIOS programs the graphics controller,
     updates the video buffer, and performs many other hardware-dependent
     programming tasks. Because the video BIOS routines contain the
     hardware-dependent code, a program that uses the BIOS is to some
     extent portable to different types of video hardware.

     You can, of course, build many more functions into a layered interface
     than the video BIOS provides. For example, commercially available
     video graphics interfaces can produce sophisticated graphics and
     perform video control functions, including geometric transformations
     (scaling, translation, rotation of graphics images), three-dimensional
     graphics (hidden-line removal, three-dimensional surface represen-
     tation), or sophisticated color mixing and shading. Such graphics
     packages can support output to printers or plotters, as well as to
     video displays. In this case, the layered interface provides a set of
     routines and data structures that allow a high-level program to
     determine the status of an output device and  to select appropriate
     output attributes (line style, drawing color, and so on) on each
     device.


     �����������������������������Ŀ
     �High-level-language interface� "Top layer"
     �  (independent of hardware)  �
     ������������������������������
                    �
                    �
                    �Language binding
                    �
                    �
     ����������������������������Ŀ
     �        Device driver        � "Bottom layer"
     �    (hardware-dependent)     �
     �������������������������������

     Figure 13-1.  A simple layered graphics interface.


     In an operating environment that relies heavily on a graphics-oriented
     video interface, access to operating system functions can be combined
     with video output routines in a high-level application program
     interface (API). This is the approach taken in Apple's Macintosh and
     in Microsoft Windows. In both these environments, support for system
     functions like windows, pull-down menus, and icons is integrated into
     a unified, graphics-oriented API.

     Most layered graphics interfaces comprise more than one intermediate
     layer. Furthermore, each layer can be broken into several independent
     modules. The desire to preserve software portability, particularly as
     existing software is adapted to new video hardware, is the main reason
     for this. Many PC graphics programs are designed so that the end-user
     can customize the hardware-dependent layer(s) to a particular hardware
     configuration. This is a great convenience for the user, since
     adapting a program with a layered video interface to a newly acquired
     piece of hardware is no more difficult than installing a new device
     driver or relinking the program with a new subroutine library.

     The price you pay for this flexibility is a certain amount of extra
     code needed to support the layered interface, so programs run somewhat
     slower. You must consider this trade-off whenever you write an
     application that relies on video display output. Although the benefits
     of using a layered graphics interface are great, many applications are
     simpler to develop and run faster when you dispense with the formal
     graphics interface and use only the necessary low-level drivers.

     To get an idea of the type of programming required when you use a
     layered graphics interface, consider how you might draw a filled
     rectangle in a video graphics mode. The following examples show how
     you could do this using one  of the routines developed earlier in this
     book and using two different layered graphics interfaces. As you
     compare the source code and the programming technique in each of the
     following examples, you will see where the advantages and
     disadvantages of each graphics interface might lie.


Direct Hardware Programming

     The routine in Listing 13-6 draws a filled rectangle directly, by
     computing the endpoints of the set of adjacent line segments that make
     up the rectangle and using a horizontal line-drawing routine to update
     the video buffer. Strictly speaking, this routine could be written
     entirely in assembly language by adapting one of the line-drawing
     routines from Chapter 6. The high-level routine in Listing 13-6 runs
     nearly as fast, however, since most of the time is spent drawing the
     lines, not computing their endpoints.


���������������������������������������������������������������������������

     Listing 13-6.  Using C to draw a filled rectangle.

���������������������������������������������������������������������������


     If raw speed is the major constraint on your program, this is the best
     way to draw a rectangle. The code, however, is relatively nonportable,
     because it makes implicit assumptions about such hardware-dependent
     constraints as the (x,y) coordinate system and color capabilities of
     the video subsystem. You could not use a routine such as the one in
     Listing 13-6 in a multitasking or windowing operating environment,
     because it programs the video hardware directly and could therefore
     inadvertently corrupt video output from a concurrently executing
     program.


Extended BIOS Interface

     As mentioned previously, the video ROM BIOS provides a certain amount
     of hardware independence and portability through the interrupt 10H
     interface. The tradeoff, of course, is speed and a certain amount of
     flexibility. Apart from inefficient implementations, the INT 10H
     routines are relatively unstructured and limited in their
     capabilities. As IBM video subsystems have become more complex,
     additional functionality has been grafted onto the INT 10H interface,
     making it more powerful but increasingly difficult to master.

     Direct Graphics Interface Standard (DGIS) is a firmware interface
     developed by Graphics Software Systems that extends the capabilities
     of the INT 10H interface in a structured manner. DGIS was designed to
     provide a uniform low-level interface to video hardware based on
     graphics coprocessors such as the Intel 82786 or the Texas Instruments
     TMS34010. Programming with DGIS is reminiscent of programming with
     IBM's video BIOS, but many elements of a high-level graphics interface
     have also been incorporated into DGIS.

     DGIS implements a hardware-independent interface by describing actual
     video subsystems, or devices, in terms of their possible display
     modes, or configurations. An application can interrogate DGIS to
     determine what devices are supported in the computer. It then selects
     a subsequent video output configuration, based on the configuration's
     resolution, number of colors, graphics and/or alphanumeric text
     support, and so on.

     For example, Listing 13-7 calls DGIS to draw the same filled
     rectangle as before. This time, however, instead of programming the
     hardware, the source code is concerned primarily with programming the
     interface. The routine first establishes the presence of a suitable
     graphics output device in the computer by calling the DGIS Inquire
     Available Devices function. This function returns a list of available
     DGIS devices; in a system with an EGA, for example, the configurations
     associated with the "EGA" device correspond to the EGA's video modes.


���������������������������������������������������������������������������

     Listing 13-7.  Using DGIS to draw a filled rectangle.

���������������������������������������������������������������������������


     The application program "connects" to an appropriate configuration,
     which DGIS identifies with a handle. The application can then
     associate an attribute context with the handle; the attribute context
     is a data structure that defines drawing colors, line styles, clipping
     boundaries, and so on. Subsequent calls to DGIS graphics output
     functions like OutputFilledRectangle refer to the attribute context
     associated with a specified handle.

     This general sequence of operations is inherently flexible. One reason
     is that it lets an application program access hardware features
     without actually programming the hardware. For example, an application
     can use DGIS functions to change a color palette or update pixels
     without writing directly to hardware control registers or to the video
     buffer.

     However, an application that performs video output through a DGIS
     interface runs slower than an equivalent application that programs the
     video hardware directly. As always, when you interpose a layer of
     functionality between your application and the hardware, you gain
     increased functionality and portability at the price of a decrease in
     speed. You must decide whether this trade-off is worthwhile in your
     own applications.


High-Level Interface

     There are several high-level graphics interface implementations
     available for IBM video subsystems. These high-level interfaces differ
     from DGIS and the IBM video BIOS in that they are implemented as
     software libraries or RAM-loadable device drivers instead of firmware
     routines. All of them relieve you of the need to program the hardware
     directly, and all provide a structured programming interface that can
     be used in a program written in a high-level language.

     The differences between the high-level graphics interfaces lie in the
     amount and type of functionality built into them. For example, the
     Virtual Device Interface (VDI) is a proposed ANSI standard designed to
     promote hardware independence in programs written in high-level
     languages. VDI presents a consistent programming interface to all
     graphics output hardware, including video subsystems, printers, and
     plotters. (The Graphics Development Toolkit sold by Graphics Software
     Systems and IBM support VDI.)

     Another well-known interface is the Graphical Kernel System (GKS), an
     internationally recognized ANSI standard. GKS offers a highly
     structured interface with powerful graphics data manipulation
     features. GKS deals not with individual hardware devices but with
     workstations that can include several related input and output devices
     (such as a display, a keyboard, and a mouse). A GKS implementation can
     be layered above a lower-level interface like VDI; an application can
     then use either interface without sacrificing functionality or
     portability.

     Still another type of high-level interface integrates graphics output
     with the computer's operating environment, as does the Graphics Device
     Interface (GDI) in Microsoft Windows. In contrast to DGIS, which is
     designed to be a low-level interface to display hardware, GDI serves
     as a high-level interface to Windows' graphics-oriented operating
     environment. In a layered graphics interface, GDI would be closer to
     the topmost layer while an interface like DGIS would be near the
     bottom. In fact, you can install Windows to run on top of DGIS; a
     Windows application can then use GDI functions which in turn call DGIS
     functions to access the hardware (Figure 13-2).

     The C source code fragment in Listing 13-8 merely scratches the
     surface of GDI programming in Windows, but it should give you an idea
     of how the video interface is structured. Most of the code in the
     example establishes a device context for the Rectangle() function to
     use. In GDI, a device context is a global data structure that contains
     information on the colors with which text and graphics are drawn, as
     well as scaling factors for pixel (x,y) coordinates, clipping
     boundaries, and other information. Windows maintains a device context
     for each window on the screen. Each device context is identified by a
     16-bit handle. When an application calls a GDI output function like
     Rectangle() or Ellipse(), it passes the handle of a device context to
     the function; the function then refers to the information in the
     device context to produce output in a window.

     To produce graphics output in a window, a Windows application starts
     by calling the Windows function CreateWindow(), which returns a handle
     (hWnd) that identifies the window. The application then monitors
     Windows' applicaiton message queue to determine when to update the
     window.

     To generate output to the window, the application can use another
     Windows function, BeginPaint(), to associate a device context
     (identified with the handle hDC) with the window. The application then
     uses GDI functions to establish drawing attributes and pixel
     coordinate mapping in the device context. In the example in Listing
     13-8, the attributes of the rectangle's border (line style and color)
     are specified by creating a data structure that becomes part of the
     device context.


     ��������������������Ŀ
     �    Application     �
     ���������������������
               �
     �������������������Ŀ
     �        GDI         �
     ���������������������
               �
     �������������������Ŀ
     �        DGIS        �
     ���������������������
               �
     �������������������Ŀ
     �      Hardware      �
     ����������������������

     Figure 13-2.  Microsoft Windows GDI installed on DGIS.


���������������������������������������������������������������������������

     Listing 13-8.  Using Microsoft Windows GDI (version 1.03) to draw a
     filled rectangle.

���������������������������������������������������������������������������


     The function CreatePen() creates the data structure and returns an
     identifying handle that is assigned to the variable hpen. The
     function SelectObject() then updates the device context with this
     information. Similarly, calls to CreateSolidBrush() and SelectObject()
     establish the color and pattern used to fill the rectangle.

     When Rectangle() executes, it uses the "pen" and "brush" attributes in
     the device context to draw the rectangle's border and interior. The
     (x,y) coordinates specified in the call to Rectangle() indicate the
     rectangle's upper left and lower right corners. The coordinates do not
     indicate absolute pixel locations in the video buffer; they specify
     points in the coordinate system that relates to the window in which
     the rectangle is displayed.

     GDI's general design is similar to that of other high-level graphics
     interfaces--the hardware-dependent, machine-language routines are
     isolated in the lowest layer of the interface, and portable, hardware-
     independent functions are implemented in the interface's upper layers.
     The differences among GDI, VDI, and other high-level graphics
     interfaces lie not so much in implementation details as in the types
     and complexity of the graphics functions they can perform.



                      Appendix A  Video BIOS Summary



     All computers in the IBM PC and PS/2 family have a BIOS (Basic
     Input/Output System) in ROM. The ROM BIOS contains a set of assembly-
     language routines that provide a low-level programming interface for
     accessing various hardware features, including disk drives, the system
     timer, serial I/O ports, a parallel printer, and, of course, the video
     hardware. By building a video BIOS in ROM into every machine, IBM has
     attempted to provide a common software interface for the various
     machines, despite substantial hardware differences among the IBM PC,
     the PC/XT, the PC/AT, and the PS/2s.

     To a large extent, this endeavor succeeded. Transporting programs
     between IBM PCs with different hardware tends to be easier when the
     programs access the hardware only by calling ROM BIOS routines. This
     is particularly true of programs that manipulate the video display.
     When you consider the many video display configurations available, you
     might regard the BIOS as a sort of "lowest common denominator" for the
     software developer.

     Still, you might not always choose to use ROM BIOS routines for video
     output for several reasons. For one, ROM BIOS video support routines
     are not very fast. When performance is critical, you probably will not
     use them. The speed of the routines is rarely important for tasks
     performed infrequently, such as loading a character set into RAM or
     changing a video display mode. On the other hand, in displaying
     graphics images or producing animation effects, using the BIOS can
     substantially decrease performance.

     Many other tasks are better performed by your operating system rather
     than the BIOS. For example, when you call the BIOS to write characters
     to the screen, you bypass any operating system processing of those
     characters. The BIOS routines know nothing about input/output
     redirection, windowing, or other functions the operating system
     provides.

     Clearly, the video ROM BIOS is essential to IBM PC video programming,
     but the extent to which your programs use it is a matter for your
     judgment.


Hardware Supported by ROM Video BIOS


MDA and CGA

     The ROM BIOS on the motherboard of every IBM/PC, PC/XT, and PC/AT
     supports both the MDA and the CGA. Also, the PS/2 Model 30's video
     BIOS supports an MDA, in addition to its integrated MCGA. When you
     power a PC on, the vector for interrupt 10H is initialized to point to
     the video service routine in ROM.

     IBM's technical documentation frequently refers to the motherboard ROM
     BIOS  in the PCs and PS/2s as the "planar" BIOS. The planar BIOS
     routines start at F000:E000 in the CPU's address space.


EGA

     IBM's EGA contains its own set of video drivers in ROM, located at
     C000:0000. The EGA's cold boot routines initialize interrupt 10H to
     point to its service routine in the EGA ROM BIOS. The EGA BIOS uses
     the interrupt 42H vector to point to the motherboard video service
     routine. Because the EGA's interrupt 10H routines access the
     motherboard BIOS routines whenever necessary through INT 42H, you
     rarely need to execute this interrupt explicitly.


MCGA

     The video ROM BIOS in the PS/2 Models 25 and 30 supports the
     integrated MCGA subsystem in these computers. The Model 30's ROM BIOS
     supports the concurrent use of an MDA, but a CGA cannot be used in the
     same machine because its I/O port assignments and video memory usage
     conflict with those of the MCGA.


VGA

     Video ROM routines in the PS/2 Models 50, 60, and 80, starting at
     E000:0000, support the VGA exclusively. The other video adapters
     described in this book cannot be installed in these computers because
     they are incompatible with the PS/2 MicroChannel bus.


VGA Adapter

     The VGA Adapter's video ROM BIOS routines start at C000:0000. The BIOS
     routines on the VGA Adapter are the same as those in the PS/2 Model
     50, 60, and 80 video BIOS, except for minor differences related to the
     different hardware implementations of the adapter and the integrated
     VGA subsystem.


Interrupt 10H


     The BIOS video routines are written in assembly language and accessed
     by performing 80x86 interrupt 10H. The INT 10H interface is designed
     for assembly-language programs, but you can call the BIOS routines
     directly from programs written in languages such as C or Pascal if
     your language compiler provides a way to execute the interrupt.

     You select a BIOS video support routine by loading a function number
     into register AH. To pass parameters to the BIOS routine, place their
     values in the 80x86 registers before executing INT 10H. Values that
     the BIOS routines return to your program are left in registers as
     well.

     The IBM PC motherboard BIOS routines explicitly preserve the contents
     of registers DS, ES, BX, CX, DX, SI, and DI (unless they are used for
     parameter passing). The EGA, MCGA, and VGA BIOS routines also preserve
     register BP.

       ���ͻ     If you are using the IBM PC or PC/XT planar BIOS, preserve
       � T �     register BP across INT 10H calls to the BIOS. For example:
       � I �           push bp                ; preserve BP
       � P �           int  10h               ; call the BIOS
       ���ͼ           pop  bp                ; restore BP


     As a rule, BIOS video input/output routines do not validate data, nor
     do they return status codes or error flags. Thus, your programs should
     never attempt to access an invalid video buffer address, select a
     video page in a video mode that does not support them, or access
     hardware not installed in your system. The BIOS routines do not
     reliably detect any of these errors.


Video BIOS Data Areas


Video Display Data Area

     The BIOS routines maintain several dynamic variables in an area of
     memory called the Video Display Data Area. Figure A-1 contains a
     summary of these variables' addresses, their symbolic names, and their
     contents.


����������������������������������������������������������������������������ķ
Address             Name           Type      Description
��������������������������������������������������������������������������
0040:0049           CRT_MODE       Byte      Current BIOS video mode number
0040:004A           CRT_COLS       Word      Number of displayed character
                                               columns
0040:004C           CRT_LEN        Word      Size of video buffer in bytes
0040:004E           CRT_START      Word      Offset of start of video
                                               buffer
0040:0050           CURSOR_POSN    Word      Array of eight words
                                               containing the cursor
                                               position for each of eight
                                               possible video pages. The
                                               high-order byte of each word
Address             Name           Type      Description
                                               high-order byte of each word
                                               contains the character row,
                                               the low-order byte the
                                               character column.
0040:0060           CURSOR_MODE    Word      Starting and ending lines for
                                               alphanumeric cursor. The
                                               high-order byte contains the
                                               starting (top) line; the
                                               low- order byte contains the
                                               ending (bottom) line.
0040:0062           ACTIVE_PAGE    Byte      Currently displayed video page
                                               number
0040:0063           ADDR_6845      Word      I/O port address of CRT
                                               Controller's Address
                                               register (3B4H for
                                               monochrome, 3D4H for color).
0040:0065           CRT_MODE_SE    T         Byte Current value for Mode
                                               Control register (3B8H on
                                               MDA, 3D8H on CGA). On the
                                               EGA and VGA, the value
Address             Name           Type      Description
                                               EGA and VGA, the value
                                               emulates those used on the
                                               MDA and CGA.
0040:0066           CRT_PALETTE    Byte      Current value for the CGA
                                               Color Select register
                                               (3D9H). On the EGA and VGA,
                                               the value emulates those
                                               used on the MDA and CGA.
0040:0084           ROWS           Byte      Number of displayed character
                                               rows - 1
0040:0085           POINTS         Word      Height of character matrix
0040:0087           INFO           Byte      (See Figure A-1a)
0040:0088           INFO_3         Byte      (See Figure A-1b)
0040:0089           Flags          Byte      Miscellaneous flags (see
                                               Figure A-1c)
0040:008A           DCC            Byte      Display Combination Code table
                                               index
0040:00A8           SAVE_PTR       Dword     Pointer to BIOS Save Area (see
                                               Figure A-3)

     Figure A-1.  BIOS Video Display Data Area.


����������������������������������������������������������������������������ķ
Bit       Description
��������������������������������������������������������������������������
7         Reflects bit 7 of video mode number passed to INT 10H
          function 0
6-5       Amount of video RAM:
                     00b - 64K
                     01b - 128K
                     10b - 192K
                     11b - 256K
4         (reserved)
3         1 - video subsystem is inactive
2         (reserved)
1         1 - video subsystem is attached to monochrome display
0         1 - alphanumeric cursor emulation is enabled

     Figure A-1a.  Mapping of INFO byte at 0040:0087 in the EGA and VGA
     Video Display Data Area.


����������������������������������������������������������������������������ķ
Bit       Description
��������������������������������������������������������������������������
7         Input from feature connector on FEAT1 (bit 6 of Input Status
            register 0) in response to output on FC1 (bit 1 of Feature
            Control register)
6         Input from feature connector on FEAT0 (bit 5 of Input Status
            register 0) in response to output on FC1 (bit 1 of Feature
            Control register)
5         Input from feature connector on FEAT1 (bit 6 of Input Status
            register 0) in response to output on FC0 (bit 0 of Feature
            Control register)
4         Input from feature connector on FEAT0 (bit 5 of Input Status
            register 0) in response to output on FC0 (bit 0 of Feature
            Control register)
3         Configuration switch 4 (1 - off, 0 - on)
2         Configuration switch 3 (1 - off, 0 - on)
1         Configuration switch 2 (1 - off, 0 - on)
0         Configuration switch 1 (1 - off, 0 - on)
Bit       Description
0         Configuration switch 1 (1 - off, 0 - on)

     Figure A-1b.  Mapping of INFO_3 byte at 0040:0088 in the EGA and VGA
     Video Display Data Area. Bits 4 through 7 reflect the power-on status
     of the feature connector. Bits 0 through 3 reflect the settings of the
     four EGA configuration switches. (The switch values are emulated by
     the VGA BIOS, depending on the type of display attached.)


����������������������������������������������������������������������������ķ
Bit       Description
��������������������������������������������������������������������������
7         Alphanumeric scan lines (with bit 4):
          bit 7   bit 4
          0       0       350-line mode
          0       1       400-line mode
          1       0       200-line mode
          1       1       (reserved)
6         1 - display switching is enabled
          0 - display switching is disabled
Bit       Description
          0 - display switching is disabled
5         (reserved)
4         (see bit 7)
3         1 - default palette loading is disabled
          0 - default palette loading is enabled
2         1 - using monochrome monitor
          0 - using color monitor
1         1 - gray scale summing is enabled
          0 - gray scale summing is disabled
0         1 - VGA active
          0 - VGA not active

     Figure A-1c.  Mapping of Flags byte at 0040:0089 in MCGA and VGA Video
     Display Data Area.


     Video BIOS routines update the values in the Video Display Data Area
     to reflect the status of the video subsystem. If you alter the video
     environment without invoking an INT 10H routine, be sure you update
     the relevant variables in the Video Display Data Area. Failing to do
     so can cause the BIOS video routines to malfunction.


Save Areas

     The ROM BIOS routines on the EGA, the MCGA, and the VGA support a set
     of save areas, which are dynamic tables of video hardware and BIOS
     information. The video BIOS can use these save areas to supplement the
     Video Display Data Area. You can also use them to override the usual
     video BIOS defaults for character sets, palette programming, and other
     configuration functions.

     The video BIOS save areas are linked by a set of doubleword
     (segment:offset) pointers (see Figure A-2). Use the variable SAVE_PTR
     (at 0040:00A8 in the Video Display Data Area) to locate the save
     areas. SAVE_PTR contains the address of the SAVE POINTER table (see
     Figure A-3). This table contains addresses of as many as seven data
     structures, each with a different format and a different set of data
     pertaining to operation of the video hardware or of the video BIOS
     routines.

     The fifth address in the SAVE POINTER table is that of the SECONDARY
     SAVE POINTER table (see Figure A-4), which only the VGA's BIOS uses.
     This table also contains the addresses of several data structures with
     contents relating to the functioning of the video hardware and the
     BIOS.


     ��������������������Ŀ
     �        PTR         �
     ����������������������
               
     ��������������������Ŀ
     �                    ������Video Parameter table
     � SAVE POINTER Table ������Parameter Save Area
     �                    ������Alphanumeric Character Set Override
     �                    ������Graphics Character Set Override
     �                    �����Ŀ
     ����������������������     �
               ������������������
               
     ��������������������Ŀ
     �   SECONDARY SAVE   ������Display Combination Code
     �   POINTER Table    ������2nd Alphanumeric Character Set Override
     �     (VGA only)     ������User Palette Profile Table
     ����������������������

     Figure A-2.  Video BIOS Save Areas.


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         Dword            Address of Video Parameter table
4         Dword            Address of Parameter Save Area (EGA, VGA only)
8         Dword            Address of Alphanumeric Character Set Override
0CH       Dword            Address of Graphics Character Set Override
10H       Dword            Address of SECONDARY SAVE POINTER table
                             (VGA only)
14H       Dword            (reserved)
18H       Dword            (reserved)

     Figure A-3.  SAVE POINTER table (EGA, MCGA, VGA).


����������������������������������������������������������������������������ķ
Offset      Type           Description
��������������������������������������������������������������������������
0           Word           Length of SECONDARY SAVE POINTER table in
                             bytes
2           Dword          Address of Display Combination Code table
6           Dword          Address of second Alphanumeric Character
                             Set Override
0AH         Dword          Address of User Palette Profile table
0EH         Dword          (reserved)
12H         Dword          (reserved)
16H         Dword          (reserved)

     Figure A-4.  SECONDARY SAVE POINTER table (VGA only).


     Apart from the SAVE POINTER and SECONDARY SAVE POINTER tables, the
     only data structures provided in the ROM BIOS are the Video Parameter
     table and, on the VGA, the Display Combination Code table. Thus, the
     only initialized addresses in the SAVE POINTER table are those of the
     Video Parameter table and of the SECONDARY SAVE POINTER table. The
     only initialized address in the SECONDARY SAVE POINTER table belongs
     to the Display Combination Code table. Remaining addresses are
     initialized to 0.


Video Parameter Table

     This data structure contains configuration parameters that the video
     BIOS video mode set routines use. The table contains entries for each
     available video mode. Its structure differs on the various models of
     the EGA, the MCGA, and the VGA. Figure A-5 is a typical entry in the
     VGA Video Parameter table. Formats for table entries in the EGA and
     MCGA BIOS are similar.


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         Byte             Value for CRT_COLS
Offset    Type             Description
0         Byte             Value for CRT_COLS
1         Byte             Value for ROWS
2         Byte             Value for POINTS
3         Word             Value for CRT_LEN
5         4-byte array     Values for Sequencer registers 1-4
9         Byte             Value for Miscellaneous Output register
0AH       25-byte array    Values for CRTC registers 0-18H
23H       20-byte array    Values for Attribute Controller registers 0-13H
37H       9-byte array     Values for Graphics Controller registers 0-8

     Figure A-5.  Format of a VGA Video Parameter table entry. The VGA
     Video Parameter table comprises 29 such entries.


Parameter Save Area

     When present, this table contains the values of the EGA or the VGA
     Graphics Controller palette registers (00H through 0FH) and the
     Overscan register (11H), as shown in Figure A-6. The video BIOS
     updates the Parameter Save Area whenever it updates the corresponding
     Attribute Controller registers.


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         16-byte array    Current contents of Graphics Controller
                             Palette registers
10H       Byte             Current contents of Graphics Controller
                             Overscan register
11H-0FFH  (reserved)

     Figure A-6.  Parameter Save Area. This area is 256 bytes in size.


       ���ͻ     When a User Palette Profile (see Figure A-10 later in
       � T �     this discussion) overrides the default palette register
       � I �     values, the Parameter Save Area is updated with default
       � P �     values, not those in the User Palette Profile.
       ���ͼ


Alphanumeric Character Set Override

     This data structure (see Figure A-7) indicates an alphanumeric
     character set to be used instead of the BIOS default character set.
     The character set is loaded whenever the video BIOS is called to
     select one of the video modes that the data structure specifies.


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         Byte             Length in bytes of each character definition
1         Byte             Character generator RAM bank
2         Word             Number of characters defined
4         Word             First character code in table
6         Dword            Address of character definition table
0AH       Byte             Number of displayed character rows
0BH       Byte array       Applicable video modes
          Byte             0FFH (end of list of video modes)

     Figure A-7.  Alphanumeric Character Set Override.


     On the VGA, you can specify a second 256-character set by creating a
     second Alphanumeric Character Set Override data structure and storing
     its address in the SECONDARY SAVE POINTER table.


Graphics Character Set Override

     This data structure (see Figure A-8) overrides the default BIOS
     character set selection whenever the video BIOS sets up one of the
     specified video modes.


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         Byte             Number of displayed character rows
1         Word             Length in bytes of each character definition
3         Dword            Address of character definition table
Offset    Type             Description
3         Dword            Address of character definition table
7         Byte array       Applicable video modes
          Byte             0FFH (end of list of video modes)

     Figure A-8.  Graphics Character Set Override.


Display Combination Code Table

     Figure A-9 lists all combinations of video subsystems that the video
     BIOS supports. The description of INT 10H function 1AH in this
     appendix explains how this table is used.

       ���ͻ     The MCGA video BIOS contains a Display Combination Code
       � T �     table in ROM to support INT 10H function 1AH. However, the
       � I �     MCGA BIOS does not support a SECONDARY SAVE POINTER table,
       � P �     so you can't modify its DCC table.
       ���ͼ


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         Byte             Number of entries in table
1         Byte             DCC table version number
2         Byte             Maximum display type code
3         Byte             (reserved)
4         Word array       Each pair of bytes in the array describes a
                             valid display combination (see INT 10H
                             function 1AH)

     Figure A-9.  Display Combination Code table.


User Palette Profile Table

     This data structure contains user-specified overrides for the default
     Attribute Controller Palette and Overscan register values, for the
     default values in the 256 video DAC color registers, and for the
     default value in the CRTC Underline Location register (see Figure A-
     10). Only the VGA video BIOS supports this table.


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         Byte             Underlining:  1 - Enable in all alphanumeric
                                               modes
                                         0 - Enable in monochrome
                                               alphanumeric mode
                                        -1 - Disable in all alphanumeric
                                               modes
1         Byte             (reserved)
2         Word             (reserved)
4         Word             Number of Attribute Controller registers in
                             table
6         Word             First Attribute Controller register number
8         Dword            Address of Attribute Controller register
                             table
0CH       Word             Number of video DAC Color registers in
                             table
0EH       Word             First video DAC Color register number
Offset    Type             Description
0EH       Word             First video DAC Color register number
10H       Dword            Address of video DAC Color register table
14H       Byte array       Applicable video modes
          Byte             0FFH (end of list of video modes)

     Figure A-10.  User Palette Profile table.


Video BIOS Save Area Programming

     To use a data structure supported in the SAVE POINTER and SECONDARY
     SAVE POINTER tables, place the data structure in RAM and update the
     appropriate SAVE POINTER or SECONDARY SAVE POINTER addresses to point
     to it. Because the default SAVE POINTER and SECONDARY SAVE POINTER
     tables are located in ROM, you must copy these tables to RAM and
     update SAVE_PTR (0040:00A8) appropriately before you can modify them.

     Listings A-1 and A-2 demonstrate two uses of the video BIOS save
     areas. The routine in Listing A-1 provides a parameter save area for
     the EGA or VGA BIOS. Once the parameter save area is established, its
     first 17 bytes are updated with the contents of the Attribute
     Controller's 16 palette registers and its Overscan register each time
     the video BIOS writes to them.


���������������������������������������������������������������������������

     Listing A-1.  Using a Parameter Save Area to keep track of EGA or VGA
     palette registers.

���������������������������������������������������������������������������


     Listing A-2 shows how to specify the palette values to be used when
     the video BIOS routines are invoked to establish a new video mode.
     First, place the values in a table whose address is stored in a User
     Palette Profile data structure. Then place the address of this data
     structure in the SECONDARY SAVE POINTER table. (Since this example
     uses the SECONDARY SAVE POINTER table, you can run it only on the
     VGA.)


���������������������������������������������������������������������������

     Listing A-2.  Using a User Palette Profile to override the
     default VGA palette.

���������������������������������������������������������������������������


       ���ͻ     Generally, your application should restore SAVE_PTR to its
       � T �     original value when the SAVE POINTER tables and save areas
       � I �     are no longer needed. If you want to preserve these tables
       � P �     in RAM for use by subsequent applications, use the MS-DOS
       ���ͼ     "Terminate-but-Stay-Resident" function (INT 21H function
                 31H) so that the RAM containing the tables is not freed
                 when the program that creates them terminates.


Interrupt 1DH Vector

     This interrupt vector contains the address of a table of video
     initialization values (see Figure A-11). These values are useful only
     for the MDA and the CGA; however, the table is maintained for
     compatibility among all PCs and PS/2s.


����������������������������������������������������������������������������ķ
Offset    Type             Description
��������������������������������������������������������������������������
0         16-byte array    CRTC registers for 40-by-25 alphanumeric
                             mode (CGA)
10H       16-byte array    CRTC registers for 80-by-25 alphanumeric
                             mode (CGA)
20H       16-byte array    CRTC registers for 320-by-200 4-color or
                             640-by-200 2-color graphics modes (CGA)
30H       16-byte array    CRTC registers for 80-by-25 monochrome
                             (MDA)
40H       Word             Video buffer length (40-by-25 alphanumeric
                             mode)
42H       Word             Video buffer length (80-by-25 alphanumeric
                             mode)
44H       Word             Video buffer length (CGA graphics modes)
Offset    Type             Description
44H       Word             Video buffer length (CGA graphics modes)
46H       Word             Video buffer length (CGA graphics modes)
48H       8-byte array     Number of displayed character columns for
                             video BIOS modes 0 through 7
50H       8-byte array     Values for CRT Mode Control register 3x8H
                             for video BIOS modes 0 through 7

     Figure A-11.  MDA and CGA Video Initialization table. This table's
     address is stored in the vector for INT 1DH.


IBM PC and PS/2 Video BIOS Functions (INT 10H Interface)


     The following pages provide detailed descriptions of each BIOS
     function available through software interrupt 10H. The descriptions
     are intended to complement the function summaries and assembly-
     language source code listings in IBM's technical literature. The
     accompanying source code fragments represent typical programming
     examples that you can modify for your own purposes.

     This summary includes information on the ROM BIOS routines found on
     the motherboard, the EGA, the MCGA, and the VGA. However, not all the
     routines are available or function identically on all computers in the
     IBM PC and PS/2 family.

     All information in this chapter is based on IBM technical
     specifications and on the following dated versions of the video ROM:

     �  IBM PC motherboard ROM: 10/27/82

     �  IBM PC/AT motherboard ROM: 6/10/85

     �  IBM EGA ROM: 9/13/84

     �  IBM PS/2 Model 30 (MCGA) ROM: 9/2/86

     �  IBM PS/2 Model 60 (VGA) ROM: 2/13/87

     �  IBM PS/2 (VGA) Display Adapter ROM: 10/27/86



���������������������������������������������������������������������������
Function 0: Select Video Mode

Caller registers:

     AH   =    0
     AL   =    video mode number:
               0      40-by-25 16-color alphanumeric, color burst disabled
               1      40-by-25 16-color alphanumeric, color burst enabled
               2      80-by-25 16-color alphanumeric, color burst disabled
               3      80-by-25 16-color alphanumeric, color burst enabled
               4      320-by-200 4-color graphics, color burst enabled
               5      320-by-200 4-color graphics, color burst disabled
               6      640-by-200 2-color graphics, color burst enabled
               7      80-by-25 monochrome alphanumeric (MDA, Hercules, EGA,
                         and VGA only)
               8      160-by-200 16-color graphics (PCjr only)
               9      320-by-200 16-color graphics (PCjr only)
               0AH    640-by-200 4-color graphics (PCjr only)
               0BH    Reserved (used by EGA BIOS function 11H)
               0CH    Reserved (used by EGA BIOS function 11H)
               0DH    320-by-200 16-color graphics (EGA and VGA only)
               0EH    640-by-200 16-color graphics (EGA and VGA only)
               0FH    640-by-350 monochrome graphics (EGA and VGA only)
               10H    640-by-350 16-color graphics (VGA, EGA with at least
                         128 KB)
                      640-by-350 4-color graphics (64 KB EGA)
               11H    640-by-480 2-color graphics (MCGA, VGA only)
               12H    640-by-480 16-color graphics (VGA only)
               13H    320-by-200 256-color graphics (MCGA and VGA only)

Returned values:

     (none)

Video Display Data Area updates:

     0040:0049  CRT_MODE
     0040:004A  CRT_COLS
     0040:004C  CRT_LEN
     0040:004E  CRT_START
     0040:0050  CURSOR_POSN
     0040:0060  CURSOR_MODE
     0040:0062  ACTIVE_PAGE
     0040:0063  ADDR_6845
     0040:0065  CRT_MODE_SET
     0040:0066  CRT_PALETTE
     0040:0084  ROWS
     0040:0085  POINTS
     0040:0087  INFO
     0040:0088  INFO_3

     INT 10H function 0 puts the video subsystem in the video mode you
     specify with the value in register AL. Function 0 programs the CRT
     Controller, selects a default color palette, and optionally clears the
     video buffer. You can modify several default tasks that function 0
     performs by setting flags in the Video Display Data Area (see INT 10H
     function 12H) or by providing character set or palette attribute
     overrides in BIOS save areas.

     Video mode numbers 0BH and 0CH are reserved for the EGA BIOS support
     routine for RAM-loadable character sets, in which video memory map 2
     is selectively enabled so a table of character definitions can be
     loaded.

     On the EGA, the MCGA, and the VGA, composite video displays are not
     supported, and there is no color burst signal to control. Thus, mode 0
     is the same as mode 1, mode 2 = mode 3, and mode 4 = mode 5.

     If you use this BIOS routine to request a video mode your system
     hardware does not support, the results are unreliable. In particular,
     if you select mode 7 (monochrome alphanumeric) with a CGA, the
     motherboard BIOS programs the CGA's CRT Controller with parameters
     appropriate for an MDA, which results in incomprehensible noise on the
     CGA screen. The third example below shows how to solve this problem by
     setting bits 4 and 5 of EQUIP_FLAG (0040:0010) to indicate which
     subsystem the BIOS is to use.

     On the EGA, the MCGA, and the VGA, if bit 7 of the requested video
     mode number in AL is set to 1, the video buffer is not cleared when
     the new video mode is selected. Thus, a program can alternate between
     two video subsystems without losing the contents of their video
     buffers.

     The following example selects 320-by-200 4-color graphics mode.

        mov  ax,0004        ; AH := 0 (INT 10H function number)
                            ; AL := 4 (video mode number)
        int  10h

     This routine shows how to change modes on the EGA without clearing
     the video buffer.

        mov  ax,000EH       ; select a video mode (in this case,
                            ;  640x200 16-color mode)
        or   al,10000000b   ; set bit 7
        int  10h

     To select video modes in a system containing both a CGA and an MDA,
     use a routine such as the following.

        mov  ax,40h
        mov  es,ax
        and  byte ptr es:[10h],11001111b  ; zero bits 4 and 5 of EQUIP_FLAG
        or   byte ptr es:[10h],00110000b  ; set bits 4 and 5:
                                          ;  11b - monochrome
                                          ;  10b - color (80x25)
                                          ;  01b - color (40x25)
                                          ;  00b - (unused)
        mov  ax,0007
        int  10h                          ; select monochrome mode 7

        and  byte ptr es:[10],11001111b   ; zero those bits
        or   byte ptr es:[10],00100000b   ; bits for 80x25 16-color
        mov  ax,0003
        int  10h                          ; select 80x25 16-color mode 3



���������������������������������������������������������������������������
Function 1: Set Alphanumeric Cursor Size

Caller registers:

     AH   =    1
     CH   =    top line of cursor
     CL   =    bottom line of cursor

Returned values:

     (none)

Video Display Data Area update:

     0040:0060  CURSOR_MODE

     INT 10H function 1 programs the CRT Controller to display the
     specified alphanumeric cursor. It programs the CRT Controller's Cursor
     Start and Cursor End registers so that the alphanumeric cursor appears
     between the specified lines in the character matrix. The contents of
     register CX are copied into CURSOR_MODE.

     If the value in CH is 20H the alphanumeric cursor is disabled.

     On the EGA and the VGA, if bit 0 of the INFO byte (0040:0087) is set
     to 0, the BIOS processes the top and bottom line values passed in CH
     and CL relative to an eight-line character matrix. Chapter 3
     discusses this "cursor emulation" in detail.

     Use INT 10H function 1 only in alphanumeric video modes.

     To select a full-height cursor in video mode 3 (80-by-25 16-color
     alphanumeric mode) on a CGA:

        mov  cx,0007h   ; CH := 0 (top line)
                        ; CL := 7 (bottom line of the 8x8 character matrix)
        mov  ah,1       ; AH := 1 (INT 10H function number)
        int  10h

     On an EGA with a 350-line monitor, video mode 3 is a 350-line
     alphanumeric mode with an 8-by-14 character matrix. Nevertheless, the
     above code normally runs unchanged in this situation, because the BIOS
     "emulates" the corresponding 200-line CGA mode and programs the Cursor
     Start and End registers accordingly.



���������������������������������������������������������������������������
Function 2: Set Cursor Location

Caller registers:

     AH   =    2
     BH   =    video page
     DH   =    character row
     DL   =    character column

Returned values:

     (none)

Video Display Data Area update:

     0040:0050  CURSOR_POSN

     INT 10H function 2 updates the BIOS Video Display Data Area, giving a
     new cursor position. If the value in BH references the currently
     displayed video page, this routine also programs the CRT Controller to
     update the displayed cursor position.

     To set the cursor position to column 10, row 5, in 80-by-25 16-color
     mode:

        mov  ah,2           ; AH := 2 (INT 10H function number)
        mov  bh,1           ; BH := video page
        mov  dh,5           ; DH := row
        mov  dl,10          ; DL := column
        int  10h



���������������������������������������������������������������������������
Function 3: Return Cursor Status

Caller registers:

     AH   =    3
     BH   =    video page number

Returned values:

     CH   =    top line of cursor
     CL   =    bottom line of cursor
     DH   =    character row
     DL   =    character column

Video Display Data Area updates:

     (none)

     INT 10H function 3 returns the character cursor location for the
     specified video page. The character row and column values are copied
     from CURSOR_POSN in the Video Display Data Area.

     The values returned in CH and CL are copied from CURSOR_MODE, also in
     the Video Display Data Area. They are meaningful only in alphanumeric
     modes.

     To determine the current cursor location (and size in an alphanumeric
     mode) in video page 0:

        mov  ah,3           ; AH := 3 (INT 10H function number)
        mov  bh,0           ; BH := 0 (video page)
        int  10h



���������������������������������������������������������������������������
Function 4: Return Light Pen Position

Caller registers:

     AH   =    4

Returned values:

     AH   =    1 if valid light pen position returned
          =    0 if no light pen position returned
     BX   =    pixel x-coordinate
     CH   =    pixel y-coordinate (CGA and EGA video modes 4, 5, and 6)
     CX   =    pixel y-coordinate (EGA except modes 4, 5, and 6)
     DH   =    character row
     DL   =    character column

Video Display Data Area updates:

     (none)

     INT 10H function 4 gets the current position of the light pen from the
     CRT Controller's Light Pen High and Light Pen Low registers.

     If the light pen switch is not set, or if the light pen latch has not
     been triggered (that is, if the CRTC's Light Pen High and Light Pen
     Low registers do not contain a valid light pen address), function 4
     returns 0 in register AH. Otherwise, function 4 sets AH to 1, leaves
     the light pen position in registers BX, CX, and DX, and resets the
     light pen trigger.

     When function 4 returns, BX contains the calculated pixel x-coordinate
     at which the light pen was triggered. Since the CRTC returns the light
     pen position as a byte address, the value in BX is only as
     accurate as the number of pixels in each byte of the video buffer. (In
     640-by-200 2-color mode, each byte of the video buffer represents
     eight pixels; function 4 thus returns the pixel x-coordinates
     of every eighth pixel.) The light pen position is calculated relative
     to the start of the displayed portion of the video buffer (CRT_START).

     INT 10H function 4 returns the pixel y-coordinate in either CH (in the
     motherboard BIOS) or CX (in all video modes in the EGA BIOS except
     modes 4, 5, and 6). For example, in 320-by-200 4-color graphics mode,
     the pixel y-coordinate is always returned in CH, but in 80-by-25
     16-color alphanumeric mode, the value is returned in CH on a CGA but
     in CX on an EGA.

     The values that function 4 returns in DH and DL represent the
     character row and column at which the light pen was triggered.

     INT 10H function 4 always returns AH = 0 on the MCGA and the VGA,
     which do not support light pens.

     To determine the light pen status in any video mode, call INT 10H
     function 4:

        mov  ah,4           ; AH := 4 (INT 10H function number)
        int  10h

     For example, if you trigger the light pen near the center of the
     display in 640-by-350 16-color mode, the values returned by this
     function might be:

     AH   =    1     (valid light pen results were returned)
     BX   =  320     (x-coordinate of first pixel at the byte
                     address where the pen was triggered)
     CX   =  175     (pixel y-coordinate)
     DH   =   12     (character row)
     DL   =   40     (character column)



���������������������������������������������������������������������������
Function 5: Select Video Page

Caller registers:

     AH   =    5
     AL   =    video page number

Returned values:

     (none)

Video Display Data Area updates:

     0040:004E  CRT_START
     0040:0062  ACTIVE_PAGE

     INT 10H function 5 selects which portion of the video buffer is
     displayed on the CGA, the EGA, the MCGA, and the VGA. It works by
     programming the CRTC Start Address registers. You can use the function
     in 40-by-25 or 80-by-25 alphanumeric video modes (BIOS modes 0, 1, 2,
     and 3) in any of these subsystems.

     On the CGA, the entire 16 KB video buffer is used in both 320-by-200
     and 640-by-200 graphics modes, so no video paging is possible. Calls
     to function 5 are ignored in these modes.

     On the MCGA, the EGA, and the VGA, video pages are available in both
     alphanumeric and graphics modes up to the limits of video RAM.
     However, the BIOS routine does not check whether video RAM is
     sufficient to support a requested video page; if the requested video
     page lies outside the video buffer, the resulting display is unusable.

     The BIOS maintains a current cursor location for as many as eight
     video pages in CURSOR_POSN. When you invoke Function 5, the BIOS moves
     the cursor to where it was located the last time the requested video
     page was displayed.

     The following routine sets the displayed portion of the CGA's video
     buffer to start at B800:1000 (video page 1) in 80-by-25 alphanumeric
     mode:

        mov  ax,0501h       ; AH := 5 (INT 10H function number)
                            ; AL := 1 (video page number)
        int  10h



���������������������������������������������������������������������������
Function 6: Scroll Up

Caller registers:
     AH   =    6
     AL   =    number of lines to scroll
     BH   =    attribute
     CH   =    upper left corner row
     CL   =    upper left corner column
     DH   =    lower right corner row
     DL   =    lower right corner column

Returned values:

     (none)

Video Display Data Area updates:

     (none)

     INT 10H function 6 performs a row-by-row upward scroll of characters
     in a designated area of the active video page. You specify the number
     of rows of characters to scroll in AL. The rectangular area
     in which the scroll is to be performed is defined by its upper left
     corner, specified in CH and CL, and its lower right corner, specified
     in DH and DL.

     The attribute you specify in BH is used for all blank lines inserted
     in the bottom of the scrolled area. In alphanumeric modes, this
     attribute is formatted in the usual manner, with the background
     attribute in the high nibble and the foreground attribute in the low
     nibble. In graphics modes, the format of the attribute in BH depends
     on the mode.

     In 640-by-200 2-color and 320-by-200 4-color modes, the value in BH
     represents a 1-byte pixel pattern. The byte represents eight 1-bit
     pixels in 640-by-200 2-color mode or four 2-bit pixels in 320-by-200
     4-color mode. The pixel pattern is replicated throughout all lines
     that function 6 blanks in the scroll area. In all other EGA, MCGA, and
     VGA graphics modes, the value in BH determines the value of all pixels
     in the blanked lines.

     In 320-by-200 4-color mode on the EGA, the MCGA, and the VGA, function
     6 always scrolls video page 0, regardless of which video page is
     currently displayed.

     Specifying 0 as the number of rows to scroll in AL causes the entire
     scroll area to be blanked.

     In 80-by-25 16-color alphanumeric mode, you can scroll the entire
     screen up one line with the following sequence:

        mov  ax,601h        ; AH := 6 (INT 10H function number)
                            ; AL := 1 (number of lines to scroll up)
        mov  bh,7           ; BH := 7 (attribute)
        mov  cx,0           ; CH := upper left corner:  row 0
                            ; CL := upper left corner:  column 0
        mov  dx,184Fh       ; DH := lower right corner:  row 24 (18H)
                            ; DL := lower right corner:  column 79 (4FH)
        int  10h

     In the same video mode, you could clear only the top three lines of
     the display with a background attribute of 1 (blue on a CGA) and a
     foreground attribute of 7 (white) using this routine:

        mov  ax,600h        ; AH := INT 10H function number
                            ; AL := 0 (clear the scroll area)
        mov  bh,17h         ; BH := attribute (background 1, foreground 7)
        mov  cx,0           ; CH,CL := upper left corner at (0,0)
        mov  dx,024Fh       ; DH,DL := lower right corner at (2,79)
        int  10h

     To get the same result in 640-by-350 16-color graphics mode on the
     EGA, you set the value in BH to indicate a pixel value instead of an
     alphanumeric attribute:

        mov  ax,600h
        mov  bh,1           ; BH := pixel value
        mov  cx,0
        mov  dx,024Fh
        int  10h

     In 640-by-200 2-color mode, the following call to INT 10H function 6
     fills the display with vertical stripes of alternating pixel values:

        mov  ax,600h
        mov  bh,10101010b   ; BH := pixel pattern
        mov  cx,0
        mov  dx,184Fh
        int  10h



���������������������������������������������������������������������������
Function 7: Scroll Down

Caller registers:

     AH   =    7
     AL   =    number of lines to scroll
     BH   =    attribute
     CH   =    upper left corner row
     CL   =    upper left corner column
     DH   =    lower right corner row
     DL   =    lower right corner column

Returned values:

     (none)

Video Display Data Area updates:

     (none)

     INT 10H function 7 performs a row-by-row downward scroll of characters
     in a designated area of the active video page. Except for the direc-
     tion of the scroll, this BIOS function is identical to function 6.



���������������������������������������������������������������������������
Function 8: Return Character Code and Attribute at Cursor

Caller registers:

     AH   =    8
     BH   =    video page

Returned values:

     AH   =    attribute (alphanumeric modes only)
     AL   =    ASCII code

Video Display Data Area updates:

     (none)

     INT 10H function 8 returns the ASCII code of the character at the
     current cursor position in the video page that BH specifies. In
     alphanumeric modes, this is done by reading a single word from the
     video buffer. In graphics modes, the routine compares the character
     matrix at the cursor position to the bit patterns in the current
     graphics character definition table.

     In graphics modes, the PC/XT and PC/AT BIOS uses the ROM character
     definitions at F000:FA6E; the EGA, MCGA, and VGA BIOS uses the
     definitions designated by the interrupt 43H vector. For ASCII codes
     80-0FFH in CGA-compatible graphics modes 4, 5, and 6, the BIOS uses
     the characters defined in the table indicated by the interrupt 1FH
     vector.

     To determine the character code for a character in a graphics mode,
     the BIOS routine regards nonzero pixels as foreground pixels. It is
     the pattern of foreground (nonzero) and background (zero) pixels that
     is compared to the bit patterns in the table. If the pixel pattern in
     the video buffer matches a bit pattern in the character definition
     table, the BIOS determines the character's ASCII code from the bit
     pattern's location in the table. If the pixel pattern in the video
     buffer does not match any bit pattern in the table, the BIOS routine
     returns 0 in AL.

     In 320-by-200 4-color mode on the EGA, the MCGA, and the VGA, this
     function works properly only in video page 0.

     The following code fragment reads the character in the screen's upper
     left corner:

        mov  ah,0Fh         ; AH := 0FH (INT 10H function number)
        int  10h            ; leaves BH = active video page
        mov  ah,2           ; AH := 2 (INT 10H function number)
        mov  dx,0           ; DH,DL := row 0, column 0
        int  10h            ; sets cursor position to (0,0)
        mov  ah,8           ; AH := 8 (INT 10H function number)
        int  10h            ; leaves AL = ASCII code



���������������������������������������������������������������������������
Function 9: Write Character and Attribute at Cursor

Caller registers:

     AH   =    9
     AL   =    ASCII code
     BH   =    background pixel value (320-by-200 256-color mode) or video
               page (all other modes)
     BL   =    foreground pixel value (graphics modes) or attribute value
               (alphanumeric modes)
     CX   =    repetition factor

Returned values:

     (none)

Video Display Data Area updates:

     (none)

     INT 10H function 9 writes a character one or more times into the video
     buffer without moving the cursor. You must specify a repetition factor
     of 1 or greater in CX. The BIOS writes a string composed of the
     character in AL into the buffer. The length of the string is
     determined by the repetition factor in CX.

     In alphanumeric modes, both the ASCII code and the corresponding
     attribute byte are updated for each character written into the video
     buffer. In graphics modes, each character is written into the buffer
     in a rectangular area the size of the character matrix. The value in
     BL is used for the character's foreground pixels. In 320-by-200 256-
     color graphics mode, the value in BH specifies the character's
     background pixel value; in all other graphics modes, BH designates a
     video page, so the character's background pixels are 0. In all
     graphics modes except 320-by-200 256-color mode, the character is
     XORed into the buffer if bit 7 of BL is set to 1.

     INT 10H function 9 does not compare the repetition factor with the
     number of displayed character columns. In alphanumeric modes, this may
     not matter; the video buffer map is such that a string too long to be
     displayed in one row of characters wraps to the next row. In graphics
     modes, however, a string should be no longer than the remainder of the
     current character row.

     You must specify a video page in register BH in alphanumeric modes as
     well as in native EGA graphics modes, but the value in BH is ignored
     by the EGA, the MCGA, and the VGA BIOS in 320-by-200 4-color graphics
     mode.

     The following routine writes a string of 20 asterisks to the upper
     left corner of the display in 80-by-25 16-color mode. The foreground
     value in each character's attribute byte is set to 7, and the
     background value is set to 1. The cursor is positioned with a call to
     INT 10H function 2 before the string is written with function 9.

        mov  ah,2           ; AH := 2 (INT 10H function number)
        mov  bh,0           ; BH := video page
        mov  dx,0           ; DH := cursor row
                            ; DL := cursor column
        int  10h            ; set cursor position to (0,0)
        mov  ah,9           ; AH := 9 (INT 10H function number)
        mov  al,'*'         ; AL := ASCII code
        mov  bl,17h         ; BL := attribute byte
        mov  cx,20          ; CX := repetition factor
        int  10h



���������������������������������������������������������������������������
Function 0AH: Write Character(s) at Cursor Position

Caller registers:

     AH   =    0AH
     AL   =    ASCII code
     BH   =    background pixel value (320-by-200 256-color mode) or video
               page (all other modes)
     BL   =    foreground pixel value (graphics modes only)
     CX   =    repetition factor

Returned values:

     (none)

Video Display Data Area updates:

     (none)

     INT 10H function 0AH is the same as INT 10H function 9, with this
     exception: In alphanumeric video modes, only the character code is
     written into the video buffer. The character's attribute remains
     unchanged in the buffer.

     This example clears one character row from the cursor position to its
     end. Before calling function 0AH, the example determines the active
     video page and the number of displayed character columns with a call
     to INT 10H function 0FH, and the cursor position using INT 10H
     function 3.

        mov  ah,0Fh         ; AH := 0FH (INT 10H function number)
        int  10h            ; leaves AH = number of columns,
                            ;        BH = active video page
        mov  al,ah
        xor  ah,ah          ; AX := number of columns
        push ax
        mov  ah,3           ; AH := 3 (INT 10H function number)
        int  10h            ; leaves DH,DL = cursor position
        pop  cx             ; CX := displayed character columns
        sub  cl,dl          ; CX := number of remaining chars in line
        xor  bl,bl          ; BL := foreground pixel value
        mov  ax,0A20h       ; AH := 0AH (INT 10H function number)
                            ; AL := 20H (ASCII blank character)
        int  10h



���������������������������������������������������������������������������
Function 0BH: Set Overscan Color, Select 4-Color Palette

Caller registers:

     AH   =    0BH
     BH   =    0 to set border or background color
          =    1 to select 4-color palette
     BL   =    color value (if BH = 0)
               palette value (if BH = 1)

Returned values:

     (none)

Video Display Data Area update:

     0040:0066  CRT_PALETTE

     INT 10H function 0BH comprises two subfunctions selected according to
     the value in BH. Function 0BH is intended for use only in 320-by-200
     4-color mode and in CGA alphanumeric modes, but you can use it with
     caution in other video modes.


     BH = 0
     When BH = 0 on the CGA and the MCGA, the BIOS loads the low-order five
     bits of the value in BL into the Color Select register (3D9H). In 320-
     by-200 4-color graphics mode, bits 0-3 determine the background color
     (the color displayed for pixels of value 0) as well as the border
     color. In 640-by-200 and 640-by-480 2-color modes, bits 0-3 specify
     the color of foreground (nonzero) pixels. On the CGA, these same four
     bits also determine the border color in alphanumeric modes.

     Bit 4 of the Color Select register selects between normal and high-
     intensity colors in CGA and MCGA graphics modes (see Chapter 4). For
     compatibility, the BIOS for the EGA and the VGA emulates this effect
     by using a palette of high-intensity colors when bit 4 of BL is set.

     In 200-line modes on the EGA and VGA, the value in BL is placed in
     the Attribute Controller's Overscan Color register (11H). This sets
     the border color. If either subsystem is in a graphics mode, the same
     value is also stored in palette register 0. This establishes the same
     color for all pixels of value 0.

     Don't use function 0BH with BL = 0 in other EGA and VGA video modes.
     In some modes, the BIOS routine stores incorrect color values in the
     Palette and Overscan registers, while in others it does nothing at
     all. You should use INT 10H function 10H to program the Attribute
     Controller on the EGA and VGA.

     Once the color register or Attribute Controller has been programmed,
     the BIOS routine copies bit 5 of CRT_PALETTE in the Video Display Area
     to bit 0 of register BL, and transfers control to the routine for
     BH = 1.


     BH = 1
     When BH = 1, the low-order bit of the value in BL determines which of
     two 4-color palettes is used for 320-by-200 4-color mode (see Figure
     A-12). On the CGA and the MCGA, this bit is copied into bit 5 of the
     Color Select register (3D9H). On the EGA and the VGA, the bit
     determines which set of color values is loaded into the Attribute
     Controller's Palette registers. The colors correspond to the CGA's
     320-by-200 4-color palettes. (See Chapter 4 for more details.)


����������������������������������������������������������������������������ķ
Pixel Value            Color Displayed
��������������������������������������������������������������������������
(bit 0 of BL = 0)
1                      Green
2                      Red
3                      Yellow

(bit 0 of BL = 1)
1                      Cyan
2                      Violet
3                      White

     Figure A-12.  Function 0BH 4-color palettes.


     Function 0BH with BH = 1 has no effect in alphanumeric modes. In
     graphics modes other than 320-by-200 4-color mode, however, the Color
     Select register (on the CGA and the MCGA) is loaded or the palette
     registers (on the EGA and the VGA) are updated as if 320-by-200 4-
     color mode were in effect. For this reason, you should use this
     subfunction cautiously in graphics modes other than 320-by-200 4-color
     mode.

     The following example has three different effects, depending on the
     current video mode. In 200-line alphanumeric modes, it sets the border
     color; in 320-by-200 4-color mode it sets both border and background
     colors; and in CGA or MCGA 2-color graphics modes, it sets the
     foreground color.

        mov  ah,0BH         ; AH := 0BH (INT 10H function number)
        mov  bh,0           ; BH := subfunction number
        mov  bl,BorderColor ; BL := color value
        int  10h

     To select a 4-color palette in 320-by-200 4-color mode, call function
     0BH with  BH = 1:

        mov  ah,0Bh
        mov  bh,1           ; BH := subfunction number
        mov  bl,0           ; bit 0 of BL := 0 (red-green-yellow palette)
        int  10h

     In 320-by-200 4-color mode, select a high-intensity set of colors by
     calling function 0BH with BH = 0 and with bit 4 of BL set to 1:

        mov  ah,0Bh
        mov  bh,0
        mov  bl,10h         ; bit 4 selects high-intensity palette
                            ; bits 3-0 select border/background color
        int  10h



���������������������������������������������������������������������������
Function 0CH: Store Pixel Value

Caller registers:

     AH   =    0CH
     AL   =    pixel value
     BH   =    video page
     CX   =    x-coordinate
     DX   =    y-coordinate

Returned values:

     (none)

Video Display Data Area updates:

     (none)

     INT 10H function 0CH updates the value of a pixel at a specified
     location in the video buffer. In all graphics modes except 320-by-200
     256-color mode, if the high-order bit of the value in AL is set to 1,
     the value in AL is XORed into the video buffer. Otherwise, the value
     in AL becomes the pixel's new value.

     On the EGA, the MCGA, and the VGA, the value in BH is used to select
     among available video pages in the current video mode. However, the
     value in BH is ignored in 320-by-200 4-color mode.

     To set the value of a pixel in a 350-line graphics mode on an EGA with
     only 64 KB of video RAM, you must account for the chaining of memory
     maps to bit planes (as discussed in Chapter 4). In this situation,
     the BIOS routine expects you to specify the pixel value in AL using
     only its odd-numbered bits. Thus, the four possible pixel values
     should be specified as 0 (0000B), 1 (0001B), 4 (0100B), and 5 (0101B)
     instead of 0, 1, 2, and 3.

     The following routine shows how you would set the value of the pixel
     at (200,100) to 1 in any graphics mode:

        mov  ah,0Ch         ; AH := 0CH (INT 10H function number)
        mov  al,1           ; AL := pixel value
        mov  cx,200         ; CX := x-coordinate
        mov  dx,100         ; DX := y-coordinate
        int  10h

     To XOR a pixel value into the video buffer, set bit 7 of AL to 1
     before executing interrupt 10H, as in the following procedure:

        mov  ah,0Ch
        mov  al,1
        mov  cx,200
        mov  dx,100
        or   al,10000000b   ; set bit 7 to indicate XOR
        int  10h

     This code fragment illustrates the special situation that arises in a
     350-line video mode on an IBM EGA with only 64 KB of video RAM. The
     code sets the value of the pixel at (75,50) to 3.

        mov  ah,0Ch
        mov  al,0101b       ; AL := pixel value of 3 (11B)
                            ; represented in odd bits only
        mov  cx,75
        mov  dx,50
        int  10h



���������������������������������������������������������������������������
Function 0DH: Return Pixel Value

Caller registers:

     AH   =    0DH
     BH   =    video page
     CX   =    x-coordinate
     DX   =    y-coordinate

Returned values:

     AL   =    pixel value

Video Display Data Area updates:

     (none)

     INT 10H function 0DH returns the value of a pixel at a specified
     location in the video buffer.

     On an EGA in 320-by-200 4-color mode, the function ignores the video
     page value specified in BH.

     IBM's EGA BIOS (9/13/84 version) contains a bug in INT 10H function
     0DH. In 350-line graphics modes on an IBM EGA with only 64 KB of video
     RAM, the value returned in AL is incorrect. Apparently, the BIOS
     routine calculates the pixel's byte offset in the video buffer without
     properly accounting for the mapping of even addresses to even bit
     planes and odd addresses to odd bit planes.

     To determine the value of the pixel at (100,100), you could execute
     the following sequence of instructions:

        mov  ah,0Dh         ; AH := 0DH (INT 10H function number)
        mov  bh,0           ; BH := video page (0 in this example)
        mov  cx,100         ; CX := x-coordinate
        mov  dx,100         ; DX := y-coordinate
        int  10h            ; leaves AL = pixel value



���������������������������������������������������������������������������
Function 0EH: Display Character in Teletype Mode

Caller registers:

     AH   =    0EH
     AL   =    ASCII code
     BH   =    video page (PC BIOS versions dated 10/19/81 and earlier)
     BL   =    foreground pixel value (graphics modes only)

Returned values:

     (none)

Video Display Data Area update:

     0040:0050  CURSOR_POSN

     INT 10H function 0EH calls INT 10H function 0AH to display the
     character you pass in register AL. Unlike function 0AH, however,
     function 0EH moves the cursor, and ASCII codes 7 (bell), 8
     (backspace), 0DH (carriage return), and 0AH (linefeed) are treated as
     cursor control commands instead of displayable characters. Function
     0EH always updates the active (currently displayed) video page except
     as noted above.

     If the character is displayed in the rightmost character column,
     function 0EH advances the cursor to the start of the next character
     row. If necessary, function 0EH calls INT 10H function 06H to scroll
     the screen. In alphanumeric modes, the attribute of the displayed
     character is used for the scroll. In graphics modes, the scroll
     attribute is always 0.

     In alphanumeric modes, the attribute byte at the position where the
     character is written determines the character's foreground and
     background attributes. For this reason, you should probably fill the
     video buffer with the desired alphanumeric attributes before using
     function 0EH.

     In graphics modes, the character is written into the video buffer in a
     rectangular area the size of the character matrix. The character's
     pixels have the value BL specifies, and the remaining background
     pixels have a value of 0. Because the value in BL is passed through to
     INT 10H function 0AH, you can set bit 7 so that the character is XORed
     into the video buffer.

     NOTE: Unfortunately, function 0EH does not expand tab characters
     (ASCII code 9) into blanks.

     The following routine shows how you might use function 0EH to display
     a string of characters.

        mov  cx,StringLength        ; CX := number of bytes in string
        jcxz L02                    ; do nothing if null string
        mov  si,StringAddr          ; DS:SI := address of string
        mov  bl,GraphicsAttribute   ; BL := attribute (graphics modes only)
L01:    lodsb                       ; AL := next character in string
        mov  ah,0Eh                 ; AH := 0EH (INT 10H function number)
        int  10h
        loop L01
L02:    .
        .
        .



���������������������������������������������������������������������������
Function 0FH: Return Current Video Status

Caller register:

     AH   =    0FH

Returned values:

     AH   =    number of displayed character columns
     AL   =    video mode number
     BH   =    active video page

Video Display Data Area updates:

     (none)

     INT 10H function 0FH returns information about the current video mode
     and the width of the displayed portion of the video buffer. The number
     of character columns (returned in AH) and the number of the current
     video page (returned in BH) are copied from CRT_COLS and ACTIVE_PAGE
     in the Video Display Data Area.

     The value returned in AL is copied from CRT_MODE in the Video Display
     Data Area. It corresponds to the video display modes tabulated for
     function 0. On the EGA and the VGA, bit 7 of the value in AL is
     derived from bit 7 of the INFO byte. (INT 10H function 0 sets bit 7 of
     the INFO byte whenever you use function 0 to select a video mode
     without clearing the video buffer.)

     This example shows how to determine the current position of the
     displayed cursor. Before calling INT 10H function 3 to find out the
     cursor position, the example uses function 0FH to determine the
     currently displayed video page.

        mov  ah,0Fh         ; AH := 0FH (INT 10H function number)
        int  10h            ; leaves BH = active video page
        mov  ah,3           ; AH := 3 (INT 10H function number)
        int  10h            ; leaves DH,DL = cursor position



���������������������������������������������������������������������������
Function 10H: Set Palette Registers, Set Intensity/Blink Attribute

Caller registers:

     AH   =    10H

Update a specified palette register:

     AL   =    0
     BH   =    color value
     BL   =    palette register number

Specify the overscan (border) color:

     AL   =    1
     BH   =    color value

Update all 16 palette registers plus the Overscan register:

     AL    =    2
     ES:DX =    address of 17-byte table

Select Background Intensity or Blink attribute:

     AL   =    3
     BL   =    0 for background intensity (blink disabled)
          =    1 for blink

Read a specified palette register:

     AL   =    7
     BL   =    palette register number

     Returned value:
     BH   =    contents of specified palette register

Read the contents of the Overscan register:

     AL   =    8

     Returned value:
     BH   =    contents of Overscan register

Read all 16 palette registers plus the Overscan register:

     AL    =    9
     ES:DX =    address of 17-byte table

     Returned values:
     Bytes 00H through 0FH of table contain palette register values.
     Byte 10H of table contains Overscan register value.

Update the specified video DAC Color register:

     AL   =    10H
     BX   =    color register number
     CH   =    green value
     CL   =    blue value
     DH   =    red value

Update a block of video DAC color registers:

     AL    =    12H
     BX    =    first register to update
     CX    =    number of registers to update
     ES:DX =    address of table of red-green-blue values

Set Attribute Controller Color Select State:

     AL   =    13H
     BL   =    0 to set Mode Control register bit 7, 1 to set Color Select
                 register
     BH   =    value for bit 7 (if BL = 0) or value for Color Select
                 register (if BL = 1)

Read specified video DAC Color register:

     AL   =    15H
     BX   =    color register number

     Returned values:
     CH   =    green
     CL   =    blue
     DH   =    red

Read a block of video DAC color registers:

     AL    =    17H
     BX    =    first register to read
     CX    =    number of registers to read
     ES:DX =    address of table of red-green-blue values

     Returned values:
     Bytes 0 through 3n - 1 (where n is the number of registers passed in
     CX) contain the red-green-blue values read from the specified block of
     color registers.

Update video DAC Mask register:

     AL   =    18H
     BL   =    new mask value

Read video DAC Mask register:

     AL   =    19H

     Returned value:
     BL   =    value read from video DAC Mask register

Read Attribute Controller Color Select register:

     AL   =    1AH

     Returned values:
     BL   =    bit 7 of Mode Control register
     BH   =    bits 2 through 3 of Color Select register (if BL = 0)
               bits 0 through 3 of Color Select register (if BL = 1)

Perform gray-scaling on a block of video DAC color registers:

     AL   =    1BH
     BX   =    first color register in block
     CX   =    number of color registers

Video Display Data Area updates:

     0040:0065  CRT_MODE_SET
     0040:0066  CRT_PALETTE

     INT 10H function 10H exists only in the EGA, MCGA, and VGA BIOS. The
     function comprises 16 subfunctions that are selected according to the
     value in AL. Figure A-13 shows the support that the various
     subsystems provide for these subfunctions. All subfunctions work in
     both alphanumeric and graphics modes.

     Subfunctions 0 through 9 support attribute and palette programming.
     Subfunctions 10H through 1BH support the video DAC on the MCGA and the
     VGA.


     AL = 0
     When AL = 0 on the EGA and the VGA, function 10H updates the value in
     one of the palette registers in the Attribute Controller. The routine
     loads the value in BH into the register that BL specifies.

     Although this subfunction's intended purpose is to load a color value
     into a palette register, the BIOS routine does not validate the
     register number in BL. Thus, you can also use it to update the
     Attribute Controller's Mode Control, Overscan, Color Plane Enable, and
     Horizontal Pel Panning registers.

     On the MCGA, when BH = 7 and BL = 12H, the BIOS routine sets bit 3 of
     the Video DAC Mask register (3C6H) to 0. This causes the BIOS to
     regard bit 3 of all 4-bit pixel values or alphanumeric attributes as a
     "don't care" bit in reference to the Video DAC color registers, so
     only the first eight registers can be referenced. This is useful in
     displaying two 256-character sets in an alphanumeric mode (see Chapter
     10). The MCGA BIOS ignores all other values in BH or BL.


����������������������������������������������������������������������������ķ
Subfunction           EGA           MCGA         VGA
��������������������������������������������������������������������������
0                     x             x            x
1                     x                          x
2                     x                          x
3                     x             x            x
4 (reserved)
5 (reserved)
6 (reserved)
7                                                x
8                                                x
9                                                x
10H                                 x            x
11H (reserved)
12H                                 x            x
13H                                              x
14H (reserved)
15H                                 x            x
16H (reserved)
17H                                 x            x
18H                                 x            x
Subfunction           EGA           MCGA         VGA
18H                                 x            x
19H                                 x            x
1AH                                              x
1BH                                 x            x

     Figure A-13.  INT 10H Function 10H support in EGA, MCGA, and VGA BIOS.


     AL = 1
     When AL = 1 on the EGA and the VGA, the BIOS copies the value in BH
     into the Attribute Controller's Overscan register (11H).


     AL = 2
     When AL = 2 on the EGA and the VGA, the BIOS expects ES:DX to contain
     the address of a 17-byte table of values for the 16 Palette registers
     (bytes 0 through 15) and for the Overscan register (byte 16). The
     routine copies these values into the corresponding registers in the
     Attribute Controller.


     AL = 3
     When AL = 3 on the EGA and the VGA, the value in BL determines the
     value of bit 3 of the Attribute Controller's Mode Control register
     (10H). If BL = 0, bit 3 of the Mode Control register value is set to
     0, disabling the blinking attribute. If BL is 1, bit 3 is set to 1 to
     enable blinking.

     When AL = 3 on the MCGA, bit 5 of the Color Control register (3D8H) is
     set to reflect the value in BL. If BL = 0, bit 5 is set to 0 to
     disable blinking. If BL is 1,  bit 5 is set to 1.


     AL = 7
     When AL = 7 on the VGA, the value in the Attribute Controller Palette
     register that BL specified is returned in BH. Because the BIOS does
     not check the specified register number, this subfunction may be used
     to return the contents of any VGA Attribute Controller register.


     AL = 8
     When AL = 8 on the VGA, the contents of the Attribute Controller's
     Overscan register are returned in BH.


     AL = 9
     When AL = 9 on the VGA, the contents of all 16 palette registers and
     the Overscan register are returned to a 17-byte table whose address
     was passed to the BIOS in the register pair ES:DX.


     AL = 10H
     When AL = 10H on the MCGA and the VGA, the video DAC color register
     that BX specifies is updated with the red, green, and blue values
     specified in DH, CH, and CL. Only the low-order six bits of each of
     the three color values are significant.

     If gray-scale summing is enabled, the value stored in the color
     register is the gray-scale value that corresponds to the specified
     color values (see INT 10H function 12H with BL = 33H).


     AL = 12H
     When AL = 12H on the MCGA and the VGA, a block of consecutive video
     DAC color registers is updated from the table whose address is passed
     in ES:DX. The value in BX (00H through 0FFH) indicates the first color
     register to update, and CX contains the number of registers affected.
     The BIOS routine performs no error checking; if the sum of the values
     in BX and CX is greater than 256 (100H), the routine wraps around and
     updates the first color register(s) in the video DAC.

     If gray-scale summing is enabled, the values stored in the color
     registers are the gray-scale values that correspond to the color
     values in the table (see INT 10H function 12H with BL = 33H).

     You must format the table in three-byte groups. Each group must
     contain a red color value in the first byte, a green value in the
     second byte, and a blue value in the third byte. Only the low-order
     six bits of each color value are significant.


     AL = 13H
     On the VGA, when AL = 13H, the ROM BIOS updates the Attribute
     Controller's Mode Control register (10H) and the Color Select register
     (14H) to enable grouping of the 256 video DAC color registers into
     blocks of 16 or 64 registers each, as discussed in Chapter 3.

     When BL = 0, the BIOS uses the value passed in BH to update bit 7 of
     the Mode Control register. When BH = 1, bit 7 is set to 1. This causes
     the BIOS to use bits 0 and 1 of the Color Select register in
     place of bits 4 and 5 of the palette register values. When BH = 0,
     bit 7 is set to 0, and all six low-order bits of the values in the
     palette registers are significant.

     When BL = 1, the value in BH is stored in the appropriate bit fields
     in the Color Select register. If bit 7 of the Mode Control register is
     1, bits 0 through 3 of the value in BH are copied into bits 0 through
     3 of the Color Select register. If bit 7 of the Mode Control register
     is 0, bits 0 through 1 of BH are copied into bits 2 through 3 of the
     Color Select register.


     AL = 15H
     When AL = 15H on the MCGA and the VGA, the contents of the video DAC
     color register specified in BX are returned in registers DH (red), CH
     (green), and CL (blue). Only the low-order six bits of each of the
     color values are significant.


     AL = 17H
     When AL = 17H on the MCGA and the VGA, the values from a block of
     adjacent video DAC color registers are copied to the table whose
     address is passed in ES:DX. The value in BX (00H through 0FFH)
     indicates the first color register to be read, and CX contains the
     number of registers affected. The BIOS routine performs no error
     checking; the sum of the values in BX and CX should not exceed 256
     (100H).

     The table must contain three bytes for every color register read.
     Color values for each register are stored sequentially in the table in
     three-byte groups. The first byte of each group contains the color
     register's red value, the second its green value, and the third its
     blue value.


     AL = 18H
     On the MCGA and the VGA, when AL = 18H, the value in BL is copied into
     the video DAC Mask register (3C6H).


     AL = 19H
     On the MCGA and the VGA, when AL = 19H, the value in the video DAC
     Mask register (3C6H) is returned in BL.

     NOTE: The BIOS on the VGA Adapter does not support subfunctions 18H
     and 19H. Also, IBM's BIOS Interface Technical Reference does not
     document these subfunctions, so they might not be supported in future
     BIOS releases.


     AL = 1AH
     On the VGA, when AL = 1AH, the current values of bit 7 of the
     Attribute Controller's Mode Control register (10H) and bits 0 through
     3 of the Color Select register (14H) are returned in BL and BH
     respectively. If bit 7 of the Mode Control register is 1, the value in
     BH represents bits 0 through 3 of the Color Select register. If bit 7
     of the Mode Control register is 0, only bits 2 through 3 are returned
     as bits 0 through 1 of BH.


     AL = 1BH
     On the MCGA and the VGA, when AL = 1BH, gray-scale summing is
     performed on a block of consecutive video DAC color registers. BX
     indicates the first color register affected. CX specifies the number
     of registers to update.

     The following example uses INT 10H function 10H to update the color
     value in a single palette register:

        mov  ax,1000h       ; AH := 10H (INT 10H function number)
                            ; AL := 0
        mov  bh,6           ; BH := new color value (yellow)
        mov  bl,7           ; BL := palette register number
        int  10h

     To update the Overscan register and change the displayed border color,
     call function 10H with AL = 1:

        mov  ax,1001h       ; AH := 10H
                            ; AL := 1
        mov  bh,1           ; BH := color value for overscan
        int  10h

     To load all 16 palette registers and the Overscan register from a
     table, call function 10H with AL = 2:

        mov  ax,1002h               ; AH := 10H
                                    ; AL := 2
        mov  dx,seg PaletteTable
        mov  es,dx
        mov  dx,offset PaletteTable ; ES:DX -> table of palette register
                                    ;           values
        int  10h
        .
        .
        .
PaletteTable db    00h,01h,02h,03h,04h,05h,06h,07h ; palette registers 0-7
             db    38h,39h,3Ah,3Bh,3Ch,3Dh,3Eh,3Fh ; palette regs 8-0FH
             db    00h                             ; Overscan reg

     To disable the blinking attribute, call function 10H with AL = 3 and
     BL = 0:

        mov  ax,1003h       ; AH := 10H
                            ; AL := 3
        mov  bl,0           ; BL := 0 (disable blinking)
        int  10h

     The following fragment performs gray-scale summing on the first 16
     video DAC color registers. The remaining 240 registers are unaffected.

        mov  ax,101Bh       ; AH := 10H
                            ; AL := 1BH
        mov  bx,0           ; BX := first color register affected
        mov  cx,16          ; CX := number of color registers
        int  10h



���������������������������������������������������������������������������
Function 11H: Character Generator Interface

Caller registers:

     AH    =    11H

Load alphanumeric character definitions.

     User-specified character definition table:
     AL    =    0
     BH    =    points (bytes per character definition)
     BL    =    table in character generator RAM
     CX    =    number of characters defined in table
     DX    =    ASCII code of first character defined
     ES:BP =    address of user-specified table

     ROM BIOS 8-by-14 character definitions:
     AL    =    1
     BL    =    table in character generator RAM

     ROM BIOS 8-by-8 character definitions:
     AL    =    2
     BL    =    table in character generator RAM

     ROM BIOS 8-by-16 character definitions:
     AL    =    4
     BL    =    table in character generator RAM

Select displayed character definition tables.

     AL    =    3
     BL    =    value for Character Map Select register (EGA, VGA)
           =    character generator RAM table numbers (MCGA)

Load alphanumeric character definitions and program the CRT Controller.

     User-specified character definition table:
     AL    =    10H
     BH    =    points
     BL    =    table in character generator RAM
     CX    =    number of characters defined in table
     DX    =    ASCII code of first character defined
     ES:BP =    address of user-specified table

     ROM BIOS 8-by-14 character definitions:
     AL    =    11H
     BL    =    table in character generator RAM

     ROM BIOS 8-by-8 character definitions:
     AL    =    12H
     BL    =    table in character generator RAM

     ROM BIOS 8-by-16 character definitions:
     AL    =    14H
     BL    =    table in character generator RAM

Load graphics character definitions.

     User-specified 8-by-8 character definition table for interrupt 1FH
     vector:
     AL    =    20H
     ES:BP =    address of user-specified character definition table

     User-specified character definition table:
     AL    =    21H
     BL    =    0  (character rows per screen specified in DL)
           =    1  14 character rows per screen
           =    2  25 character rows per screen
           =    3  43 character rows per screen
     CX    =    points (bytes per character definition)
     DL    =    character rows per screen (when BL = 0)
     ES:BP =    address of user-specified character definition table

     ROM BIOS 8-by-14 character definitions:
     AL    =    22H
     BL    =    character rows per screen (as above)
     DL    =    (as above)

     ROM BIOS 8-by-8 character definitions:
     AL    =    23H
     BL    =    character rows per screen (as above)
     DL    =    (as above)

     ROM BIOS 8-by-16 character definitions:
     AL    =    24H
     BL    =    character rows per screen (as above)
     DL    =    (as above)

Get current character generator information.

     AL    =    30H
     BH    =    0  Contents of interrupt 1FH vector
           =    1  Contents of interrupt 43H vector
           =    2  Address of ROM 8-by-14 character table
           =    3  Address of ROM 8-by-8 character table
           =    4  Address of second half of ROM 8-by-8 character table
           =    5  Address of ROM 9-by-14 alternate character table
           =    6  Address of ROM 8-by-16 character table
           =    7  Address of ROM 9-by-16 alternate character table

     Returned values:
     CX    =    POINTS (height of character matrix)
     DL    =    ROWS (displayed character rows - 1)
     ES:BP =    address of character definition table

Video Display Data Area updates:

     0040:004C  CRT_LEN
     0040:0060  CURSOR_MODE
     0040:0084  ROWS
     0040:0085  POINTS

     INT 10H function 11H comprises a gamut of subfunctions that support
     both the alphanumeric and the graphics character generators on the
     EGA, the MCGA, and the VGA. You choose a subfunction with the value
     you specify in AL. The contents of the other registers depend on the
     subfunction.


     AL = 0, 1, 2, or 4
     You can use subfunctions 0, 1, 2, and 4 to load a table of character
     definitions into video RAM for use by the character generator.
     (Chapter 10 describes this in detail.) All four subfunctions are
     available on the VGA. On the EGA, the BIOS ignores subfunction 4. The
     MCGA BIOS does not contain an 8-by-14 character definition table, so
     calls with AL = 1 are treated as calls with AL = 4.

     On the MCGA, character definitions in character generator RAM are not
     displayed until they are loaded into the character generator's
     internal font pages (see Chapter 10). To accomplish this through the
     video BIOS, follow each call to function 11H performed with AL = 0, 1,
     2, or 4 with a call to function 11H with AL = 3.

     The MCGA's CRTC can only display characters that are 2, 4, 6, 8, 10,
     12, 14, or 16 lines high. Thus, BH should specify one of these values.
     Also, for compatibility with the VGA BIOS, the MCGA BIOS routine
     extends character definitions for 14-line characters into definitions
     for 16-line characters by duplicating the 14th line of each character
     definition.


     AL = 3
     On the EGA and the VGA, when AL = 3, function 11H loads the value
     passed in BL into the Sequencer's Character Map Select register. On
     the EGA and the MCGA, bits 0 and 1 of BL indicate which of four 256-
     character tables is used when bit 3 of a character's attribute byte is
     0. Bits 2 and 3 of BL indicate which table is used when bit 3 of a
     character's attribute is 1. On the VGA, bits 0, 1, and 4 specify one
     of eight tables to be used when a character's attribute bit 3 is 0,
     and bits 2, 3, and 5 specify the table used when attribute bit 3 is 1.

     If both bit fields in BL specify the same character definition table,
     only that table is loaded and displayed.

     AL = 10H, 11H, 12H, or 14H
     Subfunctions 10H, 11H, 12H, and 14H are analogous to subfunctions 0,
     1, 2, and 4 in that they load an alphanumeric character definition
     table into video RAM. The difference is that, for these subfunctions
     on the EGA and the VGA, the BIOS reprograms the CRT Controller to
     accommodate the height of the character matrix. On the MCGA, calls to
     function 11H with AL = 10H, 11H, 12H, and 14H are treated as calls to
     functions 0, 1, 2, and 4 respectively.

     NOTE: Disable alphanumeric cursor emulation before using these
     subfunctions on the EGA. The EGA BIOS cursor emulation routine does
     not always produce a satisfactory alphanumeric cursor. (Chapter 3
     discusses this in detail.)


     AL = 20H
     If AL = 20H, the address in ES:BP is copied into the interrupt 1FH
     vector at 0000:007C. This vector points to a table of 8-by-8 character
     definitions for ASCII codes 80H through FFH. This character definition
     table is used by the BIOS in CGA-compatible 320-by-200 4-color and
     640-by-200 2-color graphics modes.


     AL = 21H, 22H, 23H, or 24H
     Subfunctions 21H, 22H, 23H, and 24H are analogous to subfunctions 0,
     1, 2, and 4 respectively. The BIOS updates the interrupt 43H vector
     and the Video Display Data Area variables POINTS and ROWS with values
     that describe the specified graphics character definitions.

     The BIOS does not reprogram the CRT Controller when it loads graphics-
     mode character definition tables.


     AL = 30H
     If AL = 30H, INT 10H function 11H returns information about the
     character generator's current status. The value in POINTS in the Video
     Display Data Area is copied into register CX, the value of ROWS is
     returned in DL, and the address of one of eight character definition
     tables is returned in ES:BP. The value in BH indicates which table's
     address is returned.

     NOTE: If you call this subfunction on the EGA with BH equal to 6 or 7,
     or on the MCGA with BH equal to 5 or 7, the address returned in ES:BP
     is undefined.

     To select an 80-by-43 alphanumeric mode on a 350-line display, invoke
     INT 10H function 11H to load the ROM 8-by-8 character set and
     reprogram the CRTC to display 43 character rows. (Dividing 350 lines
     by 8 lines per character gives 43 character rows.) The following
     example assumes that the EGA is already in an 80-by-25 alphanumeric
     mode (BIOS mode number 3 or 7).

        mov  ax,40h
        mov  es,ax

        push es:[87h]               ; preserve INFO
        or   byte ptr es:[87h],1    ; disable cursor emulation
        mov  ax,1112h               ; AH := 11H (INT 10H function number)
                                    ; AL := 12H (subfunction:  load 8x8
                                    ;  alphanumeric characters, reprogram
                                    ;  CRTC)
        mov  bl,0                   ; BL := table 0 in character generator
                                    ;  RAM
        int  10h
        pop  es:[87h]               ; restore INFO



���������������������������������������������������������������������������
Function 12H: Video Subsystem Configuration (Alternate Select)

Caller registers:

     AH    =    12H

Return video configuration information:

     BL    =    10H

     Returned values:
     BH    =    default BIOS video mode
                0  Color
                1  Monochrome
     BL    =    amount of EGA video RAM
                0  64 KB
                1  128 KB
                2  192 KB
                3  256 KB
     CH    =    feature bits
     CL    =    configuration switch setting

Select alternate Print Screen routine:

     BL    =    20H

Select scan lines for alphanumeric modes:

     BL    =    30H
     AL    =    0  200 scan lines
                1  350 scan lines
                2  400 scan lines

     Returned value:
     AL    =    12H

Select default palette loading:

     BL    =    31H
     AL    =    0  Enable default palette loading
           =    1  Disable default palette loading

     Returned value:
     AL    =    12H

CPU access to video RAM:

     BL    =    32H
     AL    =    0  Enable CPU access to video RAM and I/O ports
           =    1  Disable CPU access to video RAM and I/O ports

     Returned value:
     AL    =    12H

Gray-scale summing:

     BL    =    33H
     AL    =    0  Enable gray-scale summing
           =    1  Disable gray-scale summing

     Returned value:
     AL    =    12H

Cursor emulation:

     BL    =    34H
     AL    =    0  Enable cursor emulation
           =    1  Disable cursor emulation

     Returned value:
     AL    =    12H

PS/2 video display switching:

     BL    =    35H
     AL    =    0  Initial adapter video off
                1  Initial planar video on
                2  Switch active video off
                3  Switch inactive video on
     ES:DX =    address of 128-byte save area (for AL = 0, 2, or 3)

     Returned value:
     AL    =    12H

Video refresh control:

     BL    =    36H
     AL    =    0  Enable refresh
                1  Disable refresh

     Returned value:
     AL    =    12H

Video Display Data Area updates:

     (see below)

     INT 10H function 12H comprises nine subfunctions selected using the
     value in BL.


     BL = 10H
     When BL = 10H on the EGA and the VGA, this BIOS routine returns
     information about the configuration of the video subsystem. This
     information is copied from INFO and INFO_3 in the Video Display Data
     Area. These variables are initialized in the BIOS power-on startup
     code.

     The value returned in BH reflects whether the video subsystem is
     configured for a color (BH = 0) or monochrome (BH = 1) video mode.
     Bits 0 and 1 in BL indicate how much video RAM is present. The values
     returned in CH and CL are derived from the INFO_3 byte. Bits 4 through
     7 of INFO_3 (input from the EGA feature connector) are copied to bits
     0 through 3 of CH. Bits 0 through 3 of INFO_3 (configuration switch
     settings) are copied to bits 0 through 3 of CL.


     BL = 20H
     When BL = 20H on the MCGA, the EGA, and the VGA, the BIOS points the
     interrupt 5 vector at 0000:0014 to an alternate Print Screen routine
     contained in the video ROM BIOS. The difference between this routine
     and the default planar BIOS routine is that the video ROM version uses
     the Video Display Data Area variable ROWS to determine the number of
     character rows to print. The PC/XT and PC/AT planar BIOS versions
     always print 25 rows.


     BL = 30H
     When BL = 30 on the VGA, the BIOS routine updates bits 0-3 of the
     INFO_3 byte (0040:0088) and bits 7 and 4 of the Flags byte at
     0040:0089. INT 10H function 0 refers to INFO_3 and the Flags byte to
     determine whether to configure the video subsystem for a 200-line,
     350-line, or 400-line mode when it establishes an alphanumeric video
     mode. You can thus select among 200-line, 350-line, and 400-line
     alphanumeric modes by first executing INT 10H function 12H with BL =
     30H and AL = 0, 1, or 2, and then calling INT 10H function 0 to set
     the video mode.

     This function normally returns the value 12H in AL. If the VGA is
     inactive (bit 3 of INFO is set to 1), the function returns with
     AL = 0.


     BL = 31H
     When BL = 31H on the MCGA or VGA, the BIOS routine updates bit 3 of
     the Flags byte at 0040:0089 to indicate whether ROM BIOS default
     palette values should be loaded when a video mode is selected using
     INT 10H function 0. If the value 0 is passed in AL, bit 3 of the Flags
     byte is set to 0 to enable default palette setting. If AL = 1, bit 3
     is set to 1 to disable default palette setting.

     When a valid value is passed in AL, the function returns with AL =
     12H.

     BL = 32H
     When BL = 32H on the MCGA or the VGA, the value in AL specifies
     whether CPU access to the video buffer and I/O ports is enabled (AL =
     0) or disabled (AL = 1). Although the hardware interface for control
     of video addressing differs on the MCGA, the VGA, and the VGA Adapter,
     this BIOS function is the same in all three subsystems (see Chapter
     2).

     When a valid value is passed in AL, the function returns with AL =
     12H.

     NOTE: Although the EGA video BIOS does not support this function, you
     can control CPU addressing of video RAM on the EGA by updating bit 1
     of the Miscellaneous Output register (3C2H).


     BL = 33H
     When BL = 33H on the MCGA or the VGA, the BIOS routine updates bit 1
     of the Flags byte at 0040:0089 to indicate whether red-green-blue
     color values should be averaged to gray-scale values when INT 10H
     functions 0 and 10H update the video DAC color registers. If the value
     0 is passed in AL, bit 1 of the Flags byte is set to 1 to enable gray-
     scale summing. If AL = 1, bit 1 is set to 0 to disable gray-scale
     summing.

     When a valid value is passed in AL, the function returns with
     AL = 12H.


     BL = 34H
     When BL = 34H on the VGA, the BIOS routine updates bit 0 of INFO
     (0040:0087) to indicate whether BIOS cursor emulation is in effect. If
     the value 0 is passed in AL, bit 0 of INFO is set to 0 to enable
     cursor emulation. If AL = 1, bit 0 is set to 1 to disable cursor
     emulation.

     When a valid value is passed in AL, the function returns with
     AL = 12H.


     BL = 35H
     INT 10H function 1AH with BL = 35H provides a set of routines that
     support switching between two PS/2 video subsystems in the same
     computer. In a computer that contains two different PS/2-compatible
     video subsystems, calls to this function let a program separately
     access the video BIOS on a video adapter and the video BIOS on a PS/2
     motherboard.

     When you boot a PS/2 that contains a PS/2-compatible video adapter,
     the adapter subsystem is always the active subsystem by default. To
     use the PS/2's planar (motherboard) subsystem, you must use the
     display switch interface to disable the adapter subsystem and enable
     the planar subsystem.

     You can specify four related subfunctions for function 12H with
     BL = 35H, using the value passed in register AL. The four subfunctions
     are designed to be called in pairs. Subfunctions 0 and 1 should be
     called once each to initialize the BIOS display switch interface and
     to establish a default video mode for the planar video subsystem.
     Subsequent calls to subfunctions 2 and 3 then let you switch between
     the two video subsystems.

     When AL = 0, the adapter BIOS initializes the display switch
     interface. First, the adapter BIOS calls the motherboard BIOS to set
     bit 6 of the Flags byte at 0040:0089 to 1 to indicate that the
     interface is supported. Next, the current Video Display Data Area and
     video interrupt vectors are preserved in the 128-byte buffer whose
     address is passed in ES:DX, and the video interrupt vectors are
     redirected to the motherboard BIOS. Finally, the adapter's video
     buffer and control port addressing are disabled (see INT 10H function
     12H, BL = 32H).

     When AL = 1, the motherboard BIOS establishes a default 80-by-25
     alphanumeric mode on the planar video subsystem.

     When AL = 2 and bit 6 of the Flags byte is 1, the contents of the
     Video Display Data Area and video interrupt vectors are copied to the
     128-byte buffer whose address is passed in ES:DX, and the video
     interrupt vectors are redirected to the currently inactive BIOS. Then
     video buffer and control port addressing are disabled for the
     currently active subsystem. A call to this subfunction should normally
     be followed by a call with AL = 3.

     When AL = 3 and bit 6 of the Flags byte is 1, the contents of the
     Video Display Data Area and interrupt vectors are restored from the
     buffer whose address is in ES:DX. (This buffer should contain
     information previously saved by a call with AL = 0 or AL = 2.) Then
     video buffer and control port addressing are enabled, using the
     restored video information.

     When a valid value is passed in AL, and when both the adapter BIOS and
     the planar BIOS support the display switch interface, each of the four
     subfunctions returns with AL = 12H.

     NOTE: The PS/2 Model 30 BIOS (dated 12/12/86 and earlier) and the PS/2
     Model 25 BIOS (dated 6/26/87) contain a bug that makes the display
     switch interface unusable. The problem should be corrected in later
     BIOS versions.


     BL = 36H
     When BL = 36H on the VGA, the value in AL specifies whether the BIOS
     routine enables (AL = 0) or disables (AL = 1) video refresh.
     (Temporarily disabling video refresh can speed software that performs
     repeated video memory accesses.) Bit 5 of the VGA's Sequencer Clocking
     Mode register (01H) controls whether video refresh is enabled or
     disabled. When the value 0 is passed in AL, bit 5 is set to 0 to
     enable video refresh; when AL is 1, bit 5 is set to 1 to disable video
     refresh.

     The function always returns with AL = 12H.

     To obtain EGA configuration information, call INT 10H function 12H
     with BL = 10H:

        mov  ah,12h
        mov  bl,10h
        int  10h

     To vector the EGA BIOS alternate Print Screen routine, call INT 10H
     function 12H with BL = 20H:

        mov  ah,12h
        mov  bl,20h
        int  10h

     To implement display switching between a VGA Adapter and the MCGA in a
     PS/2 Model 30:

; save areas for video BIOS display switch interface

VGAsave   db   128 dup(?)     ; save area for VGA
MCGAsave  db   128 dup(?)     ; save area for MCGA

; initialize display switching (execute this code only once)

     mov  ax,1200h            ; AH := 12H (INT 10H function number)
                              ; AL := 0
     mov  bl,35h              ; BL := 35H (display switch interface)
     mov  dx,seg VGAsave
     mov  es,dx
     mov  dx,offset VGAsave   ; ES:DX -> save area for VGA BIOS info
     int  10h
     cmp  al,12h
     jne  Error               ; exit if display switching not supported

     mov  ax,1201h
     mov  bl,35h
     int  10h                 ; disable adapter, enable planar video

; switch from planar (MCGA) to adapter (VGA) subsystem

     mov  ax,1202h            ; AL := 2 (switch active
                              ;  video off)
     mov  bl,35h
     mov  dx,seg VGAsave
     mov  es,dx
     mov  dx,offset VGAsave   ; ES:DX -> save area for
                              ;  currently active subsystem
     int  10h

     mov  ax,1203h            ; AL := 3 (switch inactive
                              ;  video on)
     mov  bl,35h
     mov  dx,offset MCGAsave  ; ES:DX -> save area for
                              ;  subsystem to be made active
     int  10h

; (to switch from adapter to planar, interchange VGAsave and
;  MCGAsave in the calls with AL = 2 and AL = 3)




���������������������������������������������������������������������������
Function 13H: Display Character String

Caller registers:

     AH    =    13H
     AL    =    0  BL contains attribute for string. Cursor position
                   not updated.
           =    1  BL contains attribute for string. Cursor position
                   updated.
           =    2  String contains embedded attribute bytes. Cursor
                   position not updated.
           =    3  String contains embedded attribute bytes. Cursor
                   position updated.
     BH    =    video page
     BL    =    attribute
     CX    =    string length
     DH    =    character row
     DL    =    character column
     ES:BP =    address of start of string

Returned values:

     (none)

Video Display Data Area updates:

     0040:0050  CURSOR_POSN

     INT 10H function 13H writes a character string into the video buffer.
     Bell, backspace, linefeed, and carriage-return characters embedded in
     the string are treated as commands rather than displayable characters.
     If the string cannot be displayed in one row of characters, function
     13H wraps the string around to the start of the next line. Function
     13H also scrolls the screen upward as necessary.

     The string is copied from the address you specify in ES:BP to the
     location in the video buffer indicated by registers DH and DL
     (character row and column) and register BH (video page). You must also
     specify the number of characters in the string in register CX.

     Function 13H comprises four subfunctions that are selected according
     to the value in AL. These four subfunctions allow you to select the
     method of specifying display attributes for characters in the string
     and to control the cursor's final position after the string is
     displayed.

     You can specify the attribute used for each character either in BL
     (AL = 0 or 1) or by pairing each character code with its attribute in
     the string itself (AL = 2 or 3). Also, you can indicate whether the
     cursor will stay in place after the string is written (AL = 0 or 2) or
     will move to the character position just past the end of the string
     (AL = 1 or 3).

     In all graphics modes except 320-by-200 256-color mode, setting bit 7
     of the attribute value in BL to 1 causes the BIOS to XOR the string
     into the video buffer.

     The video page specified in BH must be 0 in 320-by-200 4-color mode.

     NOTE: On the PC/AT, the EGA, and the MCGA, linefeed and carriage-
     return characters are always written to the currently displayed video
     page, regardless of the value you specify in BH. If you write a string
     containing any of these control characters to a video page not
     currently displayed, function 13H writes them to the wrong video page.

     The following routine writes the string "Hello, World" into the video
     buffer in video page 0 at row 12, column 34. An attribute value of 7
     is used for all characters in the string.

        mov  ax,1300h               ; AH := 13H (INT 10H function number)
                                    ; AL := 0 (attribute specified in BL,
                                    ;  don't move the cursor)
        mov  bh,0                   ; BH := video page
        mov  bl,7                   ; BL := attribute
        mov  cx,12                  ; CX := number of characters to display
        mov  dh,12                  ; DH := row 12
        mov  dl,34                  ; DL := column 34
        mov  bp,seg HelloString
        mov  es,bp
        mov  bp,offset HelloString  ; ES:BP := string address
        int  10h
        .
        .
        .
HelloString  db      'Hello, World'

     This example displays the digits 1 through 7 in the upper left corner
     of video page 0. The attribute used for each digit corresponds to the
     digit:

        mov  ax,1303h               ; AH := 13H (INT 10H function number)
                                    ; AL := 3 (string contains embedded
                                    ;  attribute bytes, move cursor to end
                                    ;  of string)
        mov  bh,0                   ; BH := video page
        mov  cx,7                   ; CX := number of characters to display
        mov  dx,0                   ; DH := row 0
                                    ; DL := column 0
        mov  bp,seg StringData
        mov  es,bp
        mov  bp,offset StringData   ; ES:BP := address of string
        int  10h
        .
        .
        .
StringData   db      '1',1,'2',2,'3',3,'4',4,'5',5,'6',6,'7',7



���������������������������������������������������������������������������
Function 14H: (PC Convertible only)


Function 15H: (PC Convertible only)


Function 16H: (reserved)


Function 17H: (reserved)


Function 18H: (reserved)



���������������������������������������������������������������������������
Function 19H: (reserved)


Function 1AH: Video Display Combination

Caller registers:

     AH    =    1AH

Return video display combination:

     AL    =    0

     Returned values:
     AL    =    1AH
     BL    =    active display
     BH    =    inactive display

Set video display combination:

     AL    =    1
     BL    =    active display
     BH    =    inactive display

     Returned value:
     AL    =    1AH

Video Display Data Area update:

     0040:008A  DCC byte

     INT 10H function 1AH returns or updates the video BIOS video display
     combination status. This status is represented in the DCC byte at
     0040:008A in the Video Display Data Area. This byte contains an index
     into the ROM BIOS Display Combination Code table, which contains a
     list of byte pairs that specify valid combinations of one or two video
     subsystems. Video subsystems are designated by the following values.

     FFH       Unrecognized video subsystem
       0        No display
       1       MDA with monochrome display
       2       CGA with color display
       3       (reserved)
       4       EGA with color display
       5       EGA with monochrome display
       6       Professional Graphics Controller
       7       VGA with analog monochrome display
       8       VGA with analog color display
       9       (reserved)
     0AH       MCGA with digital color display
     0BH       MCGA with analog monochrome display
     0CH       MCGA with analog color display

     AL = 0
     When AL = 0 on the MCGA or the VGA, the video BIOS routine uses the
     value in the DCC byte as an index into its Display Combination Code
     table and copies the 2-byte table entry into BH and BL. If two video
     subsystems are present, one subsystem must be monochrome and the other
     color; the BIOS routine determines which is active by examining bits 4
     through 5 of EQUIP_FLAG (0040:0010).


     AL = 1
     When AL = 1 on the MCGA or the VGA, the BIOS routine scans the Display
     Combination Code table for the combination specified in BH and BL. If
     the specified combination is found in the table, the DCC byte is
     updated with the appropriate index into the table. If the specified
     combination is not found, 0FFH is stored in the DCC byte.

     When a valid value (0 or 1) is passed in AL, INT 10H function 1AH
     returns with AL = 1AH.

     The following sequence returns the display combination in registers BH
     and BL.

        mov  ax,1A00h               ; AH := 1AH (INT 10H function number)
                                    ; AL := 0
        int  10h
        cmp  al,1AH
        jne  ErrorExit              ; jump if function not supported
                                    ; at this point BL = active display
                                    ; BH = inactive display

     If this sequence is executed on a PS/2 Model 30 with an analog
     monochrome display attached to the MCGA and a monochrome display
     attached to an MDA, the values returned are:

      AL    =    1AH
      BL    =    0BH  (active display = MCGA with analog monochrome)
      BH    =      1  (inactive display = MDA with digital monochrome)



���������������������������������������������������������������������������
Function 1BH: Video BIOS Functionality/State Information

Caller registers:

     AH    =    1BH
     BX    =    implementation type (must be 0)
     ES:DI =    address of 64-byte buffer

Returned values:


     ES:DI =    buffer updated with function and state information
     AL    =    1BH

Video Display Data Area updates:

    (none)

     INT 10H function 1BH returns a table of video BIOS state information
     on the MCGA and the VGA. The table contains dynamic information (shown
     in Figure A-14) that is determined when function 1BH is invoked, as
     well as static information (shown in Figure A-15) describing the
     capabilities of the video BIOS itself.

     The dynamic information is copied into the 64-byte buffer whose
     address is passed to the BIOS routine in ES:DI. The 32-bit address of
     the static information table is returned as bytes 0 through 3 of the
     dynamic information table.

     When called with BX = 0, INT 10H function 1BH always returns with
     AL = 1BH.


����������������������������������������������������������������������������ķ
Offset    Data Type        Description
���������������������������������������������������������������������������
0         Dword            Address of static functionality table
4         Byte             Video mode
5         Word             Number of displayed character columns
7         Word             Length of displayed portion of video buffer
                             in bytes
9         Word             Start address of upper left corner of video
                             buffer
0BH       16-byte array    Table of cursor locations (column, row) for
                             eight video pages
Offset    Data Type        Description
                             eight video pages
1BH       Byte             Cursor end line
1CH       Byte             Cursor start line
1DH       Byte             Active video page
1EH       Word             I/O port for CRTC Address register
20H       Byte             CRT_MODE_SET (current value of 3x8H register)
21H       Byte             CRT_PALETTE (current value of 3x9H register)
22H       Byte             Number of displayed character rows
23H       Word             POINTS (height of displayed character matrix)
25H       Byte             Active display combination code
26H       Byte             Inactive display combination code
27H       Word             Number of displayed colors (0 for monochrome)
29H       Byte             Number of video pages supported
2AH       Byte             Raster scan lines:
                             0: 200 lines
                             1: 350 lines
                             2: 400 lines
                             3: 480 lines
2BH       Byte             Alphanumeric character table used when attribute
                              bit 3 is 0 (VGA only)
Offset    Data Type        Description
                              bit 3 is 0 (VGA only)
2CH       Byte             Alphanumeric character table used when attribute
                             bit 3 is 1 (VGA only)
2DH       Byte             Miscellaneous state information (bits are set
                             to 1 if state is true)
                           Bit 0: all modes active on all video subsystems
                             (always 0 on MCGA)
                           Bit 1: gray-scale summing enabled
                           Bit 2: monochrome display attached
                           Bit 3: default palette loading disabled
                           Bit 4: cursor emulation enabled
                           Bit 5: blinking attribute enabled
                           (bits 6-7 reserved)
2EH       Byte             (reserved)
2FH       Byte             (reserved)
30H       Byte             (reserved)
31H       Byte             Video RAM available
                             0: 64K
                             1: 128K
                             2: 192K
Offset    Data Type        Description
                             2: 192K
                             3: 256K
32H       Byte             Save area status (bits are set to 1 if state
                             is true)
                           Bit 0: two alphanumeric character sets are
                             active (VGA only)
                           Bit 1: dynamic save area is active
                           Bit 2: alphanumeric character set override is
                             active
                           Bit 3: graphics character set override is active
                           Bit 4: palette override is active
                           Bit 5: display combination code extension is
                             active
                           (bits 6-7 reserved)
33H through 3FH            (reserved)

     Figure A-14.  Dynamic video state table returned by INT 10H
     function 1BH.


����������������������������������������������������������������������������ķ
Offset    Data Type      Description
��������������������������������������������������������������������������
0         Byte           Video modes supported (bits = 1 if a mode is
                           supported)
                           Bit 0: mode 0
                           Bit 1: mode 1
                           Bit 2: mode 2
                           Bit 3: mode 3
                           Bit 4: mode 4
                           Bit 5: mode 5
                           Bit 6: mode 6
                           Bit 7: mode 7
1         Byte           Video modes supported (bits = 1 if a mode is
                           supported)
                           Bit 0: mode 8
                           Bit 1: mode 9
                           Bit 2: mode 0AH
                           Bit 3: mode 0BH
                           Bit 4: mode 0CH
                           Bit 5: mode 0DH
Offset    Data Type      Description
                           Bit 5: mode 0DH
                           Bit 6: mode 0EH
                           Bit 7: mode 0FH
2         Byte           Video modes supported (bits = 1 if a mode is
                           supported)
                           Bit 0: mode 10H
                           Bit 1: mode 11H
                           Bit 2: mode 12H
                           Bit 3: mode 13H
                           Bit 4: (reserved)
                           Bit 5: (reserved)
                           Bit 6: (reserved)
                           Bit 7: (reserved)
3         Byte           (reserved)
4         Byte           (reserved)
5         Byte           (reserved)
6         Byte           (reserved)
7         Byte           Scan lines available in alphanumeric modes
                           (bits = 1 if supported)
                           Bit 0: 200 lines
Offset    Data Type      Description
                           Bit 0: 200 lines
                           Bit 1: 350 lines
                           Bit 2: 400 lines
8         Byte           Maximum number of displayable alphanumeric
                           character sets
9         Byte           Number of available alphanumeric character
                           definition tables in character generator RAM
0AH       Byte           Miscellaneous video BIOS capabilities (bits = 1
                           if available)
                           Bit 0: all modes on all monitors (INT 10H
                             function 0) (Note: This bit is always 0 on
                             MCGA)
                           Bit 1: gray-scale summing (INT 10H function 10H
                             and 12H)
                           Bit 2: character set loading (INT 10H function
                             11H)
                           Bit 3: default palette loading (INT 10H
                             function 0)
                           Bit 4: cursor emulation (INT 10H function 1)
                           Bit 5: 64-color palette (INT 10H function 10H)
Offset    Data Type      Description
                           Bit 5: 64-color palette (INT 10H function 10H)
                           Bit 6: video DAC loading (INT 10H function 10H)
                           Bit 7: control of video DAC via Attribute
                             Controller Color Select (INT 10H function 10H)
0BH       Byte           Miscellaneous video BIOS capabilities (bits = 1
                           if available)
                           Bit 0: light pen support (INT 10H function 4)
                           Bit 1: save/restore video state (INT 10H
                             function 1CH)
                           Bit 2: blinking/background intensity (INT 10H
                             function 10H)
                           Bit 3: Display Combination Code (INT 10H
                             function 1AH)
                           (bits 4-7 reserved)
0CH       Byte           (reserved)
0DH       Byte           (reserved)
0EH       Byte           Save area capabilities
                           Bit 0: multiple alphanumeric character sets
                           Bit 1: dynamic save area
                           Bit 2: alphanumeric character set override
Offset    Data Type      Description
                           Bit 2: alphanumeric character set override
                           Bit 3: graphics character set override
                           Bit 4: palette override
                           Bit 5: Display Combination Code extension
                           (bits 6-7 reserved)
0FH       Byte           (reserved)

     Figure A-15.  Static functionality table. This table's address is
     returned by INT 10H function 1BH. The table describes the capabilities
     of the ROM BIOS in the video subsystem.


     The following sequence returns video BIOS state information in the
     buffer whose address is passed in ES:DI.

        mov     ax,1B00h                ; AH := 1BH (INT 10H function
                                        ;         number)
                                        ; AL := 0
        mov     bx,0                    ; BX := 0 (Implementation type)
        mov     di,seg StateTable
        mov     es,di
        mov     di,offset StateTable    ; ES:DI -> buffer
        int     10h
        cmp     al,1BH
        jne     ErrorExit               ; jump if function not supported
        .
        .                               ; at this point StateTable contains
        .                               ;  the dynamic information table
StateTable db 64 dup(?)



���������������������������������������������������������������������������
Function 1CH: Save or Restore Video State

Caller registers:

     AH    =    1CH

Return save/restore buffer size:

     AL    =    0
     CX    =    requested states
                Bit 0:       video hardware state
                Bit 1:       video BIOS data areas
                Bit 2:       video DAC state
                Bits 3-0FH:  reserved

     Returned values:
     AL    =    1CH
     BX    =    buffer size in 64-byte blocks

Save requested state(s):

     AL    =    1
     CX    =    requested states (as above)
     ES:BX =    buffer address

Restore requested state(s):

     AL    =    2
     CX    =    requested states (as above)
     ES:BX =    buffer address

Video Display Data Area updates:

     (see below)

     INT 10H function 1CH, supported only on the VGA, lets you save and
     restore the state of the video hardware and video ROM BIOS. INT 10H
     function 1CH comprises three subfunctions selected by the value passed
     in AL. For each subfunction, you must set the low-order three bits in
     CX to indicate the combination of video subsystem states you wish to
     save or restore. You must also pass the address of a save/restore
     buffer in ES:BX whenever you use function 1CH to save or restore the
     video state.


     AL = 0
     When AL = 0, function 1CH returns the size of the buffer required to
     store the state information for states requested in CX. The value
     returned in BX is in 64-byte blocks.

     Function 1CH returns AL = 1CH when called with AL = 0 and at least one
     of the low-order three bits in CX set to 1.


     AL = 1
     When AL = 1, function 1CH copies the state information requested in CX
     into the buffer whose address is passed in ES:BX.


     AL = 2
     When AL = 2, function 1CH restores the video hardware state, the BIOS
     state, or both using information saved in the buffer whose address is
     passed in ES:BX.

     NOTE: The BIOS routine may modify the current video state as it
     executes function 1CH. If you plan not to change the video state after
     saving it with function 1CH, restore the video state immediately
     afterward (using function 1CH with  AL = 2) to ensure that it isn't
     inadvertently modified.

     The following sequence runs under MS-DOS version 2.0 or later. It
     calls MS-DOS INT 21H function 48H to allocate RAM for a save/restore
     buffer. It then calls INT 10H function 1CH to save the current video
     state.

        mov  ax,1C00h               ; AH := 1CH (INT 10H function number)
                                    ; AL := 0
        mov  cx,111b                ; CX := 111b (all three video states)
        int  10h
        cmp  al,1Ch
        jne  ErrorExit              ; jump if function not supported
        shl  bx,1                   ; convert number of 64-byte blocks
        shl  bx,1                   ;  to number of 16-byte blocks
        mov  ah,48h                 ; AH := 48H (MS-DOS INT 21H function
                                    ;        number)
        int  21h                    ; AX := segment of allocated buffer
        jc   ErrorExit              ; jump if error
        mov  es,ax
        xor  bx,bx                  ; ES:BX -> buffer
        mov  cx,111b                ; CX := 111b (all three video states)
        mov  ax,1C01h               ; AH := INT 10H function number
                                    ; AL := 1
        int  10h                    ; save video state in buffer



                    Appendix B  Printing the Screen



     Many computer users find it convenient to "snapshot" the current
     contents of the video display. Although all members of the IBM PC and
     PS/2 series come with a short ROM BIOS routine that dumps the contents
     of the video buffer to a printer, you may need to write your own video
     snapshot program to supplement the ROM routine. This appendix
     discusses how to use the BIOS screen dump utility, as well as why and
     how to write your own.


Alphanumeric Modes


     You invoke the motherboard ROM's alphanumeric screen dump routine by
     executing software interrupt 5. (The ROM BIOS keyboard handler issues
     this interrupt when you press Shift-PrtSc.) This routine copies the
     contents of the currently displayed video page to the printer in
     80-by-25 or 40-by-25 alphanumeric mode. The routine prints only the
     ASCII character codes, ignoring the attribute bytes in the video
     buffer.


EGA, MCGA, VGA

     The EGA, the MCGA, and the VGA ROM BIOS contain a more flexible
     version of the INT 5 screen dump routine. That version uses the Video
     Display Data Area value ROWS (0040:0084) to determine how many rows of
     characters to print. (The motherboard ROM version always prints 25
     rows.) An IBM PC/XT or PC/AT uses the motherboard version by default.
     To make the EGA or VGA ROM BIOS routine accessible through interrupt
     5, call INT 10H function 12H with BL = 20H. This points the interrupt
     5 vector to the more flexible routine.


Block Graphics Characters

     Because most printers are designed to work with many different
     computers, not just IBM PCs, manufacturers do not always design their
     printers to print the same 256 ASCII characters that the video
     hardware displays in alphanumeric modes. In particular, the characters
     used for block graphics are not always available on PC-compatible
     printers. These characters may print differently than they are
     displayed or they may not print at all.


Graphics Modes


     The ROM BIOS does not support screen dumps in graphics modes, so in
     these modes you must use some other program to print the video
     buffer's contents.


GRAPHICS

     GRAPHICS is a RAM-resident graphics-mode screen dump program that
     Microsoft supplies as part of MS-DOS under the name GRAPHICS.COM or
     GRAPHICS.EXE. This program establishes a memory-resident screen dump
     program for CGA graphics modes (320-by-200 4-color and 640-by-200
     2-color) when executed. The program uses an IBM- or Epson-compatible
     dot-matrix printer for output.

     The RAM-resident portion of GRAPHICS traps interrupt 5 and tests the
     current video mode. If a graphics mode is active, it performs the
     screen dump. Otherwise, the BIOS interrupt 5 routine gets control and
     performs the alphanumeric-mode screen dump. Thus, once GRAPHICS.COM or
     GRAPHICS.EXE has been executed, you can obtain a graphics-mode screen
     dump by pressing Shift-PrtSc, just as you would in alphanumeric video
     modes.


Writing a Screen Dump Routine

     If you want screen snapshots in native EGA, VGA, or MCGA graphics
     modes or on a Hercules adapter, or if GRAPHICS produces unsatisfactory
     output on your printer, you can write your own screen dump routine.
     Listing B-1 is an example of a simple routine for CGA graphics modes.
     ScreenDumpCGA can be incorporated into an assembly-language program or
     a high-level-language program by calling it with the appropriate
     register values and memory model. (See Chapter 13 for more on this
     topic.) You might also build ScreenDumpCGA into a Terminate-but-Stay-
     Resident program that, like GRAPHICS, chains into the interrupt 5
     vector and executes whenever Shift-PrtSc is pressed.


���������������������������������������������������������������������������

     Listing B-1.  A simple screen dump routine for the
     CGA.

���������������������������������������������������������������������������


     ScreenDumpCGA copies pixels from the video buffer into an inter-
     mediate print buffer. It formats the print buffer so that its contents
     can be sent directly to the printer (an Epson MX-80 in this example).
     Since the video buffer can be accessed randomly, ScreenDumpCGA reads
     pixels from it in an order that is conveniently transmitted to the
     printer.

     The heart of ScreenDumpCGA is the subroutine TranslatePixels. This
     routine maps pixels from the video buffer into the print buffer. In
     this example, the routine is short and fast, because it uses a simple
     transformation to convert video buffer pixels to printer pixels.
     Because the Epson MX-80 prints vertically oriented groups of pixels
     (see Figure B-1), the easiest way to print an image from  the
     horizontally mapped video buffer is to rotate it by 90 degrees.

     To customize ScreenDumpCGA, concentrate on how best to map pixels from
     the video buffer to your printer. Change the TranslatePixels routine
     to scale or rotate the pixels differently, or modify ScreenDumpCGA to
     change the order in which the contents of the video buffer are copied
     to the printer.


              * ���������������������������Ŀ
              * ������������������������Ŀ  �
              * ���������������������Ŀ  �  �
     Printed  * ������������������Ŀ  �  �  �
     pixels   * ���������������Ŀ  �  �  �  �
              * ������������Ŀ  �  �  �  �  �
              * ���������Ŀ  �  �  �  �  �  �
              * ������Ŀ  �  �  �  �  �  �  �
                        �  �  �  �  �  �  �  �
                        �  �  �  �  �  �  �  �
                        0  1  2  3  4  5  6  7 ��������Bit number

     Figure B-1.  Pixel mapping for a typical dot-matrix graphics printer.
     As the print head moves across the page, it prints eight rows of
     pixels at a time. Each byte of data transmitted to the printer
     controls 8 vertical pixels as shown.


     For example, you could modify ScreenDumpCGA and TranslatePixels  to
     dump the contents of the EGA or VGA video buffer in 640-by-350 16-
     color mode as in Listing B-2. The modified routine prints all nonzero
     pixels in the video buffer as black dots. Note how the Graphics
     Controller's read mode 1 simplifies this task in TranslatePixels.


���������������������������������������������������������������������������

     Listing B-2.  An EGA screen printing routine.

���������������������������������������������������������������������������


RAM-Based Alphanumeric Character Definitions

     You can also modify the graphics-mode screen dump routine to print
     RAM-based characters used in alphanumeric modes on the EGA, MCGA, VGA,
     HGC+, and InColor Card. The technique is to use the character codes
     stored in the displayed portion of the video buffer to index the bit
     patterns in character definition RAM. The bit pattern that defines
     each character can then be used as a dot pattern for the printer.

     As an example, Listing B-3 shows how this can be done for the
     characters defined in the default character definition table in memory
     map 2 on the EGA or VGA. The routine prints each column of characters
     in the video buffer by filling the buffer (PrintBuf) with the bit
     patterns that define each of the characters. Memory map 0 (containing
     the character codes) and map 2 (containing the character definitions)
     are addressed separately in the subroutine TranslatePixels by
     programming the Sequencer and Graphics Controller as discussed in
     Chapter 10.


���������������������������������������������������������������������������

     Listing B-3.  Using RAM-based character definition tables to print
     the character set.

���������������������������������������������������������������������������



                Appendix C  Identifying Video Subsystems



     Programs need to determine the configuration of the video hardware on
     which they are run for two reasons. One is to maintain portability. A
     program that recognizes the video subsystems in the computer in which
     it runs can adapt itself to specific hardware configurations. Imagine,
     for example, a program that displays both text and graphics images.
     This program could display text and graphics on a single screen in a
     computer with only one video subsystem, but it could also take full
     advantage of a dual-display configuration by placing text on one
     screen and graphics on the other.

     Another reason to enable a program to examine its video hardware
     environment is to allow use of the fastest possible video output
     routines. For example, if your program runs in an alphanumeric mode on
     a CGA, you may need to avoid snow by synchronizing with the CRT
     Controller's timing signals. However, this overhead can be avoided if
     the program is running on some other video subsystem. If your program
     "knows" that it's not running on a CGA, it can use faster video output
     routines that omit the overhead of avoiding snow.


CGA and Clones


     Unfortunately, for Color Graphics Adapters and clones, no reliable way
     exists to determine whether the hardware manages conflicts over video
     buffer memory access without display interference (see Chapter 3). If
     your program must run on  a CGA, you might wish to ask the user to
     configure your alphanumeric output routines by testing whether or not
     they produce snow.

     You can also detect whether your program is running on a CGA work-
     alike that does not have the alphanumeric snow problem. If you know
     that your program may run on a CGA work-alike such as the video
     hardware built into a COMPAQ or an AT&T 6300, you can search the ROM
     BIOS for a string indicating the name of the computer, for example,
     "COMPAQ". You might also inspect the ROM BIOS ID byte at F000:FFFE to
     determine whether your program is running on a member of the IBM PC
     family that does not have the snow problem (such as the PCjr).


Other Video Adapters


     Although determining whether a particular CGA or clone has a problem
     with alphanumeric snow can be hard, distinguishing among the various
     common IBM video adapters is relatively easy. Some of the techniques
     described in this appendix rely on serendipitous peculiarities of
     different adapters' firmware or hardware, but all are based on IBM and
     Hercules recommendations.


PS/2s


     On the PS/2s, INT 10H function 1AH lets you determine which video
     subsystems are present and active in the computer (see Appendix A). Of
     course, the PS/2 video BIOS does not recognize non-IBM video adapters.
     For example, if you use a Hercules adapter in a PS/2 Model 30, a
     call to INT 10H function 1AH returns only the information that
     an MDA-compatible adapter is present in the system. Identifying
     the adapter is then up to you.

     VideoID, the routine in Listing C-1, detects the presence of either
     one or two adapters. If two adapters are present, VideoID indicates
     which is active (that is, which one the BIOS is currently using for
     output). The techniques used to identify each adapter are described in
     the listing.


���������������������������������������������������������������������������

     Listing C-1.  A routine to identify PC and PS/2 video
     subsystems.

���������������������������������������������������������������������������


     The VideoID routine checks for adapters by a process of elimination.
     For example, if the routine is run on a PS/2, the INT 10H call returns
     the desired information. On PC/XTs and PC/ATs, if an EGA with a
     monochrome display is detected, there is no reason to look for an MDA
     or a Hercules card in the same system. If a monochrome adapter is
     present, the routine differentiates between the MDA and the various
     Hercules adapters.

       ���ͻ     INT 10H function 1AH on the VGA adapter fails to report
       � T �     the presence of the MCGA when the adapter is installed in
       � I �     a PS/2 Model 30. Also, function 1AH in the MCGA ignores
       � P �     the presence of an EGA if one is installed in a Model 30.
       ���ͼ     If you are concerned about these combinations, you must
                 test for them explicitly after you call INT 10H function
                 1AH. (In the first situation, inspect the motherboard BIOS
                 identfication byte at F000:FFFE to detect the presence of
                 a Model 30. In the second situation, execute INT 10H
                 function 12H with BL = 10H to detect the presence of an
                 EGA.)

     The C program in Listing C-2 demonstrates how you might use VideoID.


���������������������������������������������������������������������������

     Listing C-2.  Calling VideoID from a C program.

���������������������������������������������������������������������������



Glossary


     This glossary includes some of the acronyms, abbreviations, buzzwords,
     engineering terms, and programming jargon that appear frequently
     throughout this book.

     80x86: Refers to all the processors in the Intel 8086 family. The IBM
     PCs and PS/2s all use one of these processors: 8086, 8088, 80286, or
     80386.

     active display: In a computer that contains two video subsystems and
     displays, the display to which a program sends its output.

     adapter: A modular, plug-in circuit that performs a specialized task
     such as generating video output. Well-known IBM PC video adapters
     include the MDA, CGA, HGC, EGA, and VGA Adapter.

     ANSI: American National Standards Institute. One of ANSI's many
     activities is to certify the standardization of programming tools,
     including languages (such as C and FORTRAN) and software interfaces
     (such as GKS).

     APA: All Points Addressable; describes graphics modes on the CGA, EGA,
     and Hercules graphics cards.

     API: Application Program Interface; a set of system-level routines
     that can be used in an application program for basic input and output,
     file management, and so on. In a graphics-oriented operating
     environment like Microsoft Windows, high-level support for video
     graphics output is part of the API.

     ASCII: American Standard Code for Information Interchange. The ASCII
     standard specifies the basic character set used in IBM PCs and PS/2s.

     aspect ratio: The ratio of a video screen's width to its height. A
     typical IBM PC display has an aspect ratio of about 4:3. This term is
     also frequently used to describe pixels: If you think of a pixel as
     being rectangular, its aspect ratio would be the ratio of its width to
     height.

     attributes: Color, intensity, blinking, and other displayed
     characteristics of characters or pixels.

     BIOS: Basic Input/Output System; a low-level programming interface to
     the system's major I/O devices.

     bit plane: Video RAM containing formatted graphics data. In IBM video
     subsystems up to four bit planes can be addressed in parallel, with
     pixel values represented by the bits at corresponding locations in the
     bit planes.

     CGA: IBM's Color Graphics Adapter.

     character code: A numeric code associated with a character. The
     default ASCII character set used in all PCs and PS/2s comprises 256
     8-bit character codes.

     character matrix: The rectangular array of pixels in which characters
     are displayed on the screen. On IBM's Monochrome Display Adapter, each
     character is displayed in a character matrix that is 9 dots wide and
     14 dots high. On the Color Graphics Adapter, the character matrix is 8
     by 8.

     character set: A set of alphabetic and numeric characters and symbols.

     clipping: The process of determining which portions of a graphics
     image lie within a specified boundary.

     code page: A character set designed for use with computers. Each
     character in a code page is associated with a numeric code (such as an
     ASCII or EBCDIC code).

     CPU: Central Processing Unit, or the main processor in a computer. For
     example, the CPU is an Intel 8088 in PCs and an 80286 in PC/ATs.

     CRT: Cathode Ray Tube, or the picture tube you see when you look at
     your computer monitor. Some people refer to the entire monitor (the
     tube and its associated circuitry) as a CRT.

     CRTC: CRT Controller; a chip that controls a video display's timing
     signals.

     DGIS: Direct Graphics Interface Specification; a firmware graphics
     interface designed for video subsystems based on hardware graphics
     coprocessors.

     display: A video monitor.

     driver: Software or firmware that directly programs a specific
     hardware unit such as a video adapter or a printer.

     EBCDIC: Extended Binary Coded Decimal Interchange Code; the character-
     set implementation used on IBM mainframe computers.

     EGA: Enhanced Graphics Adapter.

     font: A description of the style and shapes of the characters in a
     character set.

     gate array: An integrated circuit that is partly prefabricated in its
     manufacture. An application-specific integrated circuit based on gate
     array technology can be less expensive and manufactured more rapidly
     than a custom integrated circuit.

     GKS: Graphical Kernel System; a standard high-level graphics
     interface.

     HGC: Hercules monochrome Graphics Card.

     HGC+ (HGC Plus): Hercules Graphics Card Plus; a monochrome video
     adapter like the HGC, but with a hardware character generator that
     can use RAM-based character sets.

     InColor: Hercules InColor Card; a 16-color version of the HGC+.

     latch: A hardware register external to the CPU and used for transient
     storage of data. For example, the EGA Graphics Controller uses four
     internal 8-bit latches to mediate data transfers between the bit
     planes and the CPU.

     LSI: Large Scale Integration.

     MCGA: Multi-Color Graphics Array; the video subsystem integrated into
     the PS/2 Model 30. Also, Memory Controller Gate Array, one of the
     components of the Model 30's video subsystem.

     MDA: IBM's Monochrome Display Adapter.

     MDPA: Monochrome Display and Printer Adapter; same as an MDA.

     monitor: The hardware that displays your computer's video output;
     comprises a CRT (cathode ray tube) and associated circuitry.

     MPA: Monochrome/Printer Adapter; same as an MDA.

     palette: A range of colors that can be displayed by a video subsystem.

     pel: A pixel.

     PGA: Professional Graphics Adapter; another name for IBM's PGC.

     PGC: IBM's Professional Graphics Controller.

     pixel: One dot or point in an image that is composed of a matrix of
     dots or points. The image on the video screen or on a page printed by
     a dot-matrix printer is composed of a large number of pixels. (The
     word "pixel" is a rough acronym for "picture element.")

     planar BIOS: BIOS routines found in ROM on the IBM PC or PS/2
     motherboard.

     PS/2: Personal System/2.

     PS/2 Display Adapter: A VGA-compatible IBM video adapter that may be
     used in a PC/XT, PC/AT, or PS/2 Model 30; commonly called "VGA
     Adapter."

     raster: The group of closely spaced horizontal scan lines that makes
     up a displayed video image.

     RGB: Red, Green, Blue; the three primary colors displayed by the
     monitors used in PC and PS/2 video subsystems. All other colors are
     blends of these three primaries. Video displays that are driven by
     separate red, green, and blue signals are often called RGB displays.

     scan line: One horizontal line traced across the screen by a CRT's
     electron beam.

     VDI: Computer Graphics Virtual Device Interface; a proposed ANSI
     standard high-level graphics interface. The Graphics Development
     Toolkit (GDT) sold by IBM and Graphics Software Systems is a
     commercial implementation of VDI.

     VGA: Video Graphics Array. People refer to the video subsystem
     integrated into the PS/2 Models 50, 60, and 80, as well as the IBM
     PS/2 Display Adapter, as the "VGA." Strictly speaking, however, the
     VGA is the circuitry in the video subsystem that performs the tasks of
     the CRT Controller, the Sequencer, the Graphics Controller, and the
     Attribute Controller. Most of this circuitry is contained in a single
     VLSI chip.

     VGA Adapter: The IBM PS/2 Display Adapter.

     video buffer: A buffer that contains the data that appears on the
     video display; variously known as a "display buffer," "frame buffer,"
     "refresh buffer," or "regenerative buffer."

     Video Control Data Area: Part of the Video Display Data Area. The
     block of RAM from 0040:0049 through 0040:0066 is Video Control Data
     Area 1; the block between 0040:0084 and 0040:008A is Video Control
     Data Area 2.

     Video Display Data Area: A global data area maintained by the ROM BIOS
     for storage of parameters related to its INT 10H video I/O routines.

     VLSI: Very Large Scale Integration.



     Richard Wilton


     Richard Wilton has been programming computers since the late
     1960s. He has written systems software and graphics applications in
     FORTRAN, Pascal, C, Forth, and assembly language. His articles and
     reviews have appeared in several computer publications, including
     BYTE, Computer Language, and The Seybold Outlook on Professional
     Computing. Wilton lives in Los Angeles, California.



���������������������������������������������������������������������������
Listing 1-1.  SetVmode().
���������������������������������������������������������������������������

                TITLE   'Listing 1-1'
                NAME    SetVmode
                PAGE    55,132

;
; Name:         SetVmode
;
; Function:     Call IBM ROM BIOS to set a video display mode.
;
; Caller:       Microsoft C:
;
;                       void SetVmode(n);
;
;                       int n;                  /* video mode */
;

ARGn            EQU     byte ptr [bp+4] ; stack frame addressing

EQUIP_FLAG      EQU     byte ptr ds:[10h]

CGAbits         EQU     00100000b       ; bits for EQUIP_FLAG
MDAbits         EQU     00110000b

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _SetVmode
_SetVmode       PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    ds

                mov     ax,40h
                mov     ds,ax           ; DS -> Video Display Data Area

                mov     bl,CGAbits      ; BL := bits indicating presence
                                        ;  of CGA

                mov     al,ARGn         ; AL := desired video mode number

                mov     ah,al          ; test if desired mode is monochrome
                and     ah,7
                cmp     ah,7
                jne     L01             ; jump if desired mode not 7 or 0Fh

                mov     bl,MDAbits      ; BL := bits indicating presence
                                        ;  of MDA

L01:            and     EQUIP_FLAG,11001111b
                or      EQUIP_FLAG,bl   ; set bits in EQUIP_FLAG

                xor     ah,ah           ; AH := 0 (INT 10h function number)

                push    bp
                int     10h           ; call ROM BIOS to set the video mode
                pop     bp

                pop     ds              ; restore caller registers & return
                mov     sp,bp
                pop     bp
                ret

_SetVmode       ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 1-2.  GetVmode().
���������������������������������������������������������������������������

                TITLE   'Listing 1-2'
                NAME    GetVmode
                PAGE    55,132

;
; Name:         GetVmode
;
; Function:     Call IBM ROM BIOS to set a video display mode.
;
; Caller:       Microsoft C:
;
;                       int     GetVmode();
;

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _GetVmode
_GetVmode       PROC    near

                push    bp         ; preserve caller registers
                mov     bp,sp

                mov     ah,0Fh     ; AH := 0Fh (INT 10h function number)

                push    bp
                int     10h        ; call ROM BIOS to get video mode number
                pop     bp

                xor     ah,ah      ; AX := video mode number

                mov     sp,bp
                pop     bp
                ret

_GetVmode       ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 1-3.  A C program based on SetVmode().
���������������������������������������������������������������������������

/* Listing 1-3 */

main( argc, argv )
int     argc;
char    **argv;
{
      int     ModeNumber;
      void    SetVmode();


      if (argc != 2)                  /* verify command line syntax */
      {
        printf( "\nSyntax:  SETVMODE n\n" );
        exit( 1 );
      }

      sscanf( argv[1], "%x", &ModeNumber ); /* get desired mode number */

      SetVmode( ModeNumber );               /* call ROM BIOS via INT 10h */
}



���������������������������������������������������������������������������
Listing 1-4.  A C program based on GetVmode().
���������������������������������������������������������������������������

/* Listing 1-4 */

main()
{
        int     GetVmode();
        return( GetVmode() );
}



���������������������������������������������������������������������������
Listing 1-5.  Microsoft C's int86() function.
���������������������������������������������������������������������������

/* Listing 1-5 */

#include        "dos.h"

main()
{
        struct  BYTEREGS regs;     /* BYTEREGS defined in dos.h */


        regs.ah = 0x0F;            /* AH=0x0F (ROM BIOS function number) */

        int86( 0x10, &regs, &regs );  /* perform interrupt 10h */

        return( (int)regs.al );
}



���������������������������������������������������������������������������
Listing 2-1.  Reading the 6845 Cursor Location registers.
���������������������������������������������������������������������������

        mov     ax,40h
        mov     es,ax                ; ES := video BIOS data segment
        mov     dx,es:[63h]          ; DX := 3x4h (3B4h or 3D4h)

        mov     al,0Eh
        out     dx,al                ; select 6845 Cursor Location
                                     ;  High register
        inc     dx
        in      al,dx                ; read selected register at 3x5h
        mov     ah,al                ; AH := high byte of cursor
                                     ;  location
        dec     dx
        mov     al,0Fh
        out     dx,al                ; select Cursor Location Low register

        inc     dx
        in      al,dx                ; AX := offset of cursor relative
                                     ;  to start of video buffer

; convert to character row and column

        mov     dx,es:[4Eh]          ; DX := CRT_START (buffer start offset
                                     ;  in bytes)
        shr     dx,1                 ; convert to words
        sub     ax,dx                ; subtract from cursor offset
        div     byte ptr es:[4Ah]    ; divide by CRT_COLS
        xchg    ah,al                ; AH := row, AL := column



���������������������������������������������������������������������������
Listing 2-2.  Timing the horizontal blanking interval on the CGA
���������������������������������������������������������������������������

                TITLE   'Listing 2-2'
                NAME    HRTimeout
                PAGE    55,132

;
; Name:         HRTimeout
;
; Function:     Determine a timeout value for horizontal blanking interval
;
; Caller:       Microsoft C:
;
;                       int HRTimeout();
;

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _HRTimeout
_HRTimeout      PROC    near

                push    bp           ; usual C prologue to establish
                mov     bp,sp        ;  stack frame

                mov     ax,40h
                mov     es,ax        ; ES := video BIOS data segment

                mov     dx,es:[63h]  ; DX := port for CRTC Address register
                add     dl,6         ; DX := port for CRTC Status register

; synchronize with start of refresh cycle

L01:            in      al,dx        ; AL := CRTC status
                test    al,8         ; test bit 3
                jz      L01          ; loop while NOT in vertical retrace

L02:            in      al,dx
                test    al,8
                jnz     L02          ; loop during vertical retrace

; synchronize with a horizontal scan and time the horizontal blanking
;  interval

                mov     cx,0FFFFh    ; CX := loop counter

                cli                  ; disable interrupts

L03:            in      al,dx
                test    al,1
                jnz     L03          ; loop while Display Enable is
                                     ;  inactive

L04:            in      al,dx
                test    al,1
                jz      L04          ; loop while Display Enable is active

L05:            in      al,dx
                test    al,1
                loopnz  L05          ; decrement CX and loop while Display
                                     ;  Enable is inactive

                sti                  ; enable interrupts again

                mov     ax,cx        ; AX := loop counter
                neg     ax
                shl     ax,1         ; AX := timeout value

                mov     sp,bp        ; discard stack frame and return to C
                pop     bp
                ret

_HRTimeout      ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 2-3.  Updating the EGA or VGA Attribute Controller
���������������������������������������������������������������������������

; program the Attribute Controller directly

        mov     ax,40h
        mov     es,ax                ; ES := video BIOS data segment
        mov     dx,es:[63h]          ; DX := 3x4h (3B4h or 3D4h)
        add     dl,6                 ; DX := 3xAh (CRT Status Register)

        cli                          ; clear the interrupts
        in      al,dx                ; reset Attribute Controller flip-flop
        push    dx                   ; preserve Status Reg port

        mov     dl,0C0h              ; DX := 3C0h
        mov     al,RegNumber
        out     dx,al                ; write to Address Register
        jmp     $+2                  ; waste a few cycles so that Attribute
                                     ;  Controller can respond
        mov     al,DataValue
        out     dx,al                ; write to data register

        pop     dx                   ; DX := 3xAh
        in      al,dx                ; reset that flip-flop
        mov     dl,0C0h
        mov     al,20h               ; restore palette
        out     dx,al
        sti                          ; enable interrupts


; using the video BIOS

        mov     ax,1000h             ; AH := 10h (INT 10h function number)
                                     ; AL := 0 (Set individual Attribute
                                     ;  Controller register)
        mov     bl,RegNumber
        mov     bh,DataValue
        int     10h



���������������������������������������������������������������������������
Listing 2-4.  Configuring a Hercules adapter for 720-by-348 graphics mode
���������������������������������������������������������������������������

                TITLE   'Listing 2-4'
                NAME    HercGraphMode
                PAGE    55,132

;
; Name:         HercGraphMode
;
; Function:     Establish Hercules 720x348 graphics mode on HGC, HGC+,
;                InColor
;
; Caller:       Microsoft C:
;
;                               void HercGraphMode();
;

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _HercGraphMode
_HercGraphMode  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si
                push    di

; Update Video BIOS Data Area with reasonable values

                mov     ax,40h
                mov     es,ax
                mov     di,49h          ; ES:DI := 0040:0049 (BIOS data
                                        ;  area)

                mov     si,offset DGROUP:BIOSData
                mov     cx,BIOSDataLen
                rep     movsb           ; update BIOS data area

; Set Configuration Switch

                mov     dx,3BFh         ; DX := Configuration Switch port
                mov     al,1            ; AL bit 1 := 0 (exclude 2nd 32K of
                                        ;                 video buffer)
                                       ; AL bit 0 := 1 (allow graphics mode
                out     dx,al          ;                setting via 3B8h)

; Blank the screen to avoid interference during CRTC programming

                mov     dx,3B8h     ; DX := CRTC Mode Control register port
                xor     al,al       ; AL bit 3 := 0 (disable video signal)
                out     dx,al       ; blank the screen

; Program the CRTC

                sub     dl,4        ; DX := CRTC Address reg port 3B4h

                mov     si,offset DGROUP:CRTCParms
                mov     cx,CRTCParmsLen

L01:            lodsw               ; AL := CRTC register number
                                    ; AH := data for this register
                out     dx,ax
                loop    L01

; Set graphics mode

                add     dl,4        ; DX := 3B8h (CRTC Mode Control reg)
                mov     al,CRTMode  ; AL bit 1 = 1 (enable graphics mode)
                                    ;    bit 3 = 1 (enable video)
                out     dx,al

                pop     di          ; restore registers and exit
                pop     si
                mov     sp,bp
                pop     bp
                ret

_HercGraphMode  ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

                                ; These are the parameters recommended by
                                ;  Hercules.
                                ; They are based on 16 pixels/character and
                                ;  4 scan lines per character.

CRTCParms       DB      00h,35h ; Horizontal Total:  54 characters
                DB      01h,2Dh ; Horizontal Displayed:  45 characters
                DB      02h,2Eh ; Horizontal Sync Position:  at 46th
                                ;  character
                DB      03h,07h ; Horizontal Sync Width:  7 character
                                ;  clocks

                DB      04h,5Bh ; Vertical Total:  92 characters
                                ;  (368 lines)
                DB      05h,02h ; Vertical Adjust:  2 scan lines
                DB      06h,57h ; Vertical Displayed:  87 character rows
                                ;  (348 lines)
                DB      07h,57h ; Vertical Sync Position:  after 87th char
                                ;  row
                DB      09h,03h ; Max Scan Line:  4 scan lines per char

CRTCParmsLen    EQU     ($-CRTCParms)/2

BIOSData        DB      7       ; CRT_MODE
                DW      80      ; CRT_COLS
                DW      8000h   ; CRT_LEN
                DW      0       ; CRT_START
                DW      8 dup(0) ; CURSOR_POSN
                DW      0       ; CURSOR_MODE
                DB      0       ; ACTIVE_PAGE
CRTCAddr        DW      3B4h    ; ADDR_6845
CRTMode         DB      0Ah     ; CRT_MODE_SET (value for port 3B8h)
                DB      0       ; CRT_PALETTE (unused)

BIOSDataLen     EQU     $-BIOSData

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 2-5.  Enable or disable video I/O port and buffer addressing
              on an MCGA or VGA.
���������������������������������������������������������������������������

        mov     ah,12h                ; AH := 12h (INT 10h function number)
        mov     al,1                  ; AL := 1 (disable addressing)
                                      ; (use AL = 0 to enable addressing)
        mov     bl,32h                ; INT 10H subfunction number
        int     10h

        cmp     al,12h
        jne     ErrorExit             ; jump if BIOS does not support this
                                      ;  function



���������������������������������������������������������������������������
Listing 3-1.  Resetting the Enable Blink bit on the MDA or CGA.
���������������������������������������������������������������������������

        mov     ax,40h
        mov     es,ax           ; ES := video BIOS data segment
        mov     dx,es:[63h]     ; DX := 3B4h (MDA) or
                                ;  3D4h (CGA) from ADDR_6845
        add     dl,4            ; DX := 3x8h (CRT Mode Control reg)
        mov     al,es:[65h]     ; AL := current value of reg (CRT_MODE_SET)
        and     al,11011111b    ; zero bit 5
        out     dx,al           ; update the register
        mov     es:[65h],al     ; update the BIOS data area



���������������������������������������������������������������������������
Listing 3-2.  Setting and resetting the Enable Blink bit on
              the MCGA, EGA, or VGA.
���������������������������������������������������������������������������

        mov     bl,0                    ; BL := value for Enable Blink bit
        mov     ax,1003h                ; AH := INT 10H function number
                                        ; AL := subfunction number
        int     10h



���������������������������������������������������������������������������
Listing 3-3.  Palette register programming on the EGA or VGA.
���������������������������������������������������������������������������

; updating apalette register directly:

        mov     ax,40h
        mov     es,ax                  ; ES := video BIOS data segment
        mov     dx,es:[63h]            ; DX := CRTC address reg (3x4h)
        add     dl,6                   ; DX := Status reg (3xAh)
        push    dx                     ;preserve this value
        cli
        in      al,dx                  ; reset Attribute Controller address
                                       ;  flip-flop
        mov     dl,0C0h                ; DX := 3C0h
        mov     al,PaletteRegNumber
        out     dx,al                  ; update one palette register
        mov     al,PaletteRegValue
        out     dx,al
        pop     dx                     ; DX := Status register port
        in      al,dx                  ; reset the flip-flop
        mov     dl,0C0h
        mov     al,20h
        out     dx,al                  ; set bit 5 of
                                       ;  Attribute Controller address reg
        sti


; updating a palette register using the video BIOS

        mov     bl,PaletteRegNumber
        mov     bh,PaletteRegValue
        mov     ax,1000h               ; AH := INT 10H function number
                                       ; AL := subfunction number
        int     10h



���������������������������������������������������������������������������
Listing 3-4.  InColor Exception register programming.
���������������������������������������������������������������������������

        mov     ax,0017h          ; AH bit 5 := 0 (disable
                                  ;  monochrome attributes)
                                  ; AH bit 4 := 0 (disable palette)
                                  ; AH bits 0-3 := 0 (default cursor color)
                                  ; AL := 17h (Exception Register number)
        mov     dx,3B4h           ; DX := I/O port
        out     dx,ax



���������������������������������������������������������������������������
Listing 3-5.  InColor palette register programming.
���������������������������������������������������������������������������

        mov     dx,3B4h               ; DX := CRTC address register
        mov     al,1Ch                ; AL := 1Ch (Palette Register number)
        out     dx,al
        inc     dx                    ; DX := 3B5h
        in      al,dx                 ; reset palette register index

        mov     si,offset PaletteTable ; DS:SI ->Palette Table
        mov     cx,16                 ; CX := number of palette registers
L01:    lodsb                         ; AL := next byte from table
        out     dx,al                 ; update next palette reg
        loop    L01
        .
        .
        .
PaletteTable    db     00h,01h,02h,03h,04h,05h,06h,07h ;palette regs 0-7
                db     38h,39h,3Ah,3Bh,3Ch,3Dh,3Eh,3Fh ;palette regs 8-0Fh



���������������������������������������������������������������������������
Listing 3-6.  Loading an alternative MCGA monochrome gray-scale palette.
���������������������������������������������������������������������������

        mov     bx,0Fh                 ; BX := first video DAC
                                       ;  Color register number
        mov     di,offset VDACTable    ; DS:DI ->table

L01:    mov     dh,[bx+di]             ; DH := red value
        mov     ch,dh
        mov     cl,dh                  ; green and blue values are the same
        mov     ax,1010h               ; AH := INT 10h function number
                                       ; AL := subfunction number
        int     10h
        dec     bx
        jns     L01                    ; loop from register 0FH through
                                       ;  register 0
        .
        .
        .
VDACTable       db      00h,05h,08h,0Bh,0Eh,11h,14h,18h
                db      1Ch,20h,24h,28h,2Dh,32h,38h,3Fh



���������������������������������������������������������������������������
Listing 3-7.  Loading an alternative VGA monochrome gray-scale palette.
���������������������������������������������������������������������������

        mov     bx,0Fh                ; BX := first Palette register number
        mov     di,offset VDACTable   ; DS:DI -> table

L01:    mov     dh,[bx+di]            ; DH := red value
        mov     ch,dh
        mov     cl,dh                 ; green and blue values are the same

        push    bx                    ; preserve Palette register number
        mov     ax,1007h              ; AH := INT 10h function number
                                      ; AL := subfunction number
                                      ;  (read Palette register)
        int     10h                   ; BH := Palette register value
        mov     bl,bh
        xor     bh,bh                 ; BX := desired video DAC
                                      ; Color register number

        mov     ax,1010h              ; AH := INT 10h function number
                                      ; AL := subfunction number
        int     10h
        pop     bx
        dec     bx                    ; BX := next Palette register number
        jns     L01                   ; loop from Palette registers
                                      ;  0FH through 0
        .
        .
        .
VDACTable       db      00h,05h,08h,0Bh,0Eh,11h,14h,18h
                db      1Ch,20h,24h,28h,2Dh,32h,38h,3Fh



���������������������������������������������������������������������������
Listing 3-8.  Setting a border color.
���������������������������������������������������������������������������

; updating the CRT Color Register directly (CGA only)

        mov     ax,40h
        mov     es,ax                   ; ES := video BIOS data segment
        mov     dx,es:[63h]             ; DX := 3D4H (ADDR_6845)
        add     dl,5                    ; DX := 3D9H (CRT Color Select reg)
        mov     al,es:[66h]             ; AL := current value of reg
                                        ;  (CRT_PALETTE)
        and     al,11110000b            ; zero bits 0-3
        or      al,BorderValue          ; update bits 0-3
        out     dx,al                   ; update the register
        mov     es:[66h],al             ; update the BIOS data area


; using the video BIOS interface (CGA, EGA, VGA)

        mov     bl,BorderValue
        mov     bh,0                    ; BH := subfunction number
        mov     ah,0Bh                  ; AH := INT 10h function number
        int     10h



���������������������������������������������������������������������������
Listing 3-9.  Display alphanumeric text on the CGA by blanking the display.
���������������������������������������������������������������������������

                TITLE   'Listing 3-9'
                NAME    DisplayText
                PAGE    55,132

;
; Name:         DisplayText
;
; Function:     Display an alphanumeric string without interference on the
;                CGA
;
; Caller:       Microsoft C:
;
;                       int DisplayText1(buf,n,offset);
;
;                       char *buf;           /* buffer containing text in
;                                               CGA alphanumeric format
;                                               (alternating character
;                                               codes and attribute
;                                               bytes) */
;
;                       int n;               /* buffer length in bytes */
;
;                       unsigned int offset; /* offset into video buffer */
;

Set80X25        EQU     (1 SHL 0)       ; bit masks for Mode Control
                                        ;  Register
Set320X200      EQU     (1 SHL 1)
BlackAndWhite   EQU     (1 SHL 2)
EnableVideo     EQU     (1 SHL 3)
Set640X200      EQU     (1 SHL 4)
EnableBlink     EQU     (1 SHL 5)

ARGbuf          EQU     word ptr [bp+4] ; stack frame addressing
ARGn            EQU     word ptr [bp+6]
ARGoffset       EQU     word ptr [bp+8]
TIMEOUT         EQU     6               ; Horizontal timeout loop limit

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _DisplayText
_DisplayText    PROC    near

                push    bp              ; usual C prologue to establish
                mov     bp,sp           ;  stack frame and preserve
                                        ;  registers
                push    di
                push    si

                mov     ax,0B800h
                mov     es,ax
                mov     di,ARGoffset    ; ES:DI -> destination in video
                                        ;  buffer
                mov     si,ARGbuf       ; DS:SI -> source buffer
                mov     bx,ARGn
                shr     bx,1            ; BX := buffer length in words

                mov     dx,3DAh         ; DX := CGA Status Port

; wait for start of vertical blanking interval

L01:            mov     cx,TIMEOUT      ; CX := loop counter (timeout
                                        ;  value)

L02:            in      al,dx           ; AL := video status
                test    al,8
                jnz     L02             ; loop if vertical sync active
                test    al,1
                jz      L02             ; loop if Display Enable active

                cli                     ; disable interrupts

L03:            in      al,dx
                test    al,1
                loopnz  L03             ; loop until end of horizontal
                                        ;  blanking or timeout

                sti                     ; reenable interrupts

                jz      L01             ; loop if no timeout

; blank the display

                mov     dl,0D8h         ; DX := 3D8h (Mode Control
                                        ;  register)
                mov     al,(Set80X25 OR EnableBlink)
                out     dx,al           ; turn video off

; copy the data to the video buffer

                mov     cx,bx           ; CX := buffer length in words
                rep     movsw

; reenable the display

                or      al,EnableVideo
                out     dx,al

                pop     si              ; usual C epilogue to restore
                pop     di              ;  registers and discard
                                        ;  stack frame
                mov     sp,bp
                pop     bp
                ret

_DisplayText    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 3-10.  Display alphanumeric text on the CGA during horizontal
               and vertical blanking intervals.
���������������������������������������������������������������������������

                TITLE   'Listing 3-10'
                NAME    DisplayText
                PAGE    55,132

;
; Name:         DisplayText
;
; Function:     Display an alphanumeric string without interference on the
;                CGA
;
; Caller:       Microsoft C:
;
;                       int DisplayText(buf,n,offset);
;
;                       char *buf;            /* buffer containing text
;                                                in CGA alphanumeric
;                                                format (alternating
;                                                character codes and
;                                                attribute bytes) */
;
;                       int n;                /* buffer length in bytes */
;
;                       unsigned int offset; /* offset into video buffer */
;
ARGbuf          EQU     word ptr [bp+4]
ARGn            EQU     word ptr [bp+6]

ARGoffset       EQU     word ptr [bp+8]
TIMEOUT         EQU     6               ; horizontal timeout loop limit
VBcount         EQU     250             ; number of words to write during
                                        ;  vertical blanking interval

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _DisplayText
_DisplayText    PROC    near

                push    bp              ; usual C prologue to establish
                mov     bp,sp           ; stack frame and preserve
                                        ;  registers
                push    di
                push    si

                mov     ax,0B800h
                mov     es,ax
                mov     di,ARGoffset    ; ES:DI -> destination in video
                                        ;  buffer
                mov     si,ARGbuf       ; DS:SI -> source buffer
                mov     cx,ARGn
                shr     cx,1            ; CX := buffer length in words

                mov     dx,3DAh         ; DX := CGA Status Port

; write during remaining vertical blanking interval

L01:            mov     bx,cx           ; preserve buffer length in BX
                mov     cx,TIMEOUT      ; CX := horizontal timeout
                cli                     ; disable interrupts during loop

L02:            in      al,dx           ; AL := video status
                test    al,1
                loopnz  L02             ; loop while Display Enable
                                        ;  inactive
                jz      L03             ; jump if loop did not time out

                movsw                   ; copy one word
                sti
                mov     cx,bx           ; CX := buffer length
                loop    L01

                jmp     short L10       ; exit (entire string copied)

; write during horizontal blanking intervals

L03:            sti
                mov     cx,bx           ; restore CX

L04:            lodsw                   ; AL := character code
                                        ; AH := attribute
                mov     bx,ax           ; BX := character and attribute

                push    cx              ; preserve word loop counter
                mov     cx,TIMEOUT      ; CX := timeout loop limit

                cli                     ; clear interrupts during one
                                        ;  scan line

L05:            in      al,dx
                test    al,1
                loopnz  L05             ; loop during horizontal blanking
                                        ;  until timeout occurs
                jnz     L07             ; jump if timed out (vertical
                                        ;  blanking has started)
L06:            in      al,dx
                test    al,1
                jz      L06             ; loop while Display Enable is
                                        ;  active

                xchg    ax,bx           ; AX := character & attribute
                stosw                   ; copy 2 bytes to display buffer

                sti                     ; restore interrupts
                pop     cx              ; CX := word loop counter
                loop    L04

                jmp     short L10       ; exit (entire string copied)

; write during entire vertical blanking interval

L07:            pop     bx              ; BX := word loop counter
                dec     si
                dec     si              ; DS:SI -> word to copy from buffer

                mov     cx,VBcount      ; CX := # of words to copy
                cmp     bx,cx
                jnb     L08             ; jump if more than VBcount words
                                        ;  remain
                                        ;  in buffer
                mov     cx,bx           ; CX := # of remaining words in
                                        ;  buffer
                xor     bx,bx           ; BX := 0
                jmp     short L09

L08:            sub     bx,cx           ; BX := (# of remaining words) -
                                        ;  VBcount

L09:            rep     movsw           ; copy to video buffer

                mov     cx,bx           ; CX := # of remaining words
                test    cx,cx
                jnz     L01             ; loop until buffer is displayed

L10:            pop     si              ; usual C epilogue to restore
                                        ;  registers
                pop     di              ;  and discard stack frame
                mov     sp,bp
                pop     bp
                ret

_DisplayText    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 3-11.  Setting the CRTC Start Address registers.
���������������������������������������������������������������������������

        mov     ax,40h
        mov     es,ax               ; ES := video BIOS data segment
        mov     dx,es:[63h]         ; DX := ADDR_6845

        mov     al,0Ch              ; AL := reg number (Start Address High)
        out     dx,al
        inc     dx                  ; DX := 3x5h
        mov     al,HiByte           ; AL := high-order byte of start offset
        out     dx,al
        dec     dx                  ; DX := 3x4h

        mov     al,0Dh              ; AL := reg number (Start Address Low)
        out     dx,al
        inc     dx                  ; DX := 3x5h
        mov     al,LoByte           ; AL := low-order byte of start offset
        out     dx,al
        mov     ah,HiByte           ; AX := start offset in words

        shl     ax,1                ; AX := offset in bytes
        mov     es:[4Eh],ax         ; update CRT_START



���������������������������������������������������������������������������
Listing 3-12.  Video page selection using the ROM BIOS.
���������������������������������������������������������������������������

        mov     al,Vpage                ; AL := video page number
        mov     ah,5                    ; AH := INT 10h function number
        int     10h



���������������������������������������������������������������������������
Listing 3-13.  Setting the cursor size.
���������������������������������������������������������������������������

; updating the CRTC registers directly

        mov     ax,40h
        mov     es,ax                   ; ES := video BIOS data segment
        mov     dx,es:[63h]             ; DX := ADDR_6845

        mov     al,0Ah                  ; AL := reg number (Cursor Start)
        out     dx,al
        inc     dx                      ; DX := 3x5h
        mov     al,TopLine              ; AL := top scan line for cursor
        out     dx,al
        dec     dx                      ; DX := 3x4h

        mov     al,0Bh                  ; AL := reg number (Cursor End)
        out     dx,al
        inc     dx                      ; DX := 3x5h
        mov     al,BottomLine           ; AL := bottom scan line for cursor
        out     dx,al

        mov     ah,TopLine              ; AX := top and bottom lines
        mov     es:[60h],ax             ; update CURSOR_MODE


; using the video BIOS interface

        mov     ch,TopLine
        mov     cl,BottomLine
        mov     ah,1                    ; AH := INT 10h function number
        int     10h



���������������������������������������������������������������������������
Listing 3-14.  Setting the cursor location.
���������������������������������������������������������������������������

; updating the CRTC registers directly

        mov     ax,40h
        mov     es,ax             ; ES := video BIOS data segment
        mov     dx,es:[63h]       ; DX := ADDR_6845

        mov     al,0Eh            ; AL := reg number (Cursor Location High)
        out     dx,al
        inc     dx                ; DX := 3x5h
        mov     al,HiByte         ; AL := high-order byte of cursor offset
        out     dx,al
        dec     dx                ; DX := 3x4h

        mov     al,0Fh            ; AL := reg number (Cursor Location Low)
        out     dx,al
        inc     dx                ; DX := 3x5h
        mov     al,LoByte         ; AL := low-order byte of cursor offset
        out     dx,al


; using the video BIOS interface

        mov     dh,CursorRow
        mov     dl,CursorColumn
        mov     bh,VideoPage
        mov     ah,2              ; AH := INT 10h function number
        int     10h



���������������������������������������������������������������������������
Listing 3-15.  An invisible alphanumeric cursor for IBM video subsystems.
���������������������������������������������������������������������������

        mov     cx,2000h                ; CH := top scan line for cursor
                                        ; CL := bottom scan line for cursor
        mov     ah,1                    ; AH := INT 10h function number
        int     10h



���������������������������������������������������������������������������
Listing 4-1.  Computing a pixel's address in 320-by-200 4-color mode
���������������������������������������������������������������������������

                TITLE   'Listing 4-1'
                NAME    PixelAddr04
                PAGE    55,132

;
; Name:         PixelAddr04
;
; Function:     Determine buffer address of pixel in 320x200 4-color mode
;
; Caller:       AX = y-coordinate (0-199)
;               BX = x-coordinate (0-319)
;
; Returns:      AH = bit mask
;               BX = byte offset in buffer
;               CL = number of bits to shift left
;               ES = video buffer segment
;


OriginOffset    EQU     0               ; byte offset of (0,0)
VideoBufferSeg  EQU     0B800h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT
                PUBLIC  PixelAddr04
PixelAddr04     PROC    near

                mov     cl,bl           ; CL := low-order byte of x

                xchg    ah,al           ; AX := 100h * y
                shr     ax,1            ; AL := 80h * (y&1)
                add     bh,al           ; BX := x + 8000h*(y&1)
                xor     al,al           ; AX := 100h*(y/2)
                add     bx,ax           ; BX := x + 8000h*(y&1) +
                                        ;  100h*(y/2)
                shr     ax,1
                shr     ax,1            ; AX := 40h*(y/2)
                add     bx,ax           ; BX := x + 8000h*(y&1) +
                                        ;  140h*(y/2)
                shr     bx,1
                shr     bx,1            ; BX := x/4 + 2000h*(y&1) +
                                        ;  50h*(y/2)
                add     bx,OriginOffset ; BX := byte offset in video buffer

                mov     ax,VideoBufferSeg
                mov     es,ax           ; ES:BX := byte address of pixel

                mov     ah,3            ; AH := unshifted bit mask
                and     cl,ah           ; CL := x & 3
                xor     cl,ah           ; CL := 3 - (x & 3)
                shl     cl,1            ; CL := # bits to shift left

                ret

PixelAddr04     ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 4-2.  Computing a pixel's address in 640-by-200 2-color mode
���������������������������������������������������������������������������

                TITLE   'Listing 4-2'
                NAME    PixelAddr06
                PAGE    55,132

;
; Name:         PixelAddr06
;
; Function:     Determine buffer address of pixel in 640x200 2-color mode
;
; Caller:       AX = y-coordinate (0-199)
;               BX = x-coordinate (0-639)
;
; Returns:      AH = bit mask
;               BX = byte offset in buffer
;               CL = number of bits to shift left
;               ES = video buffer segment
;

OriginOffset    EQU     0               ; byte offset of (0,0)
VideoBufferSeg  EQU     0B800h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  PixelAddr06
PixelAddr06     PROC    near

                mov     cl,bl           ; CL := low-order byte of x

                xchg    ah,al           ; AX := 100h * y
                shr     bx,1            ; BX := x/2
                shr     ax,1            ; AL := 80h*(y&1)
                add     bh,al           ; BX := x/2 + 8000h*(y&1)
                xor     al,al           ; AX := 100h*(y/2)
                add     bx,ax           ; BX := x/2 + 8000h*(y&1) +
                                        ;  100h*(y/2)
                shr     ax,1
                shr     ax,1            ; AX := 40h*(y/2)
                add     bx,ax           ; BX := x/2 + 8000h*(y&1) +
                                        ;  140h*(y/2)
                shr     bx,1
                shr     bx,1            ; BX := x/8 + 2000h*(y&1) +
                                        ;  50h*(y/2)
                add     bx,OriginOffset ; BX := byte offset in video buffer

                mov     ax,VideoBufferSeg
                mov     es,ax           ; ES:BX := byte address of pixel

                and     cl,7            ; CL := x & 7
                xor     cl,7            ; CL := number of bits to shift
                                        ;  left
                mov     ah,1            ; AH := unshifted bit mask

                ret

PixelAddr06     ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 4-3.  Computing a pixel's address in Hercules graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 4-3'
                NAME    PixelAddrHGC
                PAGE    55,132

;
; Name:         PixelAddrHGC
;
; Function:     Determine buffer address of pixel in 720x348 Hercules
;                graphics
;
; Caller:       AX = y-coordinate (0-347)
;               BX = x-coordinate (0-719)
;
; Returns:      AH = bit mask
;               BX = byte offset in buffer
;               CL = number of bits to shift left
;               ES = video buffer segment
;

BytesPerLine    EQU     90
OriginOffset    EQU     0               ; byte offset of (0,0)
VideoBufferSeg  EQU     0B000h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  PixelAddrHGC
PixelAddrHGC    PROC    near

                mov     cl,bl           ; CL := low-order byte of x

                shr     ax,1            ; AX := y/2
                rcr     bx,1            ; BX := 8000h*(y&1) + x/2
                shr     ax,1            ; AX := y/4
                rcr     bx,1            ; BX := 4000h*(y&3) + x/4
                shr     bx,1            ; BX := 2000h*(y&3) + x/8
                mov     ah,BytesPerLine
                mul     ah              ; AX := BytesPerLine*(y/4)
                add     bx,ax           ; BX := 2000h*(y&3) + x/8 +
                                        ; BytesPerLine*(y/4)
                add     bx,OriginOffset ; BX := byte offset in video buffer

                mov     ax,VideoBufferSeg
                mov     es,ax           ; ES:BX := byte address of pixel

                and     cl,7            ; CL := x & 7
                xor     cl,7            ; CL := number of bits to shift
                                        ;  left
                mov     ah,1            ; AH := unshifted bit mask

                ret

PixelAddrHGC    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 4-4.  Computing a pixel's address in CGA and VGA graphics modes.
���������������������������������������������������������������������������

                TITLE   'Listing 4-4'
                NAME    PixelAddr10
                PAGE    55,132

;
; Name:         PixelAddr10
;
; Function:     Determine buffer address of pixel in native EGA and VGA
;                modes:
;                       320x200 16-color
;                       640x200 16-color
;                       640x350 16-color
;                       640x350 monochrome (4-color)
;                       640x480 2-color
;                       640x480 16-color
;
; Caller:       AX = y-coordinate
;               BX = x-coordinate
;
; Returns:      AH = bit mask
;               BX = byte offset in buffer
;               CL = number of bits to shift left
;               ES = video buffer segment
;

BytesPerLine    EQU     80              ; bytes in one horizontal line
OriginOffset    EQU     0               ; byte offset of (0,0)
VideoBufferSeg  EQU     0A000h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  PixelAddr10
PixelAddr10     PROC    near

                mov     cl,bl           ; CL := low-order byte of x
                push    dx              ; preserve DX

                mov     dx,BytesPerLine ; AX := y * BytesPerLine
                mul     dx

                pop     dx
                shr     bx,1
                shr     bx,1
                shr     bx,1            ; BX := x/8
                add     bx,ax           ; BX := y*BytesPerLine + x/8
                add     bx,OriginOffset ; BX := byte offset in video buffer

                mov     ax,VideoBufferSeg
                mov     es,ax           ; ES:BX := byte address of pixel

                and     cl,7            ; CL := x & 7
                xor     cl,7            ; CL := number of bits to shift
                                        ;  left
                mov     ah,1            ; AH := unshifted bit mask
                ret

PixelAddr10     ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 4-5.  Computing a pixel's address in 320-by-200 256-color mode
���������������������������������������������������������������������������

                TITLE   'Listing 4-5'
                NAME    PixelAddr13
                PAGE    55,132

;
; Name:         PixelAddr13
;
; Function:     Determine buffer address of pixel in 320x200 256-color mode
;
; Caller:       AX = y-coordinate (0-199)
;               BX = x-coordinate (0-319)
;
; Returns:      BX = byte offset in buffer
;               ES = video buffer segment
;

OriginOffset    EQU     0               ; byte offset of (0,0)
VideoBufferSeg  EQU     0A000h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  PixelAddr13
PixelAddr13     PROC    near

                xchg    ah,al           ; AX := 256*y
                add     bx,ax           ; BX := 256*y + x
                shr     ax,1
                shr     ax,1            ; AX := 64*y
                add     bx,ax           ; BX := 320*y + x

                add     bx,OriginOffset ; BX := byte offset in video buffer

                mov     ax,VideoBufferSeg
                mov     es,ax           ; ES:BX := byte address of pixel
                ret

PixelAddr13     ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 4-6.  Foreground color in CGA 640-by-200 2-color graphics.
���������������������������������������������������������������������������

mov     ah,0Bh          ; AH := 0BH (INT 10H function number)
mov     bh,0            ; BH := subfunction number
mov     bl,ColorValue   ; BL := desired color (0-0FH)
int     10h



���������������������������������������������������������������������������
Listing 4-7.  Four-color palettes in CGA 320-by-200 4-color mode.
���������������������������������������������������������������������������

; cyan-red-white

mov     ax,40h
mov     es,ax           ; ES := Video BIOS data segment
mov     al,es:[65h]     ; AL := CRT_MODE_SET
or      al,00000100b    ; AL bit 2 := 1
mov     dx,3D8h         ; DX := Mode Control I/O port
out     dx,al           ; update Mode Control register
mov     es:[65h],al     ; update CRT_MODE_SET

; green-red-yellow or cyan-violet-white

mov     ax,40h
mov     es,ax           ; ES := Video BIOS data segment
mov     al,es:[65h]     ; AL := CRT_MODE_SET
and     al,11111011b    ; AL bit 2 := 0
mov     dx,3D8h         ; DX := Mode Control I/O port
out     dx,al           ; update Mode Control register
mov     es:[65h],al     ; update CRT_MODE_SET

mov     al,es:[66h]     ; AL := CRT_PALETTE
and     al,11011111b    ; AL bit 5 := 0
or      al,PaletteSelect; 00000000b for green-red-yellow
                        ; 00100000b for cyan-violet-white
inc     dx              ; DX := Color Select I/O port
out     dx,al           ; update Color Select register
mov     es:[66h],al     ; update CRT_PALETTE



���������������������������������������������������������������������������
Listing 4-8.  Four-color palettes in CGA 320-by-200 4-color mode using
              video BIOS.
���������������������������������������������������������������������������

; cyan-red-white

        mov     ax,0005         ; AH := 0 (INT 10H function number)
                                ; AL := 5 (320x200 4-color mode, color
                                ;  burst disabled)
        int     10h

; green-red-yellow or cyan-violet-white

        mov     ax,0004         ; AH := 0 (INT 10H function number)
                                ; AL := 4 (320x200 4-color mode, color
                                ;  burst enabled)
        int     10h

        mov     ah,0Bh          ; AH := INT 10H function number
        mov     bh,1
        mov     bl,PaletteID    ; 0 for green-red-yellow
                                ; 1 for cyan-violet-white
        int     10h



���������������������������������������������������������������������������
Listing 5-1.  How to set Graphics Controller read and write modes. This
              example sets read mode 0 and write mode 1 in 640-by-350
              16-color mode.
���������������������������������������������������������������������������

                mov     ax,0105h     ; AH := 1 (reg 5 value)
                                     ;  bit 3 := 0 (reead mode 0)
                                     ;  bits 0-1 := 1 (write mode 1)
                                     ; AL := register number
                mov     dx,3CEh      ; DX := Graphics Controller port
                out     dx,ax



���������������������������������������������������������������������������
Listing 5-2.  Determining a pixel value in CGA 640-by-200 2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-2'
                NAME    ReadPixel06
                PAGE    55,132

;
; Name:         ReadPixel06
;
; Function:     Read the value of a pixel in 640x200 2-color mode
;
; Caller:       Microsoft C:
;
;                       int ReadPixel06(x,y);
;
;                       int x,y;                /* pixel coordinates */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr06:near

                PUBLIC  _ReadPixel06
_ReadPixel06    PROC    near

                push    bp            ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy       ; AX := y
                mov     bx,ARGx       ; BX := x
                call    PixelAddr06   ; AH := bit mask
                                      ; ES:BX -> buffer
                                      ; CL := # bits to shift

                mov     al,es:[bx]    ; AL := byte containing pixel
                shr     al,cl         ; shift pixel value to low-order bits
                and     al,ah         ; AL := pixel value
                xor     ah,ah         ; AX := pixel value

                mov     sp,bp         ; restore caller registers and return
                pop     bp
                ret

_ReadPixel06    ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 5-3.  Determining a pixel value in CGA 320-by-200 4-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-3'
                NAME    ReadPixel04
                PAGE    55,132

;
; Name:         ReadPixel04
;
; Function:     Read the value of a pixel in 320x200 4-color mode
;
; Caller:       Microsoft C:
;
;                       int ReadPixel04(x,y);
;
;                       int x,y;                /* pixel coordinates */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr04:near

                PUBLIC  _ReadPixel04
_ReadPixel04    PROC    near

                push    bp            ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy       ; AX := y
                mov     bx,ARGx       ; BX := x
                call    PixelAddr04   ; AH := bit mask
                                      ; ES:BX -> buffer
                                      ; CL := # bits to shift

                mov     al,es:[bx]    ; AL := byte containing pixel
                shr     al,cl         ; shift pixel value to low-order bits
                and     al,ah         ; AL := pixel value
                xor     ah,ah         ; AX := pixel value

                mov     sp,bp         ; restore caller registers and return
                pop     bp
                ret

_ReadPixel04    ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 5-4.  Determining a pixel value in native EGA graphics modes.
���������������������������������������������������������������������������

                TITLE   'Listing 5-4'
                NAME    ReadPixel10
                PAGE    55,132

;
; Name:         ReadPixel10
;
; Function:     Read the value of a pixel in native EGA graphics modes
;
; Caller:       Microsoft C:
;
;                       int ReadPixel10(x,y);
;
;                       int x,y;                /* pixel coordinates */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _ReadPixel10
_ReadPixel10    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift

                mov     ch,ah
                shl     ch,cl           ; CH := bit mask in proper position

                mov     si,bx           ; ES:SI -> regen buffer byte
                xor     bl,bl           ; BL is used to accumulate the
                                        ;  pixel value

                mov     dx,3CEh         ; DX := Graphics Controller port
                mov     ax,304h         ; AH := initial bit plane number
                                        ; AL := Read Map Select register
                                        ;  number

L01:            out     dx,ax           ; select bit plane
                mov     bh,es:[si]      ; BH := byte from current bit plane
                and     bh,ch           ; mask one bit
                neg     bh              ; bit 7 of BH := 1 (if masked
                                        ;  bit = 1)
                                        ; bit 7 of BH := 0 (if masked
                                        ;  bit = 0)
                rol     bx,1            ; bit 0 of BL := next bit from
                                        ;  pixel value
                dec     ah              ; AH := next bit plane number
                jge     L01

                mov     al,bl           ; AL := pixel value
                xor     ah,ah           ; AX := pixel value

                pop     si              ; restore caller registers and
                                        ;  return
                mov     sp,bp
                pop     bp
                ret

_ReadPixel10    ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 5-5.  Determining a pixel value in EGA monochrome graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-5'
                NAME    ReadPixel0F
                PAGE    55,132

;
; Name:         ReadPixel0F
;
; Function:     Read the value of a pixel in 640x350 monochrome mode
;
; Caller:       Microsoft C:
;
;                       int     ReadPixel0F(x,y);
;
;                       int x,y;                /* pixel coordinates */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _ReadPixel0F
_ReadPixel0F    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift

; concatenate bits from bit planes 2 and 0

                mov     ch,ah
                shl     ch,cl           ; CH := bit mask in proper position
                mov     si,bx           ; ES:SI -> regen buffer byte

                mov     dx,3CEh         ; DX := Graphics Controller port
                mov     ax,204h         ; AH := initial bit plane number
                                        ; AL := Read Map Select register
                                        ;  number

                xor     bl,bl           ; BL is used to accumulate the
                                        ;  pixel value

L01:            out     dx,ax           ; (same as before)
                mov     bh,es:[si]
                and     bh,ch
                neg     bh

                rol     bx,1
                sub     ah,2            ; decrement map number by 2
                jge     L01

                mov     al,bl
                xor     ah,ah

                pop     si
                mov     sp,bp
                pop     bp
                ret

_ReadPixel0F    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 5-6.  Determining a pixel value in 640-by-350 modes on
              an EGA with 64 KB.
���������������������������������������������������������������������������

                TITLE   'Listing 5-6'
                NAME    ReadPixel10
                PAGE    55,132

;
; Name:         ReadPixel10
;
; Function:     Read the value of a pixel in 640x350 modes on 64K EGA
;
; Caller:       Microsoft C:
;
;                       int     ReadPixel10(x,y);
;
;                       int x,y;                /* pixel coordinates */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _ReadPixel10
_ReadPixel10    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift

; concatenate bits from bit planes 2 and 0 (even byte address)
;  or 3 and 1 (odd byte address)

                mov     ch,ah
                shl     ch,cl           ; CH := bit mask in proper position

                mov     si,bx           ; ES:SI -> regen buffer byte

                mov     ah,bl           ; AH := low-order byte of address
                and     ax,100h         ; AH := low-order bit of address
                                        ; AL := 0
                add     ax,204h         ; AH := initial bit plane number (2
                                        ;  or 3)
                                        ; AL := Read Map Select register
                                        ;  number

                mov     dx,3CEh         ; DX := Graphics Controller port
                xor     bl,bl           ; BL is used to accumulate the
                                        ;  pixel value

L01:            out     dx,ax           ; (same as before)
                mov     bh,es:[si]
                and     bh,ch
                neg     bh

                rol     bx,1
                sub     ah,2
                jge     L01

                mov     al,bl
                xor     ah,ah

                pop     si
                mov     sp,bp
                pop     bp
                ret

_ReadPixel10    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 5-7.  Determining a pixel value in InColor graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-7'
                NAME    ReadPixelInC
                PAGE    55,132

;
; Name:         ReadPixelInC
;
; Function:     Read the value of a pixel in InColor 720x348 16-color mode
;
; Caller:       Microsoft C:
;
;                       int     ReadPixelInC(x,y);
;
;                       int x,y;
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]

DefaultRWColor  EQU     0Fh             ; default value for R/W Color
                                        ;  Register


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddrHGC:near

                PUBLIC  _ReadPixelInC
_ReadPixelInC   PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift

; set up to examine each bit plane separately

                mov     si,bx           ; ES:SI -> buffer

                shl     ah,cl
                mov     cl,ah           ; CL := bit mask in proper position

                mov     dx,3B4h         ; DX := graphics control port

                mov     ax,0F01Ah       ; AH bits 4-7 := 1111b (background
                                        ;  value)
                                        ; AL := 1Ah (R/W Color Register)
                out     dx,ax           ; set background value

                mov     bx,800h         ; BH := 1000b (initial "don't care"
                                        ;  bits)
                                        ; BL := 0 (initial value for
                                        ;  result)

                dec     ax              ; AL := 19h (R/W Control Register
                                        ;  number)

; loop across bit planes by updating "don't care" bits

L01:            mov     ah,bh           ; AH bits 0-3 := next "don't care"
                                        ;  bits
                                        ; AH bit 6 := 0 (Mask Polarity bit)
                xor     ah,1111b        ; invert "don't care" bits
                out     dx,ax           ; set R/W Control Register

                mov     ch,cl           ; CH := bit mask
                and     ch,es:[si]      ; latch bit planes
                                        ; CH <> 0 if bit in latch is set

                neg     ch              ; cf set if CH <> 0
                rcl     bl,1            ; accumulate result in BL

                shr     bh,1            ; BH := shifted "don't care" bits
                jnz     L01             ; loop until shifted out of BH,
                                        ;  at which point BX = pixel value
; restore default state

                mov     ah,40h          ; AH := default R/W Control
                                        ;  Register value
                out     dx,ax

                inc     ax              ; AL := 1Ah (R/W Color Register
                                        ;  number)
                mov     ah,DefaultRWColor
                out     dx,ax

                mov     ax,bx           ; AX := pixel value

                pop     si              ; restore caller registers and
                                        ;  return
                mov     sp,bp
                pop     bp
                ret

_ReadPixelInC   ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 5-8.  Determining a pixel value in MCGA and VGA
              640-by-480 2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-8'
                NAME    ReadPixel11
                PAGE    55,132

;
; Name:         ReadPixel11
;
; Function:     Read the value of a pixel in 640x480 2-color mode (MCGA or
;                VGA)
;
; Caller:       Microsoft C:
;
;                       int ReadPixel11(x,y);
;
;                       int x,y;                /* pixel coordinates */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _ReadPixel11
_ReadPixel11    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift

                mov     al,es:[bx]      ; AL := byte containing pixel
                shr     al,cl           ; shift pixel value to low-order
                                        ;  bits
                and     al,ah           ; AL := pixel value
                xor     ah,ah           ; AX := pixel value

                mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_ReadPixel11    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 5-9.  Determining a pixel value in MCGA and VGA 320-by-200
              256-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-9'
                NAME    ReadPixel13
                PAGE    55,132

;

; Name:         ReadPixel13
;
; Function:     Read the value of a pixel in 320x200 256-color mode
;                (MCGA and VGA)
;
; Caller:       Microsoft C:
;
;                       int ReadPixel13(x,y);
;
;                       int x,y;                /* pixel coordinates */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr13:near

                PUBLIC  _ReadPixel13
_ReadPixel13    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr13     ; ES:BX -> buffer

                mov     al,es:[bx]      ; AL := pixel value
                xor     ah,ah           ; AX := pixel value

                mov     sp,bp
                pop     bp
                ret

_ReadPixel13    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 5-10.  Setting a pixel value in CGA and 640-by-200 2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-10'
                NAME    SetPixel06
                PAGE    55,132

;
; Name:         SetPixel06
;
; Function:     Set the value of a pixel in 640x200 2-color mode
;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr06:near

                PUBLIC  _SetPixel06
_SetPixel06     PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr06     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn         ; AL := unshifted pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;  position

                jmp     word ptr SetPixelOp06   ; jump to Replace, AND,
                                                ;  OR or XOR routine


                                        ; routine to Replace pixel value

ReplacePixel06: not     ah              ; AH := inverse bit mask
                and     es:[bx],ah      ; zero the pixel value
                or      es:[bx],al      ; set the pixel value
                jmp     short L02


                                        ; routine to AND pixel value
ANDPixel06:     test    al,al
                jnz     L02             ; do nothing if pixel value = 1

L01:            not     ah              ; AH := inverse of bit mask
                and     es:[bx],ah      ; set bit in video buffer to 0
                jmp     short L02


                                        ; routine to OR pixel value
ORPixel06:      test    al,al
                jz      L02             ; do nothing if pixel value = 0

                or      es:[bx],al      ; set bit in video buffer
                jmp     short L02


                                        ; routine to XOR pixel value
XORPixel06:     test    al,al
                jz      L02             ; do nothing if pixel value = 0

                xor     es:[bx],al      ; XOR bit in video buffer


L02:            mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_SetPixel06     ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

SetPixelOp06    DW      ReplacePixel06  ; contains addr of pixel operation

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 5-11.  Setting a pixel value in CGA 320-by-200 2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-11'
                NAME    SetPixel04
                PAGE    55,132

;
; Name:         SetPixel04
;
; Function:     Set the value of a pixel in 320x200 4-color mode
;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr04:near

                PUBLIC  _SetPixel04
_SetPixel04     PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x

                call    PixelAddr04     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;  position

                jmp     word ptr SetPixelOp04   ; jump to Replace, AND,
                                                ;  OR or XOR routine


                                        ; routine to Replace pixel value

ReplacePixel04: not     ah              ; AH := inverse bit mask
                and     es:[bx],ah      ; zero the pixel value
                or      es:[bx],al      ; set the pixel value
                jmp     short L02

                                        ; routine to AND pixel value

ANDPixel04:     not     ah              ; AH := inverse bit mask
                or      al,ah           ; AL := all 1's except pixel value
                and     es:[bx],al
                jmp     short L02


ORPixel04:      or      es:[bx],al      ; routine to OR pixel value
                jmp     short L02


XORPixel04:     xor     es:[bx],al      ; routine to XOR pixel value


L02:            mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_SetPixel04     ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

SetPixelOp04    DW      ReplacePixel04  ; contains addr of pixel operation

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 5-12.  Setting a pixel value in native EGA graphics modes
               using write mode 0.
���������������������������������������������������������������������������

                TITLE   'Listing 5-12'
                NAME    SetPixel10
                PAGE    55,132

;
; Name:         SetPixel10
;
; Function:     Set the value of a pixel in native EGA graphics modes.
;
;               *** Write Mode 0, Set/Reset ***
;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]

RMWbits         EQU     18h             ; read-modify-write bits


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near
                PUBLIC  _SetPixel10

_SetPixel10     PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

; set Graphics Controller Bit Mask register

                shl     ah,cl           ; AH := bit mask in proper position
                mov     dx,3CEh         ; GC address register port
                mov     al,8            ; AL := Bit Mask register number
                out     dx,ax

; set Graphics Controller Mode register

                mov     ax,0005h        ; AL :=  Mode register number
                                        ; AH :=  Write Mode 0 (bits 0,1)
                                        ;        Read Mode 0 (bit 3)
                out     dx,ax

; set Data Rotate/Function Select register

                mov     ah,RMWbits      ; AH := Read-Modify-Write bits
                mov     al,3            ; AL := Data Rotate/Function Select
                                        ;  reg
                out     dx,ax

; set Set/Reset and Enable Set/Reset registers

                mov     ah,ARGn         ; AH := pixel value
                mov     al,0            ; AL := Set/Reset reg number
                out     dx,ax

                mov     ax,0F01h        ; AH := value for Enable Set/Reset
                                        ;  (all bit planes enabled)
                                        ; AL := Enable Set/Reset reg number
                out     dx,ax

; set the pixel value

                or      es:[bx],al      ; load latches during CPU read
                                        ; update latches and bit planes
                                        ;  during CPU write

; restore default Graphics Controller registers

                mov     ax,0FF08h       ; default Bit Mask
                out     dx,ax

                mov     ax,0005         ; default Mode register
                out     dx,ax

                mov     ax,0003         ; default Function Select
                out     dx,ax

                mov     ax,0001         ; default Enable Set/Reset
                out     dx,ax

                mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_SetPixel10     ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 5-13.  Setting a pixel value in native EGA graphics modes
               using the Sequencer Map Mask.
���������������������������������������������������������������������������

                TITLE   'Listing 5-13'
                NAME    SetPixel10
                PAGE    55,132
;
; Name:         SetPixel10
;
; Function:     Set the value of a pixel in native EGA graphics modes.
;
;               *** Write Mode 0, Sequencer Map Mask ***

;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;                       int n;                  /* pixel value */

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _SetPixel10
_SetPixel10     PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

; set Graphics Controller Bit Mask register

                shl     ah,cl           ; AH := bit mask in
                                        ;  proper position
                mov     dx,3CEh         ; Graphics Controller address
                                        ;  reg port
                mov     al,8            ; AL := Bit Mask register number
                out     dx,ax

; zero the pixel value

                mov     al,es:[bx]      ; latch one byte from each
                                        ;  bit plane
                mov     byte ptr es:[bx],0  ; zero masked bits in
                                        ;  all planes

; set Sequencer Map Mask register

                mov     dl,0C4h         ; DX := 3C4h (Sequencer addr
                                        ;  reg port)
                mov     ah,ARGn         ; AH := value for Map Mask
                                        ;  register
                                        ;  (nonzero bits in pixel
                                        ;  value select
                                        ;  enabled bit planes for
                                        ;  Sequencer)

                mov     al,2            ; AL := Map Mask register number
                out     dx,ax

; set the nonzero bits in the pixel value
                mov     byte ptr es:[bx],0FFh ; set bits in enabled
                                              ;  bit planes

; restore default Sequencer registers

                mov     ah,0Fh          ; AH := value for Map Mask reg
                                        ;  (all bit
                                        ;  planes enabled)
                out     dx,ax

; restore default Graphics Controller registers

                mov     dl,0CEh         ; DX := 3CEh (Graphics
                                        ;  Controller port)
                mov     ax,0FF08h       ; default Bit Mask
                out     dx,ax

                mov     sp,bp           ; restore caller registers
                                        ;  and return
                pop     bp
                ret

_SetPixel10     ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 5-14.  Setting a pixel value in native EGA graphics modes
               using write mode 2.
���������������������������������������������������������������������������

                TITLE   'Listing 5-14'
                NAME    SetPixel10
                PAGE    55,132

;
; Name:         SetPixel10
;
; Function:     Set the value of a pixel in native EGA graphics modes.
;
;               *** Write Mode 2 ***

;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;                       int n;                  /* pixel value */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]

RMWbits         EQU     18h             ; read-modify-write bits


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _SetPixel10
_SetPixel10     PROC    near

                push    bp              ; preserve stack frame
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

; set Graphics Controller Bit Mask register

                shl     ah,cl           ; AH := bit mask in proper position
                mov     dx,3CEh         ; GC address register port
                mov     al,8            ; AL := Bit Mask register number
                out     dx,ax

; set Graphics Controller Mode register

                mov     ax,205h         ; AL :=  Mode register number
                                        ; AH :=  Write Mode 2 (bits 0,1)
                                        ;        Read Mode 0 (bit 3)
                out     dx,ax

; set Data Rotate/Function Select register

                mov     ah,RMWbits      ; AH := Read-Modify-Write bits
                mov     al,3            ; AL := Data Rotate/Function Select
                                        ;  reg
                out     dx,ax

; set the pixel value

                mov     al,es:[bx]      ; latch one byte from each bit
                                        ;  plane
                mov     al,ARGn         ; AL := pixel value
                mov     es:[bx],al      ; update all bit planes

; restore default Graphics Controller registers

                mov     ax,0FF08h       ; default Bit Mask
                out     dx,ax

                mov     ax,0005         ; default Mode register
                out     dx,ax

                mov     ax,0003         ; default Function Select
                out     dx,ax

                mov     sp,bp           ; restore stack frame and return
                pop     bp
                ret

_SetPixel10     ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 5-15.  Setting a pixel value in InColor graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-15'
                NAME    SetPixelInC
                PAGE    55,132

;
; Name:         SetPixelInC
;
; Function:     Set the value of a pixel in 720x348 16-color mode
;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;

;                       int x,y;                /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]

DefaultRWColor  EQU     0Fh             ; default value for R/W Color
                                        ;  Register


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddrHGC:near

                PUBLIC  _SetPixelInC
_SetPixelInC    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                shl     ah,cl           ; AH := bit mask in proper position

                mov     dx,3B4h         ; DX := CRTC port

                jmp     word ptr SetPixelOpInC  ; jump to Replace, AND,
                                                ;  OR or XOR routine


ReplacePixelInC:                        ; routine to Replace pixel value

                mov     ch,ah           ; CH := bit mask for pixel
                mov     ax,1F19h        ; AH bit 6 := 0 (Mask Polarity)
                                        ; AH bits 5-4 := 1 (Write Mode)
                                        ; AH bits 3-0 := "don't care" bits
                                        ; AL := R/W Control Register number
                out     dx,ax           ; set R/W Control Register

                inc     ax              ; AL := 1Ah (R/W Color Reg number)
                mov     ah,ARGn         ; AH := foreground value
                out     dx,ax           ; set R/W color register

                and     es:[bx],ch      ; update bit planes
                jmp     short L01

ANDPixelInC:                            ; routine to AND pixel value

                mov     ch,ah           ; CH := bit mask for pixel
                mov     ax,1F19h        ; AH bit 6 := 0 (Mask Polarity)
                                        ; AH bits 5-4 := 1 (Write Mode)
                                        ; AH bits 3-0 := "don't care" bits
                                        ; AL := R/W Control Register number
                out     dx,ax           ; set R/W Control Register

                dec     ax              ; AL := 18h (Plane Mask Register
                                        ;  number)
                mov     ah,ARGn         ; AH := pixel value
                mov     cl,4
                shl     ah,cl           ; AH bits 7-4 := writeable plane
                                        ;  mask
                or      ah,0Fh          ; AH bits 3-0 := visible plane mask
                out     dx,ax           ; set Plane Mask Register

                mov     ax,001Ah        ; AH := 0 (foreground value)
                                        ; AL := 1Ah (R/W Color reg)
                out     dx,ax           ; set R/W Color Register

                and     es:[bx],ch      ; update bit planes
                jmp     short L01

                                        ; routine to OR pixel value
ORPixelInC:
                mov     ch,ah           ; CH := bit mask for pixel
                mov     ax,1F19h        ; AH bit 6 := 0 (Mask Polarity)
                                        ; AH bits 5-4 := 1 (Write Mode)
                                        ; AH bits 3-0 := "don't care" bits
                                        ; AL := R/W Control Register number
                out     dx,ax           ; set R/W Control Register

                dec     ax              ; AL := 18h (Plane Mask Register
                                        ;  number)
                mov     ah,ARGn         ; AH := pixel value
                not     ah              ; AH := complement of pixel value
                mov     cl,4
                shl     ah,cl           ; AH bits 7-4 := writeable plane
                                        ;  mask
                or      ah,0Fh          ; AH bits 3-0 := visible plane mask
                out     dx,ax           ; set Plane Mask Register

                mov     ax,0F1Ah        ; AH := 0 (foreground value)
                                        ; AL := 1Ah (R/W Color reg)
                out     dx,ax           ; set R/W Color Register

                and     es:[bx],ch      ; update bit planes
                jmp     short L01

XORPixelInC:                            ; routine to XOR pixel value
                mov     ch,ah           ; CH := bit mask for pixel
                mov     ax,3F19h        ; AH bit 6 := 0 (Mask Polarity)
                                        ; AH bits 5-4 := 3 (Write Mode)
                                        ; AH bits 3-0 := "don't care" bits
                                        ; AL := R/W Control Register number
                out     dx,ax           ; set R/W Control Register

                dec     ax              ; AL := 18h (Plane Mask Register
                                        ;  number)
                mov     ah,ARGn         ; AH := pixel value
                not     ah              ; AH := complement of pixel value
                mov     cl,4
                shl     ah,cl           ; AH bits 7-4 := writeable plane
                                        ;  mask
                or      ah,0Fh          ; AH bits 3-0 := visible plane mask
                out     dx,ax           ; set Plane Mask Register

                xor     es:[bx],ch      ; update bit planes
                jmp     short L01

L01:            mov     ax,0F18h
                out     dx,ax           ; restore default Plane Mask value

                mov     ax,4019h        ; restore default R/W Control value
                out     dx,ax

                inc     ax              ; restore default R/W Color value
                mov     ah,DefaultRWColor
                out     dx,ax

                mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_SetPixelInC    ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

SetPixelOpInC   DW      ReplacePixelInc ; contains addr of pixel operation

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 5-16.  Setting a pixel value in MCGA or VGA 640-by-480
               2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-16'
                NAME    SetPixel11
                PAGE    55,132

;
; Name:         SetPixel11
;
; Function:     Set the value of a pixel in 640x480 2-color mode (MCGA or
;                VGA)
;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr10:near

                PUBLIC  _SetPixel11
_SetPixel11     PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn         ; AL := unshifted pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;  position

                jmp     word ptr SetPixelOp11   ; jump to Replace, AND,
                                                ;  OR or XOR routine


                                        ; routine to Replace pixel value

ReplacePixel11: not     ah              ; AH := inverse bit mask
                and     es:[bx],ah      ; zero the pixel value
                or      es:[bx],al      ; set the pixel value
                jmp     short L02


                                        ; routine to AND pixel value
ANDPixel11:     test    al,al
                jnz     L02             ; do nothing if pixel value = 1

L01:            not     ah              ; AH := inverse of bit mask
                and     es:[bx],ah      ; set bit in video buffer to 0
                jmp     short L02


                                        ; routine to OR pixel value
ORPixel11:      test    al,al
                jz      L02             ; do nothing if pixel value = 0

                or      es:[bx],al      ; set bit in video buffer
                jmp     short L02


                                        ; routine to XOR pixel value
XORPixel11:     test    al,al
                jz      L02             ; do nothing if pixel value = 0

                xor     es:[bx],al      ; XOR bit in video buffer


L02:            mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_SetPixel11     ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

SetPixelOp11    DW      ReplacePixel11  ; contains addr of pixel operation

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 5-17.  Setting a pixel value in MCGA or VGA 320-by-200
               256-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 5-17'
                NAME    SetPixel13
                PAGE    55,132

;
; Name:         SetPixel13
;
; Function:     Set the value of a pixel in 320x200 256-color mode (MCGA or
;                VGA)
;
; Caller:       Microsoft C:
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr13:near

                PUBLIC  _SetPixel13
_SetPixel13     PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr13     ; ES:BX -> buffer

                mov     al,ARGn         ; AL := pixel value

                jmp     word ptr SetPixelOp13   ; jump to Replace, AND,
                                                ;  OR or XOR routine


ReplacePixel13: mov     es:[bx],al
                jmp     short L01

ANDPixel13:     and     es:[bx],al
                jmp     short L01

ORPixel13:      or      es:[bx],al
                jmp     short L01

XORPixel13:     xor     es:[bx],al


L01:            mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_SetPixel13     ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

SetPixelOp13    DW      ReplacePixel13

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 5-18.  Simple CGA graphics buffer fill.
���������������������������������������������������������������������������

        mov     di,0B800h
        mov     es,di
        xor     di,di           ; ES:DI -> start of video buffer
        mov     al,11110000b    ; AL := pixel pattern
        mov     ah,al           ; AX := replicated pixel pattern
        mov     cx,2000h        ; CX := number of words in video buffer
        rep     stosw           ; fill buffer with pixel pattern

; this may also be accomplished using the video BIOS

        mov     ah,0Fh          ; AH := 0Fh (INT 10H function number)
        int     10h             ; get current video state; AH = number of
                                ;  character columns
        mov     dl,ah           ; DL := number of character columns

        mov     ax,600h         ; AH := 6 (INT 10H function number)
                                ; AL := 0 (number of rows to scroll)
        mov     bh,11110000b    ; BH := pixel pattern
        mov     cx,0            ; CH := 0 (upper left character column)
                                ; CL := 0 (upper left character row)
        mov     dh,18h          ; DH := 18h (lower right character row)
        dec     dl              ; DL := lower right character column
        int     10h



���������������������������������������������������������������������������
Listing 5-19.  CGA graphics buffer fill using two-way interleave.
���������������������������������������������������������������������������

        mov     di,0B800h
        mov     es,di
        xor     di,di           ; ES:DI -> start of video buffer

        mov     al,11001100b    ; AL pixel pattern
        mov     ah,al           ; AX := replicated pixel pattern

        mov     bx,100          ; BX := number of pairs of rows

L01:    mov     cx,40           ; CX := number of words in each row
        rep     stosw           ; fill even row

        add     di,2000h-80     ; ES:DI -> odd row
        mov     cx,40
        rep     stosw           ; fill odd row

        sub     di,2000h        ; ES:DI -> next even row
        dec     bx
        jnz     L01



���������������������������������������������������������������������������
Listing 5-20.  CGA graphics buffer fill with different pixel pattern
               in odd and even rows.
���������������������������������������������������������������������������

        mov     di,0B800h
        mov     es,di
        xor     di,di           ; ES:DI -> start of pixel row 0

        mov     al,10101010b    ; AL := pixel pattern for even rows
        mov     ah,al           ; AX := replicated pixel pattern
        mov     cx,1000h        ; CX := number of words in video buffer
        rep     stosw           ; fill even pixel rows

        mov     di,2000h        ; ES:DI -> start of pixel row 1
        mov     al,01010101b    ; AL := pixel pattern for odd rows
        mov     ah,al
        mov     cx,1000h
        rep     stosw           ; fill odd pixel rows



���������������������������������������������������������������������������
Listing 5-21.  HGC graphics buffer fill using four-way interleave.
���������������������������������������������������������������������������

        mov     es,BufferSeg    ; ES := 0B000h for first video page
                                ;    or 0B800h for second video page
        xor     di,di           ; ES:DI -> first byte to fill

        mov     al,10101010b    ; AL pixel pattern
        mov     ah,al           ; AX := replicated pixel pattern

L01:    mov     cx,1000h        ; CX := number of words in
                                ;  each 8 KB buffer interleave
        rep     stosw           ; fill interleave; increment DI by 2000h

        ror     ax,1            ; shift pixel pattern between rows
        or      di,di
        jns     L01             ; jump if DI < 8000h



���������������������������������������������������������������������������
Listing 5-22.  MCGA and VGA 640-by-480 2-color graphics buffer fill.
���������������������������������������������������������������������������

        mov     di,0A000h
        mov     es,di
        xor     di,di           ; ES:DI -> start of video buffer
        mov     al,01010101b    ; AL := pixel pattern
        mov     ah,al           ; AX := replicated pixel pattern
        mov     cx,480*40       ; CX := (pixel rows) * (words per row)
        rep     stosw           ; fill buffer with pixel pattern


; this may also be accomplished using the video BIOS

        mov     ax,1130h        ; AH := 11h (INT 10H function number)
                                ; AL := 30h (character generator info)
        int     10h             ; get info; DL = number of
                                ;  character rows - 1

        mov     ax,600h         ; AH := 6 (INT 10H function number)
                                ; AL := 0 (number of rows to scroll)
        mov     bh,01010101b    ; BH := pixel pattern
        mov     cx,0            ; CH := 0 (upper left character column)
                                ; CL := 0 (upper left character row)
        mov     dh,dl           ; DH := lower right character row
        mov     dl,4Fh          ; DL := 4Fh (lower right character column)
        int     10h



���������������������������������������������������������������������������
Listing 5-23.  MCGA and VGA 320-by-200 256-color graphics buffer fill.
               This routine fills alternate pixel rows separately
               to allow dithered pixel patterns.
���������������������������������������������������������������������������

        mov     di,0A000h
        mov     es,di
        xor     di,di           ; ES:DI -> start of video buffer
        mov     ah,PixelValue1  ; AX := 2-pixel pattern
        mov     al,PixelValue2
        mov     bx,100          ; BX := number of pairs of rows

L01:    mov     cx,160          ; CX := number of words per row
        rep     stosw           ; fill even-numbered row
        xchg    ah,al           ; exchange pixels in pattern

        mov     cx,160
        rep     stosw           ; fill odd-numbered row
        xchg    ah,al           ; exchange pixels in pattern

        dec     bx
        jnz     L01



���������������������������������������������������������������������������
Listing 5-24.  Solid buffer fill for EGA and VGA native graphics
               modes. The code assumes that the Graphics Controller
               is already in write mode 0 (the BIOS default).
���������������������������������������������������������������������������

        mov     di,0A000h
        mov     es,di
        xor     di,di           ; ES:DI -> start of video buffer

        mov     dx,3CEh         ; DX := Graphics Controller I/O port

        mov     ah,PixelValue   ; AH := pixel value for fill
        mov     al,0            ; AL := 0 (Set/Reset register number)
        out     dx,ax           ; load Set/Reset register

        mov     ax,0F01         ; AH := 1111b (mask for Enable Set/Reset)
                                ; AL := 1 (Enable Set/Reset reg number)
        out     dx,ax           ; load Enable Set/Reset register

        mov     cx,PixelRows*40 ; CX := (pixel rows) * (words per row)
        rep     stosw           ; fill the buffer

        mov     ax,0001         ; AH := 0 (default Enable Set/Reset value)
                                ; AL := 1 (Enable Set/Reset reg number)
        out     dx,ax           ; restore default Enable Set/Reset



���������������������������������������������������������������������������
Listing 5-25.  Patterned buffer fill for EGA and VGA native graphics
               modes. The code assumes that the desired pixel
               pattern is already stored in the first eight pixels
               of the first two rows of the video buffer (that is, at
               A000:0000 and A000:0050).
���������������������������������������������������������������������������

        mov     di,0A000h
        mov     es,di
        xor     di,di           ; ES:DI -> start of video buffer

        mov     dx,3CEh         ; DX := Graphics Controller I/O port

        mov     ax,105h         ; AH bits 0-1 := 01b (write mode 1)
                                ; AL := 5 (Graphics Mode register)
        out     dx,ax           ; establish write mode 1

        mov     ax,PixelRows/2  ; AX := number of pairs of rows to fill

L01:    mov     cl,es:[0]       ; latch pixel pattern for even rows
        mov     cx,40           ; CX := words per row of pixels
        rep     stosw           ; copy latches across even-numbered row

        mov     cl,es:[50h]     ; latch pixel pattern for odd rows
        mov     cx,40
        rep     stosw           ; fill odd-numbered row

        dec     ax
        jnz     L01             ; loop down the buffer

        mov     ax,0005
        out     dx,ax           ; restore write mode 0 (default)



���������������������������������������������������������������������������
Listing 5-26.  Solid buffer fill for Hercules InColor graphics mode.
���������������������������������������������������������������������������

        mov     es,BufferSeg    ; ES := 0B000h for first video page
                                ;    or 0B800h for second video page
        xor     di,di           ; ES:DI -> first byte to fill

        mov     dx,3B4h         ; DX := control register I/O port

        mov     ah,PixelValue   ; AH := pixel value for fill
        mov     al,1Ah          ; AL := 1AH (Read/Write Color register
                                ;  number)
        out     dx,ax           ; load Read/Write Color register

        mov     ax,4019h        ; AH bits 5-6 := 00b (write mode 0)
                                ; AL := 19H (Read/Write Control register)
        out     dx,ax           ; load Read/Write Control reg

        mov     ax,0FFFFh       ; AX := pixel bit mask
        mov     cx,4000h        ; CX := number of words in buffer (32K / 2)
        rep     stosw           ; fill the buffer



���������������������������������������������������������������������������
Listing 5-27.  Patterned buffer fill for InColor Card. The code assumes
               that the desired pixel pattern is already stored in the
               first eight pixels of the first two rows of the video
               buffer (that is, at offsets 0 and 2000H in BufferSeg).
���������������������������������������������������������������������������

        mov     es,BufferSeg    ; ES := 0B000h for first video page
                                ;    or 0B800h for second video page
        xor     di,di           ; ES:DI -> first byte to fill

        mov     dx,3B4h         ; DX := control register I/O port

        mov     ax,6019h        ; AH bits 5-6 := 10b (write mode 2)
                                ; AL := 19H (Read/Write Control register)
        out     dx,ax           ; load Read/Write Control reg

        mov     ax,0FFFFh       ; AX := pixel bit mask

L01:    mov     cl,es:[0]       ; latch pixel pattern for even rows
        mov     cx,1000h        ; CX := number of words in
                                ;  each 8 KB buffer interleave
        rep     stosw           ; fill even-numbered interleave;
                                ;  increment DI by 2000h

        mov     cl,es:[2000h]   ; latch pixel pattern for odd rows
        mov     cx,1000h
        rep     stosw           ; fill odd-numbered interleave

        or      di,di
        jns     L01             ; loop while DI < 8000H

        mov     ax,4019h        ; restore default value of
        out     dx,ax           ;  Read/Write Control register



���������������������������������������������������������������������������
Listing 6-1.  Drawing a line using the equation of the line.
���������������������������������������������������������������������������

/* Listing 6-1 */

Line( x1, y1, x2, y2, n )
int     x1,y1;                  /* endpoint */
int     x2,y2;                  /* endpoint */
int     n;                      /* pixel value */
{
        int     x,y;
        float   m;              /* slope */
        float   b;              /* y-intercept */


        if (x2 == x1)                           /* vertical line */
        {
          if (y1 > y2)
           Swap( &y1, &y2 );                    /* force y1 < y2 */

          for (y=y1; y<=y2; y++)                /* draw from y1 to y2 */
           SetPixel( x1, y, n );

          return;
        }

        if (x1 > x2)                            /* force x1 < x2 */
        {
          Swap( &x1, &x2 );
          Swap( &y1, &y2 );
        }

        m = (float)(y2-y1) / (float)(x2-x1);    /* compute m and b */
        b = y1 - (m*x1);

        for (x=x1; x<=x2; x++)                  /* draw from x1 to x2 */
        {
          y = m*x + b;
          SetPixel( x, y, n );
        }
}

Swap( a, b )                                    /* exchange values of a */
                                                 /* and b */
int     *a,*b;
{
        int     t;

        t = *a;
        *a = *b;
        *b = t;
}



���������������������������������������������������������������������������
Listing 6-2.  A high-level implementation of Bresenham's algorithm.
���������������������������������������������������������������������������

/* Listing 6-2 */

Line( x1, y1, x2, y2, n )       /* for lines with slope between -1 and 1 */
int     x1,y1;
int     x2,y2;                  /* endpoints */
int     n;                      /* pixel value */
{
        int     d,dx,dy;
        int     Aincr,Bincr,yincr;
        int     x,y;

        if (x1 > x2)                        /* force x1 < x2 */
        {
          Swap( &x1, &x2 );
          Swap( &y1, &y2 );
        }

        if (y2 > y1)                        /* determine increment for y */
          yincr = 1;
        else
          yincr = -1;

        dx = x2 - x1;                       /* initialize constants */
        dy = abs( y2-y1 );
        d = 2 * dy - dx;

        Aincr = 2 * (dy - dx);
        Bincr = 2 * dy;

        x = x1;                             /* initial x and y */
        y = y1;

        SetPixel( x, y, n );                /* set pixel at (x1,y1) */

        for (x=x1+1; x<=x2; x++)            /* do from x1+1 to x2 */
        {
          if (d >= 0)
          {
            y += yincr;                     /* set pixel A */
            d += Aincr;
          }
          else                              /* set pixel B */
            d += Bincr;

          SetPixel( x, y, n );
        }
}


Swap( pa, pb )
int     *pa,*pb;
{
        int     t;

        t = *pa;
        *pa = *pb;
        *pb = t;
}



���������������������������������������������������������������������������
Listing 6-3.  A routine that draws horizontal lines.
���������������������������������������������������������������������������

/* Listing 6-3 */

FilledRectangle( x1, y1, x2, y2, n )
int     x1,y1;                  /* upper left corner */
int     x2,y2;                  /* lower right corner */
int     n;                      /* pixel value */
{
        int     y;

        for (y=y1; y<=y2; y++)          /* draw rectangle as a set of */
          Line( x1, y, x2, y, n );      /*  adjacent horizontal lines */
}



���������������������������������������������������������������������������
Listing 6-4.  A line-drawing routine for CGA 640-by-200 2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 6-4'
                NAME    Line06
                PAGE    55,132

;
; Name:         Line06
;
; Function:     Draw a line in 640x200 2-color mode
;
; Caller:       Microsoft C:

;
;                       void Line06(x1,y1,x2,y2,n);
;
;                       int x1,y1,x2,y2;        /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx1           EQU     word ptr [bp+4] ; stack frame addressing
ARGy1           EQU     word ptr [bp+6]
ARGx2           EQU     word ptr [bp+8]
ARGy2           EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]
VARleafincr     EQU     word ptr [bp-6]
VARincr1        EQU     word ptr [bp-8]
VARincr2        EQU     word ptr [bp-10]
VARroutine      EQU     word ptr [bp-12]

ByteOffsetShift EQU     3               ; used to convert pixels to byte
                                        ;  offset

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr06:near

                PUBLIC  _Line06
_Line06         PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,8            ; stack space for local variables
                push    si
                push    di

                mov     si,2000h        ; increment for video buffer
                                        ;  interleave
                mov     di,80-2000h     ; increment from last to first
                                        ;  interleave

                mov     cx,ARGx2
                sub     cx,ARGx1        ; CX := x2 - x1
                jz      VertLine06      ; jump if vertical line

; force x1 < x2

                jns     L01             ; jump if x2 > x1

                neg     cx              ; CX := x1 - x2

                mov     bx,ARGx2        ; exchange x1 and x2
                xchg    bx,ARGx1
                mov     ARGx2,bx

                mov     bx,ARGy2        ; exchange y1 and y2
                xchg    bx,ARGy1
                mov     ARGy2,bx

; calculate dy = ABS(y2-y1)

L01:            mov     bx,ARGy2
                sub     bx,ARGy1        ; BX := y2 - y1
                jnz     L02

                jmp     HorizLine06     ; jump if horizontal line

L02:            jns     L03

                neg     bx              ; BX := y1 - y2
                neg     si              ; negate increments for buffer
                                        ;  interleave
                neg     di
                xchg    si,di           ; exchange increments

; select appropriate routine for slope of line

L03:            mov     VARleafincr,di  ; save increment for buffer
                                        ;  interleave

                mov     VARroutine,offset LoSlopeLine06
                cmp     bx,cx
                jle     L04             ; jump if dy <= dx (slope <= 1)
                mov     VARroutine,offset HiSlopeLine06
                xchg    bx,cx           ; exchange dy and dx

; calculate initial decision variable and increments

L04:            shl     bx,1            ; BX := 2 * dy
                mov     VARincr1,bx     ; incr1 := 2 * dy
                sub     bx,cx
                mov     di,bx           ; DI := d = 2 * dy - dx
                sub     bx,cx
                mov     VARincr2,bx     ; incr2 := 2 * (dy - dx)

; calculate first pixel address

                push    cx              ; preserve this register
                mov     ax,ARGy1        ; AX := y
                mov     bx,ARGx1        ; BX := x
                call    PixelAddr06     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn         ; AL := unshifted pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position

                mov     dx,ax           ; DH := bit mask
                                        ; DL := pixel value
                not     dh              ; DH := inverse bit mask

                pop     cx              ; restore this register
                inc     cx              ; CX := # of pixels to draw

                test    bx,2000h        ; set zero flag if BX in 1st
                                        ;  interleave
                jz      L05
                xchg    si,VARleafincr  ; exchange increment values if 1st
                                        ;  pixel lies in 1st interleave

L05:            jmp     VARroutine      ; jump to appropriate routine for
                                        ;  slope


; routine for vertical lines

VertLine06:     mov     ax,ARGy1        ; AX := y1
                mov     bx,ARGy2        ; BX := y2
                mov     cx,bx
                sub     cx,ax           ; CX := dy
                jge     L31             ; jump if dy >= 0

                neg     cx              ; force dy >= 0
                mov     ax,bx           ; AX := y2

L31:            inc     cx              ; CX := # of pixels to draw
                mov     bx,ARGx1        ; BX := x
                push    cx              ; preserve this register
                call    PixelAddr06     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     al,ARGn         ; AL := pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position
                not     ah              ; AH := inverse bit mask
                pop     cx              ; restore this register

                test    bx,si           ; set zero flag if BX in 1st
                                        ;  interleave
                jz      L32

                xchg    si,di           ; exchange increment values if 1st
                                        ;  pixel lies in 1st interleave

L32:            test    al,al
                jz      L34             ; jump if pixel value = 0

L33:            or      es:[bx],al      ; set pixel values in buffer
                add     bx,si           ; increment to next portion of
                                        ;  interleave
                xchg    si,di           ; toggle between increment values
                loop    L33             ; loop down the line
                jmp     short L35

L34:            and     es:[bx],ah      ; reset pixel values in buffer
                add     bx,si           ; increment to next portion of
                                        ;  interleave
                xchg    si,di           ; toggle between increment values
                loop    L34

L35:            jmp     Lexit


; routine for horizontal lines (slope = 0)

HorizLine06:    mov     ax,ARGy1
                mov     bx,ARGx1
                call    PixelAddr06     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     di,bx           ; ES:DI -> buffer

                mov     dh,ah
                not     dh              ; DH := unshifted bit mask for
                                        ;        leftmost byte
                mov     dl,0FFh         ; DL := unshifted bit mask for
                                        ;        rightmost byte

                shl     dh,cl           ; DH := reverse bit mask for first
                                        ;        byte
                not     dh              ; DH := bit mask for first byte

                mov     cx,ARGx2
                and     cl,7
                xor     cl,7            ; CL := number of bits to shift
                                        ;        left
                shl     dl,cl           ; DL := bit mask for last byte

; determine byte offset of first and last pixel in the line

                mov     ax,ARGx2        ; AX := x2
                mov     bx,ARGx1        ; BX := x1

                mov     cl,ByteOffsetShift     ; number of bits to shift to
                                               ;  convert pixels to bytes

                shr     ax,cl           ; AX := byte offset of x2
                shr     bx,cl           ; BX := byte offset of x1
                mov     cx,ax
                sub     cx,bx           ; CX := (# bytes in line) - 1

; propagate pixel value throughout one byte

                mov     bx,offset DGROUP:PropagatedPixel
                mov     al,ARGn         ; AL := pixel value
                xlat

; set pixels in leftmost byte of the line

                or      dh,dh
                js      L43             ; jump if byte-aligned (x1 is
                                        ;  leftmost pixel in byte)
                or      cx,cx
                jnz     L42             ; jump if more than one byte in the
                                        ;  line

                and     dl,dh           ; bit mask for the line
                jmp     short L44

L42:            mov     ah,al
                and     ah,dh           ; AH := masked pixel bits
                not     dh              ; DH := reverse bit mask for 1st
                                        ;        byte
                and     es:[di],dh      ; zero masked pixels in buffer
                or      es:[di],ah      ; update masked pixels in buffer
                inc     di
                dec     cx

; use a fast 8086 machine instruction to draw the remainder of the line

L43:            rep     stosb           ; update all pixels in the line

; set pixels in the rightmost byte of the line

L44:            and     al,dl           ; AL := masked pixels for last byte
                not     dl
                and     es:[di],dl      ; zero masked pixels in buffer
                or      es:[di],al      ; update masked pixels in buffer

                jmp     Lexit


; routine for dy <= dx (slope <= 1)     ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
LoSlopeLine06:

L10:            mov     ah,es:[bx]      ; AH := byte from video buffer

L11:            and     ah,dh           ; zero pixel value at current bit
                                        ;  offset
                or      ah,dl           ; set pixel value in byte

                ror     dl,1            ; rotate pixel value
                ror     dh,1            ; rotate bit mask
                jnc     L14             ; jump if bit mask rotated to
                                        ;  leftmost pixel position

; bit mask not shifted out

                or      di,di           ; test sign of d
                jns     L12             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L11

                mov     es:[bx],ah      ; store remaining pixels in buffer
                jmp     short Lexit

L12:            add     di,VARincr2     ; d := d + incr2
                mov     es:[bx],ah      ; update buffer

                add     bx,si           ; increment y
                xchg    si,VARleafincr  ; exchange interleave increment
                                        ;  values
                loop    L10
                jmp     short Lexit

; bit mask shifted out

L14:            mov     es:[bx],ah      ; update buffer
                inc     bx              ; BX := offset of next byte

                or      di,di           ; test sign of d
                jns     L15             ; jump if non-negative

                add     di,VARincr1     ; d := d + incr1
                loop    L10
                jmp     short Lexit

L15:            add     di,VARincr2     ; d := d + incr2

                add     bx,si           ; increment y
                xchg    si,VARleafincr

                loop    L10
                jmp     short Lexit


; routine for dy > dx (slope > 1)       ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
HiSlopeLine06:

L21:            and     es:[bx],dh      ; zero pixel value in video buffer
                or      es:[bx],dl      ; set pixel value in byte

                add     bx,si           ; increment y
                xchg    si,VARleafincr  ; exchange interleave increment
                                        ;  values

L22:            or      di,di           ; test sign of d
                jns     L23             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L21

                jmp     short Lexit


L23:            add     di,VARincr2     ; d := d + incr2

                ror     dl,1            ; rotate pixel value
                ror     dh,1            ; rotate bit mask
                cmc                     ; cf set if bit mask not rotated to
                                        ;  leftmost pixel position

                adc     bx,0            ; BX := offset of next byte

                loop    L21


Lexit:          pop     di              ; restore registers and return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_Line06         ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

PropagatedPixel DB      00000000b       ; 0
                DB      11111111b       ; 1

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 6-5.  A line-drawing routine for CGA 320-by-200 4-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 6-5'
                NAME    Line04
                PAGE    55,132

;
; Name:         Line04
;
; Function:     Draw a line in 320x200 4-color mode
;
; Caller:       Microsoft C:
;
;                       void Line04(x1,y1,x2,y2,n);
;
;                       int x1,y1,x2,y2;        /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx1           EQU     word ptr [bp+4] ; stack frame addressing
ARGy1           EQU     word ptr [bp+6]
ARGx2           EQU     word ptr [bp+8]
ARGy2           EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]
VARleafincr     EQU     word ptr [bp-6]
VARincr1        EQU     word ptr [bp-8]
VARincr2        EQU     word ptr [bp-10]
VARroutine      EQU     word ptr [bp-12]

ByteOffsetShift EQU     2               ; used to convert pixels to byte
                                        ;  offset

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr04:near

                PUBLIC  _Line04
_Line04         PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,8            ; stack space for local variables
                push    si
                push    di

                mov     si,2000h        ; increment for video buffer
                                        ;  interleave
                mov     di,80-2000h     ; increment from last to first
                                        ;  interleave

                mov     cx,ARGx2
                sub     cx,ARGx1        ; CX := x2 - x1
                jz      VertLine04      ; jump if vertical line

; force x1 < x2

                jns     L01             ; jump if x2 > x1

                neg     cx              ; CX := x1 - x2

                mov     bx,ARGx2        ; exchange x1 and x2
                xchg    bx,ARGx1
                mov     ARGx2,bx

                mov     bx,ARGy2        ; exchange y1 and y2
                xchg    bx,ARGy1
                mov     ARGy2,bx

; calculate dy = ABS(y2-y1)

L01:            mov     bx,ARGy2
                sub     bx,ARGy1        ; BX := y2 - y1
                jnz     L02

                jmp     HorizLine04     ; jump if horizontal line

L02:            jns     L03

                neg     bx              ; BX := y1 - y2
                neg     si              ; negate increments for buffer
                                        ;  interleave
                neg     di
                xchg    si,di           ; exchange increments

; select appropriate routine for slope of line

L03:            mov     VARleafincr,di  ; save increment for buffer
                                        ;  interleave

                mov     VARroutine,offset LoSlopeLine04
                cmp     bx,cx
                jle     L04             ; jump if dy <= dx (slope <= 1)
                mov     VARroutine,offset HiSlopeLine04
                xchg    bx,cx           ; exchange dy and dx

; calculate initial decision variable and increments

L04:            shl     bx,1            ; BX := 2 * dy
                mov     VARincr1,bx     ; incr1 := 2 * dy
                sub     bx,cx
                mov     di,bx           ; DI := d = 2 * dy - dx
                sub     bx,cx
                mov     VARincr2,bx     ; incr2 := 2 * (dy - dx)

; calculate first pixel address

                push    cx              ; preserve this register
                mov     ax,ARGy1        ; AX := y
                mov     bx,ARGx1        ; BX := x
                call    PixelAddr04     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn         ; AL := unshifted pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position

                mov     dx,ax           ; DH := bit mask

                                        ; DL := pixel value
                not     dh              ; DH := inverse bit mask

                pop     cx              ; restore this register
                inc     cx              ; CX := # of pixels to draw

                test    bx,2000h        ; set zero flag if BX in 1st
                                        ;  interleave
                jz      L05

                xchg    si,VARleafincr  ; exchange increment values if 1st
                                        ;  pixel
                                        ;  lies in 1st interleave

L05:            jmp     VARroutine      ; jump to appropriate routine for
                                        ;  slope

; routine for vertical lines

VertLine04:     mov     ax,ARGy1        ; AX := y1
                mov     bx,ARGy2        ; BX := y2
                mov     cx,bx
                sub     cx,ax           ; CX := dy
                jge     L31             ; jump if dy >= 0

                neg     cx              ; force dy >= 0
                mov     ax,bx           ; AX := y2

L31:            inc     cx              ; CX := # of pixels to draw
                mov     bx,ARGx1        ; BX := x
                push    cx              ; preserve this register
                call    PixelAddr04     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     al,ARGn         ; AL := pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position
                not     ah              ; AH := inverse bit mask
                pop     cx              ; restore this register

                test    bx,si           ; set zero flag if BX in 1st
                                        ;  interleave
                jz      L32

                xchg    si,di           ; exchange increment values if 1st
                                        ;  pixel lies
                                        ;  in 1st interleave

L32:            and     es:[bx],ah      ; zero pixel in buffer
                or      es:[bx],al      ; set pixel value in buffer

                add     bx,si           ; increment to next portion of
                                        ;  interleave
                xchg    si,di           ; toggle between increment values

                loop    L32

                jmp     Lexit


; routine for horizontal lines (slope = 0)

HorizLine04:    mov     ax,ARGy1
                mov     bx,ARGx1
                call    PixelAddr04     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     di,bx           ; ES:DI -> buffer

                mov     dh,ah
                not     dh              ; DH := unshifted bit mask for
                                        ;        leftmost byte
                mov     dl,0FFh         ; DL := unshifted bit mask for
                                        ;        rightmost byte

                shl     dh,cl           ; DH := reverse bit mask for first
                                        ;        byte
                not     dh              ; DH := bit mask for first byte

                mov     cx,ARGx2
                and     cl,3
                xor     cl,3
                shl     cl,1            ; CL := number of bits to shift
                                        ;        left
                shl     dl,cl           ; DL := bit mask for last byte

; determine byte offset of first and last pixel in the line

                mov     ax,ARGx2        ; AX := x2
                mov     bx,ARGx1        ; BX := x1

                mov     cl,ByteOffsetShift      ; number of bits to shift
                                                ;  to convert pixels to
                                                ;  bytes

                shr     ax,cl           ; AX := byte offset of x2
                shr     bx,cl           ; BX := byte offset of x1
                mov     cx,ax
                sub     cx,bx           ; CX := (# bytes in line) - 1

; propagate pixel value throughout one byte

                mov     bx,offset DGROUP:PropagatedPixel
                mov     al,ARGn         ; AL := pixel value
                xlat                    ; AL := propagated pixel value

; set pixels in leftmost byte of the line

                or      dh,dh
                js      L43             ; jump if byte-aligned (x1 is
                                        ;  leftmost pixel in byte)
                or      cx,cx
                jnz     L42             ; jump if more than one byte in
                                        ;  the line

                and     dl,dh           ; bit mask for the line
                jmp     short L44

L42:            mov     ah,al
                and     ah,dh           ; AH := masked pixel bits
                not     dh              ; DH := reverse bit mask for
                                        ;        1st byte
                and     es:[di],dh      ; zero masked pixels in buffer
                or      es:[di],ah      ; update masked pixels in buffer
                inc     di
                dec     cx

; use a fast 8086 machine instruction to draw the remainder of the line

L43:            rep     stosb           ; update all pixels in the line

; set pixels in the rightmost byte of the line

L44:            and     al,dl           ; AL := masked pixels for last byte
                not     dl
                and     es:[di],dl      ; zero masked pixels in buffer
                or      es:[di],al      ; update masked pixels in buffer

                jmp     Lexit

; routine for dy <= dx (slope <= 1)     ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
LoSlopeLine04:

L10:            mov     ah,es:[bx]      ; AH := byte from video buffer

L11:            and     ah,dh           ; zero pixel value at current bit
                                        ;  offset
                or      ah,dl           ; set pixel value in byte

                ror     dl,1            ; rotate pixel value
                ror     dl,1
                ror     dh,1            ; rotate bit mask
                ror     dh,1
                jnc     L14             ; jump if bit mask rotated to
                                        ;  leftmost pixel position

; bit mask not shifted out

                or      di,di           ; test sign of d
                jns     L12             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L11

                mov     es:[bx],ah      ; store remaining pixels in buffer
                jmp     short Lexit

L12:            add     di,VARincr2     ; d := d + incr2
                mov     es:[bx],ah      ; update buffer

                add     bx,si           ; increment y
                xchg    si,VARleafincr  ; exchange interleave increment
                                        ;  values

                loop    L10
                jmp     short Lexit

; bit mask shifted out

L14:            mov     es:[bx],ah      ; update buffer
                inc     bx              ; BX := offset of next byte

                or      di,di           ; test sign of d
                jns     L15             ; jump if non-negative

                add     di,VARincr1     ; d := d + incr1
                loop    L10
                jmp     short Lexit


L15:            add     di,VARincr2     ; d := d + incr2

                add     bx,si           ; increment y
                xchg    si,VARleafincr

                loop    L10
                jmp     short Lexit


; routine for dy > dx (slope > 1)       ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
HiSlopeLine04:

L21:            and     es:[bx],dh      ; zero pixel value in video buffer
                or      es:[bx],dl      ; set pixel value in byte

                add     bx,si           ; increment y
                xchg    si,VARleafincr  ; exchange interleave increment
                                        ;  values

L22:            or      di,di           ; test sign of d
                jns     L23             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L21

                jmp     short Lexit


L23:            add     di,VARincr2     ; d := d + incr2

                ror     dl,1            ; rotate pixel value
                ror     dl,1
                ror     dh,1            ; rotate bit mask
                ror     dh,1
                cmc                     ; cf set if bit mask not rotated to
                                        ;  leftmost pixel position

                adc     bx,0            ; BX := offset of next byte

                loop    L21


Lexit:          pop     di              ; restore registers and return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_Line04         ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

PropagatedPixel DB      00000000b       ; 0
                DB      01010101b       ; 1
                DB      10101010b       ; 2
                DB      11111111b       ; 3

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 6-6.  A line-drawing routine for Hercules monochrome graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 6-6'
                NAME    LineHGC
                PAGE    55,132

;
; Name:         LineHGC
;
; Function:     Draw a line in HGC or HGC+ 720x348 graphics
;
; Caller:       Microsoft C:
;
;                       void LineHGC(x1,y1,x2,y2,n);
;
;                       int x1,y1,x2,y2;        /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx1           EQU     word ptr [bp+4] ; stack frame addressing
ARGy1           EQU     word ptr [bp+6]
ARGx2           EQU     word ptr [bp+8]
ARGy2           EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]
VARleafincr     EQU     word ptr [bp-6]
VARincr1        EQU     word ptr [bp-8]
VARincr2        EQU     word ptr [bp-10]
VARroutine      EQU     word ptr [bp-12]

ByteOffsetShift EQU     3               ; used to convert pixels to byte
                                        ;  offset

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddrHGC:near

                PUBLIC  _LineHGC
_LineHGC        PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,8            ; stack space for local variables
                push    si
                push    di

                mov     si,2000h        ; increment for video buffer
                                        ;  interleave
                mov     di,90-8000h     ; increment from last to first
                                        ;  interleave

                mov     cx,ARGx2
                sub     cx,ARGx1        ; CX := x2 - x1
                jz      VertLineHGC     ; jump if vertical line

; force x1 < x2

                jns     L01             ; jump if x2 > x1

                neg     cx              ; CX := x1 - x2

                mov     bx,ARGx2        ; exchange x1 and x2
                xchg    bx,ARGx1
                mov     ARGx2,bx

                mov     bx,ARGy2        ; exchange y1 and y2
                xchg    bx,ARGy1
                mov     ARGy2,bx

; calculate dy = ABS(y2-y1)

L01:            mov     bx,ARGy2
                sub     bx,ARGy1        ; BX := y2 - y1
                jz      HorizLineHGC    ; jump if horizontal line

                jns     L03

                neg     bx              ; BX := y1 - y2
                neg     si              ; negate increments for buffer
                                        ;  interleave
                neg     di

; select appropriate routine for slope of line

L03:            mov     VARleafincr,di  ; save increment for buffer
                                        ;  interleave

                mov     VARroutine,offset LoSlopeLineHGC
                cmp     bx,cx
                jle     L04             ; jump if dy <= dx (slope <= 1)
                mov     VARroutine,offset HiSlopeLineHGC
                xchg    bx,cx           ; exchange dy and dx

; calculate initial decision variable and increments

L04:            shl     bx,1            ; BX := 2 * dy
                mov     VARincr1,bx     ; incr1 := 2 * dy
                sub     bx,cx
                mov     di,bx           ; DI := d = 2 * dy - dx
                sub     bx,cx
                mov     VARincr2,bx     ; incr2 := 2 * (dy - dx)

; calculate first pixel address

                push    cx              ; preserve this register
                mov     ax,ARGy1        ; AX := y
                mov     bx,ARGx1        ; BX := x
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn         ; AL := unshifted pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position

                mov     dx,ax           ; DH := bit mask
                                        ; DL := pixel value
                not     dh              ; DH := inverse bit mask

                pop     cx              ; restore this register
                inc     cx              ; CX := # of pixels to draw

                jmp     VARroutine      ; jump to appropriate routine for
                                        ;  slope

; routine for vertical lines

VertLineHGC:    mov     ax,ARGy1        ; AX := y1
                mov     bx,ARGy2        ; BX := y2
                mov     cx,bx
                sub     cx,ax           ; CX := dy
                jge     L31             ; jump if dy >= 0

                neg     cx              ; force dy >= 0
                mov     ax,bx           ; AX := y2

L31:            inc     cx              ; CX := # of pixels to draw
                mov     bx,ARGx1        ; BX := x
                push    cx              ; preserve this register
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     al,ARGn         ; AL := pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position
                not     ah              ; AH := inverse bit mask
                pop     cx              ; restore this register

; draw the line
                test    al,al
                jz      L34             ; jump if pixel value is zero

L32:            or      es:[bx],al      ; set pixel values in buffer

                add     bx,si           ; increment to next portion of
                                        ;  interleave
                jns     L33
                add     bx,di           ; increment to first portion of
                                        ;  interleave
L33:            loop    L32
                jmp     short L36

L34:            and     es:[bx],ah      ; reset pixel values in buffer

                add     bx,si           ; increment to next portion of
                                        ;  interleave
                jns     L35
                add     bx,di           ; increment to first portion of
                                        ;  interleave
L35:            loop    L34

L36:            jmp     Lexit


; routine for horizontal lines (slope = 0)

HorizLineHGC:   mov     ax,ARGy1
                mov     bx,ARGx1
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     di,bx           ; ES:DI -> buffer
                mov     dh,ah
                not     dh              ; DH := unshifted bit mask for
                                        ;        leftmost byte
                mov     dl,0FFh         ; DL := unshifted bit mask for
                                        ;        rightmost byte
                shl     dh,cl           ; DH := reverse bit mask for first
                                        ;        byte
                not     dh              ; DH := bit mask for first byte

                mov     cx,ARGx2
                and     cl,7
                xor     cl,7            ; CL := number of bits to shift
                                        ;        left
                shl     dl,cl           ; DL := bit mask for last byte

; determine byte offset of first and last pixel in the line

                mov     ax,ARGx2        ; AX := x2
                mov     bx,ARGx1        ; BX := x1

                mov     cl,ByteOffsetShift      ; number of bits to shift
                                                ;  to convert pixels to
                                                ;  bytes

                shr     ax,cl           ; AX := byte offset of x2
                shr     bx,cl           ; BX := byte offset of x1
                mov     cx,ax
                sub     cx,bx           ; CX := (# bytes in line) - 1

; propagate pixel value throughout one byte

                mov     bx,offset DGROUP:PropagatedPixel
                mov     al,ARGn         ; AL := pixel value
                xlat                    ; AL := propagated pixel value

; set pixels in leftmost byte of the line

                or      dh,dh
                js      L43             ; jump if byte-aligned (x1 is
                                        ;  leftmost
                                        ;  pixel in byte)
                or      cx,cx
                jnz     L42             ; jump if more than one byte in the
                                        ;  line

                and     dl,dh           ; bit mask for the line
                jmp     short L44

L42:            mov     ah,al
                and     ah,dh           ; AH := masked pixel bits
                not     dh              ; DH := reverse bit mask for 1st
                                        ;        byte
                and     es:[di],dh      ; zero masked pixels in buffer
                or      es:[di],ah      ; update masked pixels in buffer
                inc     di
                dec     cx

; use a fast 8086 machine instruction to draw the remainder of the line

L43:            rep     stosb           ; update all pixels in the line

; set pixels in the rightmost byte of the line

L44:            and     al,dl           ; AL := masked pixels for last byte
                not     dl
                and     es:[di],dl      ; zero masked pixels in buffer
                or      es:[di],al      ; update masked pixels in buffer

                jmp     Lexit

; routine for dy <= dx (slope <= 1)     ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
LoSlopeLineHGC:

L10:            mov     ah,es:[bx]      ; AH := byte from video buffer

L11:            and     ah,dh           ; zero pixel value at current bit
                                        ;  offset
                or      ah,dl           ; set pixel value in byte

                ror     dl,1            ; rotate pixel value
                ror     dh,1            ; rotate bit mask
                jnc     L14             ; jump if bit mask rotated to
                                        ;  leftmost pixel position

; bit mask not shifted out

                or      di,di           ; test sign of d
                jns     L12             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L11

                mov     es:[bx],ah      ; store remaining pixels in buffer
                jmp     short Lexit

L12:            add     di,VARincr2     ; d := d + incr2
                mov     es:[bx],ah      ; update buffer

                add     bx,si           ; increment y
                jns     L13             ; jump if not in last interleave

                add     bx,VARleafincr  ; increment into next interleave

L13:            loop    L10
                jmp     short Lexit

; bit mask shifted out

L14:            mov     es:[bx],ah      ; update buffer
                inc     bx              ; BX := offset of next byte

                or      di,di           ; test sign of d
                jns     L15             ; jump if non-negative

                add     di,VARincr1     ; d := d + incr1
                loop    L10
                jmp     short Lexit

L15:            add     di,VARincr2     ; d := d + incr2

                add     bx,si           ; increment y
                jns     L16             ; jump if not in last interleave

                add     bx,VARleafincr  ; increment into next interleave

L16:            loop    L10             ; loop until all pixels are set
                jmp     short Lexit


; routine for dy > dx (slope > 1)       ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
HiSlopeLineHGC:

L21:            and     es:[bx],dh      ; zero pixel value in video buffer
                or      es:[bx],dl      ; set pixel value in byte

                add     bx,si           ; increment y
                jns     L22             ; jump if not in last interleave

                add     bx,VARleafincr  ; increment into next interleave

L22:            or      di,di
                jns     L23             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L21
                jmp     short Lexit


L23:            add     di,VARincr2     ; d := d + incr2

                ror     dl,1            ; rotate pixel value
                ror     dh,1            ; rotate bit mask
                cmc                     ; cf set if bit mask not rotated to
                                        ;  leftmost pixel position
                adc     bx,0            ; BX := offset of next byte

                loop    L21


Lexit:          pop     di              ; restore registers and return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_LineHGC        ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

PropagatedPixel DB      00000000b       ; 0
                DB      11111111b       ; 1

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 6-7.  A line-drawing routine for native EGA graphics modes.
���������������������������������������������������������������������������

                TITLE   'Listing 6-7'
                NAME    Line10
                PAGE    55,132

;
; Name:         Line10
;
; Function:     Draw a line in the following EGA and VGA graphics modes:
;                       200-line 16-color modes
;                       350-line modes
;                       640x480 16-color
;
; Caller:       Microsoft C:
;
;                       void Line10(x1,y1,x2,y2,n);
;
;                       int x1,y1,x2,y2;        /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx1           EQU     word ptr [bp+4] ; stack frame addressing
ARGy1           EQU     word ptr [bp+6]
ARGx2           EQU     word ptr [bp+8]
ARGy2           EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]
VARvertincr     EQU     word ptr [bp-6]
VARincr1        EQU     word ptr [bp-8]
VARincr2        EQU     word ptr [bp-10]
VARroutine      EQU     word ptr [bp-12]

ByteOffsetShift EQU     3               ; used to convert pixels to byte
                                        ;  offset
BytesPerLine    EQU     80
RMWbits         EQU     0               ; value for Data Rotate/Func Select
                                        ;  reg


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _Line10
_Line10         PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,8            ; stack space for local variables
                push    si
                push    di

; configure the Graphics Controller

                mov     dx,3CEh         ; DX := Graphics Controller port
                                        ;        addr

                mov     ah,ARGn         ; AH := pixel value
                xor     al,al           ; AL := Set/Reset Register number
                out     dx,ax

                mov     ax,0F01h        ; AH := 1111b (bit plane mask for
                                        ;  Enable Set/Reset)
                out     dx,ax           ; AL := Enable Set/Reset Register #

                mov     ah,RMWbits      ; bits 3 and 4 of AH := function
                mov     al,3            ; AL := Data Rotate/Func Select
                                        ;        reg #
                out     dx,ax

; check for vertical line

                mov     si,BytesPerLine ; increment for video buffer

                mov     cx,ARGx2
                sub     cx,ARGx1        ; CX := x2 - x1
                jz      VertLine10      ; jump if vertical line

; force x1 < x2

                jns     L01             ; jump if x2 > x1

                neg     cx              ; CX := x1 - x2

                mov     bx,ARGx2        ; exchange x1 and x2
                xchg    bx,ARGx1
                mov     ARGx2,bx

                mov     bx,ARGy2        ; exchange y1 and y2
                xchg    bx,ARGy1
                mov     ARGy2,bx

; calculate dy = ABS(y2-y1)

L01:            mov     bx,ARGy2
                sub     bx,ARGy1        ; BX := y2 - y1
                jz      HorizLine10     ; jump if horizontal line

                jns     L03             ; jump if slope is positive

                neg     bx              ; BX := y1 - y2
                neg     si              ; negate increment for buffer
                                        ;  interleave

; select appropriate routine for slope of line

L03:            mov     VARvertincr,si  ; save vertical increment

                mov     VARroutine,offset LoSlopeLine10
                cmp     bx,cx
                jle     L04             ; jump if dy <= dx (slope <= 1)
                mov     VARroutine,offset HiSlopeLine10
                xchg    bx,cx           ; exchange dy and dx

; calculate initial decision variable and increments

L04:            shl     bx,1            ; BX := 2 * dy
                mov     VARincr1,bx     ; incr1 := 2 * dy
                sub     bx,cx
                mov     si,bx           ; SI := d = 2 * dy - dx
                sub     bx,cx
                mov     VARincr2,bx     ; incr2 := 2 * (dy - dx)

; calculate first pixel address

                push    cx              ; preserve this register
                mov     ax,ARGy1        ; AX := y
                mov     bx,ARGx1        ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     di,bx           ; ES:DI -> buffer
                shl     ah,cl           ; AH := bit mask in proper position
                mov     bl,ah           ; AH,BL := bit mask
                mov     al,8            ; AL := Bit Mask Register number

                pop     cx              ; restore this register
                inc     cx              ; CX := # of pixels to draw

                jmp     VARroutine      ; jump to appropriate routine for
                                        ;  slope

; routine for vertical lines

VertLine10:     mov     ax,ARGy1        ; AX := y1
                mov     bx,ARGy2        ; BX := y2
                mov     cx,bx
                sub     cx,ax           ; CX := dy
                jge     L31             ; jump if dy >= 0

                neg     cx              ; force dy >= 0
                mov     ax,bx           ; AX := y2

L31:            inc     cx              ; CX := # of pixels to draw
                mov     bx,ARGx1        ; BX := x
                push    cx              ; preserve this register
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
; set up Graphics Controller

                shl     ah,cl           ; AH := bit mask in proper position
                mov     al,8            ; AL := Bit Mask reg number
                out     dx,ax

                pop     cx              ; restore this register

; draw the line

L32:            or      es:[bx],al      ; set pixel
                add     bx,si           ; increment to next line
                loop    L32

                jmp     Lexit


; routine for horizontal lines (slope = 0)

HorizLine10:
                push    ds              ; preserve DS

                mov     ax,ARGy1
                mov     bx,ARGx1
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     di,bx           ; ES:DI -> buffer
                mov     dh,ah           ; DH := unshifted bit mask for
                                        ;        leftmost byte
                not     dh
                shl     dh,cl           ; DH := reverse bit mask for first
                                                 byte
                not     dh              ; DH := bit mask for first byte

                mov     cx,ARGx2
                and     cl,7
                xor     cl,7            ; CL := number of bits to shift
                                        ;        left
                mov     dl,0FFh         ; DL := unshifted bit mask for
                                        ;        rightmost byte
                shl     dl,cl           ; DL := bit mask for last byte

; determine byte offset of first and last pixel in the line

                mov     ax,ARGx2        ; AX := x2
                mov     bx,ARGx1        ; BX := x1

                mov     cl,ByteOffsetShift      ; number of bits to shift
                                                ;  to convert pixels to
                                                ;  bytes

                shr     ax,cl           ; AX := byte offset of x2
                shr     bx,cl           ; BX := byte offset of x1
                mov     cx,ax
                sub     cx,bx           ; CX := (# bytes in line) - 1

; get Graphics Controller port address into DX

                mov     bx,dx           ; BH := bit mask for first byte
                                        ; BL := bit mask for last byte
                mov     dx,3CEh         ; DX := Graphics Controller port
                mov     al,8            ; AL := Bit Mask Register number

; make video buffer addressable through DS:SI

                push    es
                pop     ds
                mov     si,di           ; DS:SI -> video buffer

; set pixels in leftmost byte of the line

                or      bh,bh
                js      L43             ; jump if byte-aligned (x1 is
                                        ;  leftmost pixel in byte)
                or      cx,cx
                jnz     L42             ; jump if more than one byte in the
                                        ;  line

                and     bl,bh           ; BL := bit mask for the line
                jmp     short L44

L42:            mov     ah,bh           ; AH := bit mask for 1st byte
                out     dx,ax           ; update Graphics Controller

                movsb                   ; update bit planes
                dec     cx

; use a fast 8086 machine instruction to draw the remainder of the line

L43:            mov     ah,11111111b    ; AH := bit mask
                out     dx,ax           ; update Bit Mask Register
                rep     movsb           ; update all pixels in the line

; set pixels in the rightmost byte of the line

L44:            mov     ah,bl           ; AH := bit mask for last byte
                out     dx,ax           ; update Graphics Controller

                movsb                   ; update bit planes

                pop     ds              ; restore DS
                jmp     short Lexit

; routine for dy >= dx (slope <= 1)     ; ES:DI -> video buffer
                                        ; AL = Bit Mask Register number
                                        ; BL = bit mask for 1st pixel
                                        ; CX = # pixels to draw
                                        ; DX = Graphics Controller port
                                        ;       addr
                                        ; SI = decision variable
LoSlopeLine10:

L10:            mov     ah,bl           ; AH := bit mask for next pixel

L11:            or      ah,bl           ; mask current pixel position
                ror     bl,1            ; rotate pixel value
                jc      L14             ; jump if bit mask rotated to
                                        ;  leftmost pixel position

; bit mask not shifted out

                or      si,si           ; test sign of d
                jns     L12             ; jump if d >= 0

                add     si,VARincr1     ; d := d + incr1
                loop    L11

                out     dx,ax           ; update Bit Mask Register
                or      es:[di],al      ; set remaining pixel(s)
                jmp     short Lexit

L12:            add     si,VARincr2     ; d := d + incr2
                out     dx,ax           ; update Bit Mask Register

                or      es:[di],al      ; update bit planes

                add     di,VARvertincr  ; increment y
                loop    L10
                jmp     short Lexit

; bit mask shifted out

L14:            out     dx,ax           ; update Bit Mask Register ...

                or      es:[di],al      ; update bit planes
                inc     di              ; increment x

                or      si,si           ; test sign of d
                jns     L15             ; jump if non-negative

                add     si,VARincr1     ; d := d + incr1
                loop    L10
                jmp     short Lexit

L15:            add     si,VARincr2     ; d := d + incr2
                add     di,VARvertincr  ; vertical increment
                loop    L10
                jmp     short Lexit

; routine for dy > dx (slope > 1)       ; ES:DI -> video buffer
                                        ; AH = bit mask for 1st pixel
                                        ; AL = Bit Mask Register number
                                        ; CX = # pixels to draw
                                        ; DX = Graphics Controller port
                                        ;       addr
                                        ; SI = decision variable
HiSlopeLine10:
                mov     bx,VARvertincr  ; BX := y-increment

L21:            out     dx,ax           ; update Bit Mask Register
                or      es:[di],al      ; update bit planes

                add     di,bx           ; increment y

L22:            or      si,si           ; test sign of d
                jns     L23             ; jump if d >= 0

                add     si,VARincr1     ; d := d + incr1
                loop    L21
                jmp     short Lexit


L23:            add     si,VARincr2     ; d := d + incr2

                ror     ah,1            ; rotate bit mask
                adc     di,0            ; increment DI if when mask rotated
                                        ;  to leftmost pixel position

                loop    L21


; restore default Graphics Controller state and return to caller

Lexit:          xor     ax,ax           ; AH := 0, AL := 0
                out     dx,ax           ; restore Set/Reset Register

                inc     ax              ; AH := 0, AL := 1
                out     dx,ax           ; restore Enable Set/Reset Register

                mov     al,3            ; AH := 0, AL := 3
                out     dx,ax           ; AL := Data Rotate/Func Select
                                        ;        reg #

                mov     ax,0FF08h       ; AH := 11111111b, AL := 8
                out     dx,ax           ; restore Bit Mask Register

                pop     di              ; restore registers and return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_Line10         ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 6-8.  A line-drawing routine for MCGA and VGA 640-by-480
              2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 6-8'
                NAME    Line11
                PAGE    55,132

;
; Name:         Line11
;
; Function:     Draw a line in 640x480 2-color mode (MCGA, VGA)
;
; Caller:       Microsoft C:
;
;                       void Line11(x1,y1,x2,y2,n);
;

;                       int x1,y1,x2,y2;        /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx1           EQU     word ptr [bp+4] ; stack frame addressing
ARGy1           EQU     word ptr [bp+6]
ARGx2           EQU     word ptr [bp+8]
ARGy2           EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]
VARincr1        EQU     word ptr [bp-6]
VARincr2        EQU     word ptr [bp-8]
VARroutine      EQU     word ptr [bp-10]

BytesPerLine    EQU     80              ; bytes in one row of pixels
ByteOffsetShift EQU     3               ; used to convert pixels to byte
                                        ;  offset

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr10:near

                PUBLIC  _Line11
_Line11         PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,6            ; stack space for local variables
                push    si
                push    di

; check for vertical line

                mov     si,BytesPerLine ; SI := initial y-increment

                mov     cx,ARGx2
                sub     cx,ARGx1        ; CX := x2 - x1
                jz      VertLine11      ; jump if vertical line

; force x1 < x2

                jns     L01             ; jump if x2 > x1

                neg     cx              ; CX := x1 - x2

                mov     bx,ARGx2        ; exchange x1 and x2
                xchg    bx,ARGx1
                mov     ARGx2,bx

                mov     bx,ARGy2        ; exchange y1 and y2
                xchg    bx,ARGy1
                mov     ARGy2,bx

; calculate dy = ABS(y2-y1)

L01:            mov     bx,ARGy2
                sub     bx,ARGy1        ; BX := y2 - y1
                jnz     L02

                jmp     HorizLine11     ; jump if horizontal line

L02:            jns     L03
                neg     bx              ; BX := y1 - y2
                neg     si              ; negate y-increment

; select appropriate routine for slope of line

L03:            mov     VARroutine,offset LoSlopeLine11
                cmp     bx,cx
                jle     L04             ; jump if dy <= dx (slope <= 1)
                mov     VARroutine,offset HiSlopeLine11
                xchg    bx,cx           ; exchange dy and dx

; calculate initial decision variable and increments

L04:            shl     bx,1            ; BX := 2 * dy
                mov     VARincr1,bx     ; incr1 := 2 * dy
                sub     bx,cx
                mov     di,bx           ; DI := d = 2 * dy - dx
                sub     bx,cx
                mov     VARincr2,bx     ; incr2 := 2 * (dy - dx)

; calculate first pixel address

                push    cx              ; preserve this register
                mov     ax,ARGy1        ; AX := y
                mov     bx,ARGx1        ; BX := x
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn         ; AL := unshifted pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position

                mov     dx,ax           ; DH := bit mask
                                        ; DL := pixel value
                not     dh              ; DH := inverse bit mask

                pop     cx              ; restore this register
                inc     cx              ; CX := # of pixels to draw

                jmp     VARroutine      ; jump to appropriate routine for
                                        ;  slope

; routine for vertical lines

VertLine11:     mov     ax,ARGy1        ; AX := y1
                mov     bx,ARGy2        ; BX := y2
                mov     cx,bx
                sub     cx,ax           ; CX := dy
                jge     L31             ; jump if dy >= 0

                neg     cx              ; force dy >= 0
                mov     ax,bx           ; AX := y2

L31:            inc     cx              ; CX := # of pixels to draw
                mov     bx,ARGx1        ; BX := x
                push    cx              ; preserve this register
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     al,ARGn         ; AL := pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position
                not     ah              ; AH := inverse bit mask
                pop     cx              ; restore this register

; draw the line

                test    al,al
                jz      L33             ; jump if pixel value = 0

L32:            or      es:[bx],al      ; set pixel values in buffer
                add     bx,si
                loop    L32
                jmp     short L34

L33:            and     es:[bx],ah      ; reset pixel values in buffer
                add     bx,si
                loop    L33

L34:            jmp     Lexit


; routine for horizontal lines (slope = 0)

HorizLine11:    mov     ax,ARGy1
                mov     bx,ARGx1
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     di,bx           ; ES:DI -> buffer

                mov     dh,ah
                not     dh              ; DH := unshifted bit mask for
                                        ;        leftmost byte
                mov     dl,0FFh         ; DL := unshifted bit mask for
                                        ;        rightmost byte

                shl     dh,cl           ; DH := reverse bit mask for first
                                        ;        byte
                not     dh              ; DH := bit mask for first byte

                mov     cx,ARGx2
                and     cl,7
                xor     cl,7            ; CL := number of bits to shift
                                        ;        left
                shl     dl,cl           ; DL := bit mask for last byte

; determine byte offset of first and last pixel in the line

                mov     ax,ARGx2        ; AX := x2
                mov     bx,ARGx1        ; BX := x1

                mov     cl,ByteOffsetShift      ; number of bits to shift
                                                ;  to convert pixels to
                                                ;  bytes

                shr     ax,cl           ; AX := byte offset of x2
                shr     bx,cl           ; BX := byte offset of x1
                mov     cx,ax
                sub     cx,bx           ; CX := (# bytes in line) - 1

; propagate pixel value throughout one byte

                mov     bx,offset DGROUP:PropagatedPixel
                mov     al,ARGn         ; AL := pixel value
                xlat

; set pixels in leftmost byte of the line

                or      dh,dh
                js      L43             ; jump if byte-aligned (x1 is
                                        ;  leftmost
                                        ;  pixel in byte)
                or      cx,cx
                jnz     L42             ; jump if more than one byte in the
                                        ;  line

                and     dl,dh           ; bit mask for the line
                jmp     short L44

L42:            mov     ah,al
                and     ah,dh           ; AH := masked pixel bits
                not     dh              ; DH := reverse bit mask for 1st
                                        ;        byte
                and     es:[di],dh      ; zero masked pixels in buffer
                or      es:[di],ah      ; update masked pixels in buffer
                inc     di
                dec     cx

; use a fast 8086 machine instruction to draw the remainder of the line

L43:            rep     stosb           ; update all pixels in the line

; set pixels in the rightmost byte of the line

L44:            and     al,dl           ; AL := masked pixels for last byte
                not     dl
                and     es:[di],dl      ; zero masked pixels in buffer
                or      es:[di],al      ; update masked pixels in buffer

                jmp     Lexit


; routine for dy <= dx (slope <= 1)     ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = bytes per pixel row
                                        ; DI = decision variable

LoSlopeLine11:

L10:            mov     ah,es:[bx]      ; AH := byte from video buffer

L11:            and     ah,dh           ; zero pixel value at current bit
                                        ;  offset
                or      ah,dl           ; set pixel value in byte

                ror     dl,1            ; rotate pixel value
                ror     dh,1            ; rotate bit mask
                jnc     L14             ; jump if bit mask rotated to
                                        ;  leftmost pixel position

; bit mask not shifted out

                or      di,di           ; test sign of d
                jns     L12             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L11

                mov     es:[bx],ah      ; store remaining pixels in buffer
                jmp     short Lexit

L12:            add     di,VARincr2     ; d := d + incr2
                mov     es:[bx],ah      ; update buffer

                add     bx,si           ; increment y
                loop    L10
                jmp     short Lexit

; bit mask shifted out

L14:            mov     es:[bx],ah      ; update buffer
                inc     bx              ; BX := offset of next byte

                or      di,di           ; test sign of d
                jns     L15             ; jump if non-negative

                add     di,VARincr1     ; d := d + incr1
                loop    L10
                jmp     short Lexit

L15:            add     di,VARincr2     ; d := d + incr2

                add     bx,si           ; increment y
                loop    L10
                jmp     short Lexit

; routine for dy > dx (slope > 1)       ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = inverse bit mask
                                        ; DL = pixel value in proper
                                        ;       position
                                        ; SI = bytes per pixel row
                                        ; DI = decision variable
HiSlopeLine11:

L21:            and     es:[bx],dh      ; zero pixel value in video buffer
                or      es:[bx],dl      ; set pixel value in byte

                add     bx,si           ; increment y

L22:            or      di,di           ; test sign of d
                jns     L23             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L21

                jmp     short Lexit


L23:            add     di,VARincr2     ; d := d + incr2

                ror     dl,1            ; rotate pixel value
                ror     dh,1            ; rotate bit mask
                cmc                     ; cf set if bit mask not rotated to
                                        ;  leftmost pixel position

                adc     bx,0            ; BX := offset of next byte

                loop    L21


Lexit:          pop     di              ; restore caller registers and
                                        ;  return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_Line11         ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

PropagatedPixel DB      00000000b       ; 0
                DB      11111111b       ; 1

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 6-9.  A Line-drawing routine for MCGA and VGA 320-by-200
              256-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 6-9'
                NAME    Line13
                PAGE    55,132

;
; Name:         Line13
;
; Function:     Draw a line in MCGA/VGA 320x200 256-color mode
;

; Caller:       Microsoft C:
;
;                       void Line13(x1,y1,x2,y2,n);
;
;                       int x1,y1,x2,y2;        /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx1           EQU     word ptr [bp+4] ; stack frame addressing
ARGy1           EQU     word ptr [bp+6]
ARGx2           EQU     word ptr [bp+8]
ARGy2           EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]
VARincr1        EQU     word ptr [bp-6]
VARincr2        EQU     word ptr [bp-8]
VARroutine      EQU     word ptr [bp-10]

BytesPerLine    EQU     320


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr13:near

                PUBLIC  _Line13
_Line13         PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,6            ; stack space for local variables
                push    si
                push    di

; check for vertical line

                mov     si,BytesPerLine ; initial y-increment

                mov     cx,ARGx2
                sub     cx,ARGx1        ; CX := x2 - x1
                jz      VertLine13      ; jump if vertical line

; force x1 < x2

                jns     L01             ; jump if x2 > x1

                neg     cx              ; CX := x1 - x2

                mov     bx,ARGx2        ; exchange x1 and x2
                xchg    bx,ARGx1
                mov     ARGx2,bx

                mov     bx,ARGy2        ; exchange y1 and y2

                xchg    bx,ARGy1
                mov     ARGy2,bx


; calculate dy = ABS(y2-y1)

L01:            mov     bx,ARGy2
                sub     bx,ARGy1        ; BX := y2 - y1
                jz      HorizLine13     ; jump if horizontal line

                jns     L03             ; jump if slope is positive

                neg     bx              ; BX := y1 - y2
                neg     si              ; negate y-increment

; select appropriate routine for slope of line

L03:            push    si              ; preserve y-increment

                mov     VARroutine,offset LoSlopeLine13
                cmp     bx,cx
                jle     L04             ; jump if dy <= dx (slope <= 1)
                mov     VARroutine,offset HiSlopeLine13
                xchg    bx,cx           ; exchange dy and dx

; calculate initial decision variable and increments

L04:            shl     bx,1            ; BX := 2 * dy
                mov     VARincr1,bx     ; incr1 := 2 * dy
                sub     bx,cx
                mov     si,bx           ; SI := d = 2 * dy - dx

                sub     bx,cx
                mov     VARincr2,bx     ; incr2 := 2 * (dy - dx)

; calculate first pixel address

                push    cx              ; preserve this register
                mov     ax,ARGy1        ; AX := y
                mov     bx,ARGx1        ; BX := x
                call    PixelAddr13     ; ES:BX -> buffer

                mov     di,bx           ; ES:DI -> buffer

                pop     cx              ; restore this register
                inc     cx              ; CX := # of pixels to draw

                pop     bx              ; BX := y-increment
                jmp     VARroutine      ; jump to appropriate routine for
                                        ;  slope


; routine for vertical lines

VertLine13:     mov     ax,ARGy1        ; AX := y1
                mov     bx,ARGy2        ; BX := y2
                mov     cx,bx
                sub     cx,ax           ; CX := dy
                jge     L31             ; jump if dy >= 0

                neg     cx              ; force dy >= 0
                mov     ax,bx           ; AX := y2

L31:            inc     cx              ; CX := # of pixels to draw
                mov     bx,ARGx1        ; BX := x
                push    cx              ; preserve this register
                call    PixelAddr13     ; ES:BX -> video buffer
                pop     cx

                mov     di,bx           ; ES:DI -> video buffer
                dec     si              ; SI := bytes/line - 1

                mov     al,ARGn         ; AL := pixel value

L32:            stosb                   ; set pixel value in buffer
                add     di,si           ; increment to next line
                loop    L32

                jmp     Lexit


; routine for horizontal lines (slope = 0)

HorizLine13:
                push    cx              ; preserve CX
                mov     ax,ARGy1
                mov     bx,ARGx1
                call    PixelAddr13     ; ES:BX -> video buffer
                mov     di,bx           ; ES:DI -> buffer

                pop     cx
                inc     cx              ; CX := number of pixels to draw

                mov     al,ARGn         ; AL := pixel value

                rep     stosb           ; update the video buffer

                jmp     short Lexit


; routine for dy <= dx (slope <= 1)     ; ES:DI -> video buffer
                                        ; BX = y-increment
                                        ; CX = # pixels to draw
                                        ; SI = decision variable
LoSlopeLine13:

                mov     al,ARGn         ; AL := pixel value

L11:            stosb                   ; store pixel, increment x

                or      si,si           ; test sign of d
                jns     L12             ; jump if d >= 0

                add     si,VARincr1     ; d := d + incr1
                loop    L11
                jmp     short Lexit

L12:            add     si,VARincr2     ; d := d + incr2
                add     di,bx           ; increment y
                loop    L11
                jmp     short Lexit


; routine for dy > dx (slope > 1)       ; ES:DI -> video buffer
                                        ; BX = y-increment
                                        ; CX = # pixels to draw
                                        ; SI = decision variable
HiSlopeLine13:
                mov     al,ARGn         ; AL := pixel value

L21:            stosb                   ; update next pixel, increment x

                add     di,bx           ; increment y

L22:            or      si,si           ; test sign of d
                jns     L23             ; jump if d >= 0

                add     si,VARincr1     ; d := d + incr1
                dec     di              ; decrement x (already incremented
                                        ;  by stosb)
                loop    L21
                jmp     short Lexit


L23:            add     si,VARincr2     ; d := d + incr2
                loop    L21


Lexit:          pop     di              ; restore registers and return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_Line13         ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 6-10.  A line-drawing routine for the InColor Card's 720-by-348
               16-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 6-10'
                NAME    LineInC
                PAGE    55,132

;
; Name:         LineInC
;
; Function:     Draw a line in Hercules InColor 720x348 16-color mode
;
; Caller:       Microsoft C:
;
;                       void LineInC(x1,y1,x2,y2,n);
;
;                       int x1,y1,x2,y2;        /* pixel coordinates */
;
;                       int n;                  /* pixel value */
;

ARGx1           EQU     word ptr [bp+4] ; stack frame addressing
ARGy1           EQU     word ptr [bp+6]
ARGx2           EQU     word ptr [bp+8]
ARGy2           EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]
VARleafincr     EQU     word ptr [bp-6]
VARincr1        EQU     word ptr [bp-8]
VARincr2        EQU     word ptr [bp-10]
VARroutine      EQU     word ptr [bp-12]

ByteOffsetShift EQU     3               ; used to convert pixels to byte
                                        ;  offset
DefaultRWColor  EQU     0Fh             ; default value for R/W Color
                                        ;  register


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddrHGC:near

                PUBLIC  _LineInC
_LineInC        PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,8            ; stack space for local variables
                push    si
                push    di

                mov     si,2000h        ; increment for video buffer
                                        ;  interleave
                mov     di,90-8000h     ; increment from last to first
                                        ;  interleave

; set up InColor control registers

                mov     dx,3B4h         ; DX := CRTC I/O port
                mov     ax,5F19h        ; AH bit 6 := 1 (Mask Polarity)
                                        ; AH bits 5-4 := 1 (Write Mode)
                                        ; AH bits 3-0 := "don't care" bits
                                        ; AL := R/W Control Register number
                out     dx,ax           ; set R/W Control Register

                inc     ax              ; AL := 1Ah (R/W Color Reg number)
                mov     ah,ARGn         ; AH := foreground value
                out     dx,ax           ; set R/W color register


                mov     cx,ARGx2
                sub     cx,ARGx1        ; CX := x2 - x1
                jz      VertLineInC     ; jump if vertical line

; force x1 < x2

                jns     L01             ; jump if x2 > x1

                neg     cx              ; CX := x1 - x2

                mov     bx,ARGx2        ; exchange x1 and x2
                xchg    bx,ARGx1
                mov     ARGx2,bx

                mov     bx,ARGy2        ; exchange y1 and y2
                xchg    bx,ARGy1
                mov     ARGy2,bx

; calculate dy = ABS(y2-y1)

L01:            mov     bx,ARGy2
                sub     bx,ARGy1        ; BX := y2 - y1
                jz      HorizLineInC    ; jump if horizontal line

                jns     L03

                neg     bx              ; BX := y1 - y2
                neg     si              ; negate increments for buffer
                                        ;  interleave
                neg     di

; select appropriate routine for slope of line

L03:            mov     VARleafincr,di  ; save increment for buffer
                                        ;  interleave

                mov     VARroutine,offset LoSlopeLineInC
                cmp     bx,cx
                jle     L04             ; jump if dy <= dx (slope <= 1)
                mov     VARroutine,offset HiSlopeLineInC
                xchg    bx,cx           ; exchange dy and dx

; calculate initial decision variable and increments

L04:            shl     bx,1            ; BX := 2 * dy
                mov     VARincr1,bx     ; incr1 := 2 * dy
                sub     bx,cx
                mov     di,bx           ; DI := d = 2 * dy - dx
                sub     bx,cx
                mov     VARincr2,bx     ; incr2 := 2 * (dy - dx)

; calculate first pixel address

                push    cx              ; preserve this register
                mov     ax,ARGy1        ; AX := y
                mov     bx,ARGx1        ; BX := x
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                shl     ah,cl
                mov     dh,ah           ; DH := bit mask in proper position

                pop     cx              ; restore this register
                inc     cx              ; CX := # of pixels to draw

                jmp     VARroutine      ; jump to appropriate routine for
                                        ;  slope


; routine for vertical lines

VertLineInC:    mov     ax,ARGy1        ; AX := y1
                mov     bx,ARGy2        ; BX := y2
                mov     cx,bx
                sub     cx,ax           ; CX := dy
                jge     L31             ; jump if dy >= 0

                neg     cx              ; force dy >= 0
                mov     ax,bx           ; AX := y2

L31:            inc     cx              ; CX := # of pixels to draw
                mov     bx,ARGx1        ; BX := x
                push    cx              ; preserve this register
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                shl     ah,cl           ; AH := bit mask in proper position
                pop     cx              ; restore this register

L32:            or      es:[bx],ah      ; update pixel in buffer

                add     bx,si           ; increment to next portion of
                                        ;  interleave
                jns     L33

                add     bx,di           ; increment to first portion of
                                        ;  interleave

L33:            loop    L32

                jmp     Lexit

; routine for horizontal lines (slope = 0)

HorizLineInC:   mov     ax,ARGy1
                mov     bx,ARGx1
                call    PixelAddrHGC    ; AH := bit mask
                                        ; ES:BX -> video buffer
                                        ; CL := # bits to shift left
                mov     di,bx           ; ES:DI -> buffer
                mov     dh,ah
                not     dh              ; DH := unshifted bit mask for
                                        ;        leftmost byte
                mov     dl,0FFh         ; DL := unshifted bit mask for
                                        ;        rightmost byte

                shl     dh,cl           ; DH := reverse bit mask for first
                                        ;        byte
                not     dh              ; DH := bit mask for first byte
                mov     cx,ARGx2
                and     cl,7
                xor     cl,7            ; CL := number of bits to shift
                                        ;        left
                shl     dl,cl           ; DL := bit mask for last byte

; determine byte offset of first and last pixel in the line

                mov     ax,ARGx2        ; AX := x2
                mov     bx,ARGx1        ; BX := x1

                mov     cl,ByteOffsetShift      ; number of bits to shift
                                                ;  to convert pixels to
                                                ;  bytes

                shr     ax,cl           ; AX := byte offset of x2
                shr     bx,cl           ; BX := byte offset of x1
                mov     cx,ax
                sub     cx,bx           ; CX := (# bytes in line) - 1

; set pixels in leftmost byte of the line

                or      dh,dh
                js      L43             ; jump if byte-aligned (x1 is
                                        ;  leftmost pixel in byte)
                or      cx,cx
                jnz     L42             ; jump if more than one byte in the
                                        ;  line

                and     dl,dh           ; bit mask for the line
                jmp     short L44

L42:            or      es:[di],dh      ; update masked pixels in buffer
                inc     di
                dec     cx

; use a fast 8086 machine instruction to draw the remainder of the line

L43:            mov     al,0FFh         ; 8-pixel bit mask
                rep     stosb           ; update all pixels in the line

; set pixels in the rightmost byte of the line

L44:            or      es:[di],dl      ; update masked pixels in buffer
                jmp     Lexit

; routine for dy <= dx (slope <= 1)     ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = bit mask
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
LoSlopeLineInC:

L10:            mov     ah,es:[bx]      ; latch bit planes
                                        ; AH := 0 because all planes
                                        ;  are "don't care"

L11:            or      ah,dh           ; set pixel value in byte

                ror     dh,1            ; rotate bit mask
                jc      L14             ; jump if bit mask rotated to
                                        ;  leftmost pixel position

; bit mask not shifted out

                or      di,di           ; test sign of d
                jns     L12             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L11

                mov     es:[bx],ah      ; store remaining pixels in buffer
                jmp     short Lexit

L12:            add     di,VARincr2     ; d := d + incr2
                mov     es:[bx],ah      ; update buffer

                add     bx,si           ; increment y
                jns     L13             ; jump if not in last interleave

                add     bx,VARleafincr  ; increment into next interleave

L13:            loop    L10
                jmp     short Lexit

; bit mask shifted out

L14:            mov     es:[bx],ah      ; update buffer
                inc     bx              ; BX := offset of next byte

                or      di,di           ; test sign of d
                jns     L15             ; jump if non-negative

                add     di,VARincr1     ; d := d + incr1
                loop    L10
                jmp     short Lexit

L15:            add     di,VARincr2     ; d := d + incr2

                add     bx,si           ; increment y
                jns     L16             ; jump if not in last interleave

                add     bx,VARleafincr  ; increment into next interleave

L16:            loop    L10             ; loop until all pixels are set
                jmp     short Lexit


; routine for dy > dx (slope > 1)       ; ES:BX -> video buffer
                                        ; CX = # pixels to draw
                                        ; DH = bit mask
                                        ; SI = buffer interleave increment
                                        ; DI = decision variable
HiSlopeLineInC:

L21:            or      es:[bx],dh      ; set pixel value in video buffer

                add     bx,si           ; increment y
                jns     L22             ; jump if not in last interleave

                add     bx,VARleafincr  ; increment into next interleave

L22:            or      di,di
                jns     L23             ; jump if d >= 0

                add     di,VARincr1     ; d := d + incr1
                loop    L21
                jmp     short Lexit


L23:            add     di,VARincr2     ; d := d + incr2

                ror     dh,1            ; rotate bit mask
                adc     bx,0            ; BX := offset of next byte
                                        ;  (incremented if bit mask rotated
                                        ;  to leftmost pixel position)

                loop    L21
                jmp     short Lexit


Lexit:          mov     dx,3B4h         ; DX := CRTC I/O port
                mov     ax,0F18h
                out     dx,ax           ; restore default Plane Mask value

                mov     ax,4019h        ; restore default R/W Control value
                out     dx,ax

                inc     ax              ; restore default R/W Color value
                mov     ah,DefaultRWColor
                out     dx,ax

                pop     di              ; restore registers and return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_LineInC        ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 6-11.  An implementation of the Sutherland-Cohen clipping
               algorithm.
���������������������������������������������������������������������������

/* Listing 6-11 */

struct  EndpointStruct          /* endpoints for clipped line */
{
  int   x1,y1;
  int   x2,y2;
};

struct  RegionStruct            /* rectangular clipping region */
{
  int   Xul;
  int   Yul;
  int   Xlr;
  int   Ylr;
};


union OutcodeUnion      /* outcodes are represented as bit fields */
{
  struct
  {
    unsigned code0 : 1;         /* x < Xul */
    unsigned code1 : 1;         /* y < Yul */
    unsigned code2 : 1;         /* x > Xlr */
    unsigned code3 : 1;         /* y > Ylr */
  }
        ocs;

  int   outcodes;
};


#define X1      ep->x1
#define Y1      ep->y1
#define X2      ep->x2
#define Y2      ep->y2
#define XUL     r->Xul
#define YUL     r->Yul
#define XLR     r->Xlr
#define YLR     r->Ylr


Clip(ep,r)
struct  EndpointStruct  *ep;
struct  RegionStruct    *r;
{
        union OutcodeUnion      ocu1,ocu2;
        int     Inside;
        int     Outside;


        /* initialize 4-bit codes */

        SetOutcodes( &ocu1, r, X1, Y1 );        /* initial 4-bit codes */
        SetOutcodes( &ocu2, r, X2, Y2 );

        Inside  = ((ocu1.outcodes | ocu2.outcodes) == 0);
        Outside = ((ocu1.outcodes & ocu2.outcodes) != 0);

        while (!Outside && !Inside)
        {
          if (ocu1.outcodes==0)     /* swap endpoints if necessary so */
          {                         /*  that (x1,y1) needs to be clipped */
            Swap( &X1, &X2 );
            Swap( &Y1, &Y2 );
            Swap( &ocu1, &ocu2 );
          }


          if (ocu1.ocs.code0)                   /* clip left */
          {
            Y1 += (long)(Y2-Y1)*(XUL-X1)/(X2-X1);
            X1 = XUL;
          }

          else if (ocu1.ocs.code1)              /* clip above */
          {
            X1 += (long)(X2-X1)*(YUL-Y1)/(Y2-Y1);
            Y1 = YUL;
          }

          else if (ocu1.ocs.code2)              /* clip right */
          {
            Y1 += (long)(Y2-Y1)*(XLR-X1)/(X2-X1);
            X1 = XLR;
          }

          else if (ocu1.ocs.code3)              /* clip below */
          {
            X1 += (long)(X2-X1)*(YLR-Y1)/(Y2-Y1);
            Y1 = YLR;
          }

          SetOutcodes( &ocu1, r, X1, Y1 );      /* update for (x1,y1) */

          Inside  = ((ocu1.outcodes | ocu2.outcodes) == 0); /* update  */
          Outside = ((ocu1.outcodes & ocu2.outcodes) != 0); /*  4-bit  */
                                                            /*   codes  */
        }

        return( Inside );
}


SetOutcodes( u, r, x, y )
union OutcodeUnion      *u;
struct RegionStruct     *r;
int     x,y;
{
        u->outcodes = 0;
        u->ocs.code0 = (x < XUL);
        u->ocs.code1 = (y < YUL);
        u->ocs.code2 = (x > XLR);
        u->ocs.code3 = (y > YLR);
}


Swap( pa, pb )
int     *pa,*pb;
{
        int     t;

        t = *pa;
        *pa = *pb;
        *pb = t;
}



���������������������������������������������������������������������������
Listing 7-1.  Drawing an ellipse using the equation of the ellipse.
���������������������������������������������������������������������������

/* Listing 7-1 */

Ellipse( xc, yc, a0, b0 )       /* using equation of ellipse */
int     xc,yc;                  /* center of ellipse */
int     a0,b0;                  /* major and minor axes */
{

        double  x = 0;
        double  y = b0;
        double  Bsquared = (double)b0 * (double)b0;
        double  Asquared = (double)a0 * (double)a0;
        double  sqrt();

        do                      /* do while dy/dx >= -1 */
        {
          y = sqrt( Bsquared - ((Bsquared/Asquared) * x * x) );
          Set4Pixels( (int)x, (int)y, xc, yc, PixelValue );
          ++x;
        }
        while ( (x <= a0) && (Bsquared*x > Asquared*y) );

        while (y >= 0)          /* do while dy/dx < -1 */
        {
          x = sqrt( Asquared - ((Asquared/Bsquared) * y*y) );
          Set4Pixels( (int)x, (int)y, xc, yc, PixelValue );
          --y;
        }
}

Set4Pixels( x, y, xc, yc, n )   /* set pixels in 4 quadrants by symmetry */
int     x,y;
int     xc,yc;
int     n;
{
        SPFunc(xc+x, yc+y, n);
        SPFunc(xc-x, yc+y, n);
        SPFunc(xc+x, yc-y, n);
        SPFunc(xc-x, yc-y, n);
}



���������������������������������������������������������������������������
Listing 7-2.  A high-level implementation of the midpoint algorithm.
���������������������������������������������������������������������������

/* Listing 7-2 */

Ellipse( xc, yc, a0, b0 )
int     xc,yc;                  /* center of ellipse */
int     a0,b0;                  /* semiaxes */

{
        int     x = 0;
        int     y = b0;

        long    a = a0;                         /* use 32-bit precision */
        long    b = b0;

        long    Asquared = a * a;               /* initialize values */
                                                /*  outside */
        long    TwoAsquared = 2 * Asquared;     /*  of loops */
        long    Bsquared = b * b;
        long    TwoBsquared = 2 * Bsquared;

        long    d;
        long    dx,dy;


        d = Bsquared - Asquared*b + Asquared/4L;
        dx = 0;
        dy = TwoAsquared * b;

        while (dx<dy)
        {
          Set4Pixels( x, y, xc, yc, PixelValue );

          if (d > 0L)
          {
            --y;
            dy -= TwoAsquared;
            d -= dy;
          }

          ++x;
          dx += TwoBsquared;
          d += Bsquared + dx;
        }


        d += (3L*(Asquared-Bsquared)/2L - (dx+dy)) / 2L;

        while (y>=0)
        {
          Set4Pixels( x, y, xc, yc, PixelValue );

          if (d < 0L)
          {
            ++x;
            dx += TwoBsquared;
            d += dx;
          }

          --y;
          dy -= TwoAsquared;
          d += Asquared - dy;
        }
}

Set4Pixels( x, y, xc, yc, n )       /* set pixels by symmetry in 4 */
                                    /*  quadrants */
int     x,y;
int     xc,yc;
int     n;
{
        SetPixel( xc+x, yc+y, n );
        SetPixel( xc-x, yc+y, n );
        SetPixel( xc+x, yc-y, n );
        SetPixel( xc-x, yc-y, n );
}



���������������������������������������������������������������������������
Listing 7-3.  A modified version of Set4Pixels that avoids updating
              the same pixel twice.
���������������������������������������������������������������������������

/* Listing 7-3 */

Set4Pixels( x, y, xc, yc, n )   /* avoids setting the same pixel twice */
int     x,y;
int     xc,yc;
int     n;
{
        if (x!=0)
        {
          SetPixel( xc+x, yc+y, n );
          SetPixel( xc-x, yc+y, n );
          if (y!=0)
          {
            SetPixel( xc+x, yc-y, n );
            SetPixel( xc-x, yc-y, n );
          }
        }
        else
        {
          SetPixel( xc, yc+y, n );
          if (y!=0)
            SetPixel( xc, yc-y, n );
        }
}



���������������������������������������������������������������������������
Listing 7-4.  An assembly-language implementation of the midpoint
              algorithm.
���������������������������������������������������������������������������

                TITLE   'Listing 7-4'
                NAME    Ellipse10
                PAGE    55,132

;
; Name:         Ellipse10
;
; Function:     Draw an ellipse in native EGA/VGA graphics modes.
;
; Caller:       Microsoft C:
;
;                       void Ellipse10(xc,yc,a,b,n);
;
;                       int xc,yc;              /* center of ellipse */
;
;                       int a,b;                /* major and minor axes */
;
;                       int n;                  /* pixel value */
;

ARGxc           EQU     word ptr [bp+4] ; stack frame addressing
ARGyc           EQU     word ptr [bp+6]
ARGa            EQU     word ptr [bp+8]
ARGb            EQU     word ptr [bp+10]
ARGn            EQU     byte ptr [bp+12]

ULAddr          EQU     word ptr [bp-6]
URAddr          EQU     word ptr [bp-8]
LLAddr          EQU     word ptr [bp-10]
LRAddr          EQU     word ptr [bp-12]
LMask           EQU     byte ptr [bp-14]
RMask           EQU     byte ptr [bp-16]

VARd            EQU     word ptr [bp-20]
VARdx           EQU     word ptr [bp-24]
VARdy           EQU     word ptr [bp-28]
Asquared        EQU     word ptr [bp-32]
Bsquared        EQU     word ptr [bp-36]
TwoAsquared     EQU     word ptr [bp-40]
TwoBsquared     EQU     word ptr [bp-44]

RMWbits         EQU     00h             ; read-modify-write bits
BytesPerLine    EQU     80


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _Ellipse10
_Ellipse10      PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,40           ; reserve local stack space
                push    si
                push    di

; set Graphics Controller Mode register

                mov     dx,3CEh         ; DX := Graphics Controller I/O
                                        ;        port
                mov     ax,0005h        ; AL := Mode register number
                                        ; AH := Write Mode 0 (bits 0,1)
                out     dx,ax           ;        Read Mode 0 (bit 4)

; set Data Rotate/Function Select register

                mov     ah,RMWbits      ; AH := Read-Modify-Write bits
                mov     al,3            ; AL := Data Rotate/Function Select
                                        ;        reg
                out     dx,ax

; set Set/Reset and Enable Set/Reset registers

                mov     ah,ARGn         ; AH := pixel value
                mov     al,0            ; AL := Set/Reset reg number
                out     dx,ax

                mov     ax,0F01h        ; AH := value for Enable Set/Reset
                                        ;        (all bit planes enabled)
                out     dx,ax           ; AL := Enable Set/Reset reg number

; initial constants

                mov     ax,ARGa
                mul     ax
                mov     Asquared,ax
                mov     Asquared+2,dx   ; a^2
                shl     ax,1
                rcl     dx,1
                mov     TwoAsquared,ax
                mov     TwoAsquared+2,dx ; 2 * a^2

                mov     ax,ARGb
                mul     ax
                mov     Bsquared,ax
                mov     Bsquared+2,dx   ; b^2
                shl     ax,1
                rcl     dx,1
                mov     TwoBsquared,ax
                mov     TwoBsquared+2,dx ; 2 * b^2
;
; plot pixels from (0,b) until dy/dx = -1
;

; initial buffer address and bit mask

                mov     ax,BytesPerLine ; AX := video buffer line length
                mul     ARGb            ; AX := relative byte offset of b
                mov     si,ax
                mov     di,ax

                mov     ax,ARGyc        ; AX := yc
                mov     bx,ARGxc        ; BX := xc
                call    PixelAddr10     ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left
                mov     ah,1
                shl     ah,cl           ; AH := bit mask for first pixel
                mov     LMask,ah
                mov     RMask,ah

                add     si,bx           ; SI := offset of (0,b)
                mov     ULAddr,si
                mov     URAddr,si
                sub     bx,di           ; AX := offset of (0,-b)
                mov     LLAddr,bx
                mov     LRAddr,bx

; initial decision variables

                xor     ax,ax
                mov     VARdx,ax
                mov     VARdx+2,ax      ; dx = 0

                mov     ax,TwoAsquared
                mov     dx,TwoAsquared+2
                mov     cx,ARGb
                call    LongMultiply    ; perform 32-bit by 16-bit multiply
                mov     VARdy,ax
                mov     VARdy+2,dx      ; dy = TwoAsquared * b

                mov     ax,Asquared
                mov     dx,Asquared+2   ; DX:AX = Asquared
                sar     dx,1
                rcr     ax,1
                sar     dx,1
                rcr     ax,1            ; DX:AX = Asquared/4

                add     ax,Bsquared
                adc     dx,Bsquared+2   ; DX:AX = Bsquared + Asquared/4
                mov     VARd,ax
                mov     VARd+2,dx

                mov     ax,Asquared
                mov     dx,Asquared+2
                mov     cx,ARGb
                call    LongMultiply    ; DX:AX = Asquared*b
                sub     VARd,ax
                sbb     VARd+2,dx       ; d = Bsquared - Asquared*b +
                                        ;      Asquared/4

; loop until dy/dx >= -1

                mov     bx,ARGb         ; BX := initial y-coordinate

                xor     cx,cx           ; CH := 0 (initial y-increment)
                                        ; CL := 0 (initial x-increment)
L10:            mov     ax,VARdx
                mov     dx,VARdx+2
                sub     ax,VARdy
                sbb     dx,VARdy+2
                jns     L20             ; jump if dx>=dy

                call    Set4Pixels

                mov     cx,1            ; CH := 0 (y-increment)
                                        ; CL := 1 (x-increment)
                cmp     VARd+2,0
                js      L11             ; jump if d < 0

                mov     ch,1            ; increment in y direction
                dec     bx              ; decrement current y-coordinate

                mov     ax,VARdy
                mov     dx,VARdy+2
                sub     ax,TwoAsquared
                sbb     dx,TwoAsquared+2 ; DX:AX := dy - TwoAsquared
                mov     VARdy,ax
                mov     VARdy+2,dx      ; dy -= TwoAsquared

                sub     VARd,ax
                sbb     VARd+2,dx       ; d -= dy

L11:            mov     ax,VARdx
                mov     dx,VARdx+2
                add     ax,TwoBsquared
                adc     dx,TwoBsquared+2 ; DX:AX := dx + TwoBsquared
                mov     VARdx,ax
                mov     VARdx+2,dx      ; dx += TwoBsquared

                add     ax,Bsquared
                adc     dx,Bsquared+2   ; DX:AX := dx + Bsquared
                add     VARd,ax
                adc     VARd+2,dx       ; d += dx + Bsquared

                jmp     L10
;
; plot pixels from current (x,y) until y < 0
;

; initial buffer address and bit mask

L20:            push    bx              ; preserve current y-coordinate
                push    cx              ; preserve x- and y-increments

                mov     ax,Asquared
                mov     dx,Asquared+2
                sub     ax,Bsquared
                sbb     dx,Bsquared+2   ; DX:AX := Asquared-Bsquared

                mov     bx,ax
                mov     cx,dx           ; CX:BX := (Asquared-Bsquared)

                sar     dx,1
                rcr     ax,1            ; DX:AX := (Asquared-Bsquared)/2
                add     ax,bx
                adc     dx,cx           ; DX:AX := 3*(Asquared-Bsquared)/2

                sub     ax,VARdx
                sbb     dx,VARdx+2
                sub     ax,VARdy
                sbb     dx,VARdy+2      ; DX:AX := 3*(Asquared-Bsquared)/2
                                        ;           - (dx+dy)

                sar     dx,1
                rcr     ax,1            ; DX:AX :=
                                        ;  ( 3*(Asquared-Bsquared)/2 -
                                        ;  (dx+dy) )/2
                add     VARd,ax
                adc     VARd+2,dx       ; update d

; loop until y < 0

                pop     cx              ; CH,CL := y- and x-increments
                pop     bx              ; BX := y

L21:            call    Set4Pixels

                mov     cx,100h         ; CH := 1 (y-increment)
                                        ; CL := 0 (x-increment)

                cmp     VARd+2,0
                jns     L22             ; jump if d >= 0

                mov     cl,1            ; increment in x direction

                mov     ax,VARdx
                mov     dx,VARdx+2
                add     ax,TwoBsquared
                adc     dx,TwoBsquared+2 ; DX:AX := dx + TwoBsquared
                mov     VARdx,ax
                mov     VARdx+2,dx      ; dx += TwoBsquared

                add     VARd,ax
                adc     VARd+2,dx       ; d += dx

L22:            mov     ax,VARdy
                mov     dx,VARdy+2
                sub     ax,TwoAsquared
                sbb     dx,TwoAsquared+2 ; DX:AX := dy - TwoAsquared
                mov     VARdy,ax
                mov     VARdy+2,dx      ; dy -= TwoAsquared

                sub     ax,Asquared
                sbb     dx,Asquared+2   ; DX:AX := dy - Asquared
                sub     VARd,ax
                sbb     VARd+2,dx       ; d += Asquared - dy

                dec     bx              ; decrement y
                jns     L21             ; loop if y >= 0

; restore default Graphics Controller registers

Lexit:          mov     ax,0FF08h       ; default Bit Mask
                mov     dx,3CEh
                out     dx,ax

                mov     ax,0003         ; default Function Select
                out     dx,ax

                mov     ax,0001         ; default Enable Set/Reset
                out     dx,ax

                pop     di
                pop     si
                mov     sp,bp
                pop     bp
                ret

_Ellipse10      ENDP


Set4Pixels      PROC    near            ; Call with:    CH := y-increment
                                        ;                      (0, -1)
                                        ;               CL := x-increment
                                        ;                      (0, 1)

                push    ax              ; preserve these regs
                push    bx
                push    dx

                mov     dx,3CEh         ; DX := Graphics Controller port

                xor     bx,bx           ; BX := 0
                test    ch,ch
                jz      L30             ; jump if y-increment = 0

                mov     bx,BytesPerLine ; BX := positive increment
                neg     bx              ; BX := negative increment

L30:            mov     al,8            ; AL := Bit Mask reg number

; pixels at (xc-x,yc+y) and (xc-x,yc-y)

                xor     si,si           ; SI := 0
                mov     ah,LMask

                rol     ah,cl           ; AH := bit mask rotated
                                        ;        horizontally
                rcl     si,1            ; SI := 1 if bit mask rotated
                                        ;        around
                neg     si              ; SI := 0 or -1

                mov     di,si           ; SI,DI := left horizontal
                                        ;           increment

                add     si,ULAddr       ; SI := upper left addr + horiz
                                        ;        incr
                add     si,bx           ; SI := new upper left addr
                add     di,LLAddr
                sub     di,bx           ; DI := new lower left addr

                mov     LMask,ah        ; update these variables
                mov     ULAddr,si
                mov     LLAddr,di

                out     dx,ax           ; update Bit Mask register

                mov     ch,es:[si]      ; update upper left pixel
                mov     es:[si],ch
                mov     ch,es:[di]      ; update lower left pixel
                mov     es:[di],ch


; pixels at (xc+x,yc+y) and (xc+x,yc-y)

                xor     si,si           ; SI := 0
                mov     ah,RMask

                ror     ah,cl           ; AH := bit mask rotated
                                        ;        horizontally
                rcl     si,1            ; SI := 1 if bit mask rotated
                                        ;        around

                mov     di,si           ; SI,DI := right horizontal
                                        ;           increment

                add     si,URAddr       ; SI := upper right addr + horiz
                                        ;        incr
                add     si,bx           ; SI := new upper right addr
                add     di,LRAddr
                sub     di,bx           ; DI := new lower right addr

                mov     RMask,ah        ; update these variables
                mov     URAddr,si
                mov     LRAddr,di

                out     dx,ax           ; update Bit Mask register

                mov     ch,es:[si]      ; update upper right pixel
                mov     es:[si],ch
                mov     ch,es:[di]      ; update lower right pixel
                mov     es:[di],ch

                pop     dx              ; restore these regs
                pop     bx
                pop     ax
                ret

Set4Pixels      ENDP


LongMultiply    PROC    near            ; Caller:   DX = u1 (hi-order word
                                        ;                 of 32-bit number)
                                        ;           AX = u2 (lo-order word)
                                        ;           CX = v1 (16-bit number)
                                        ; Returns:  DX:AX = 32-bit result)

                push    ax              ; preserve u2
                mov     ax,dx           ; AX := u1
                mul     cx              ; AX := hi-order word of result
                xchg    ax,cx           ; AX := v1, CX := hi-order word
                pop     dx              ; DX := u2
                mul     dx              ; AX := lo-order word of result
                                        ; DX := carry
                add     dx,cx           ; CX := hi-order word of result
                ret

LongMultiply    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 7-5.  Using pixel coordinate scaling to display a circle in
              640-by-350 16-color mode.
���������������������������������������������������������������������������

/* Listing 7-5 */

Circle10( xc, yc, xr, yr, n)         /* circles in 640x350 16-color mode */
int     xc,yc;          /* center of circle */
int     xr,yr;          /* point on circumference */
int     n;              /* pixel value */
{
        double  x,y;
        double  sqrt();
        double  Scale10 = 1.37;         /* pixel scaling factor */
        int     a,b;

        x = xr - xc;                    /* translate center of ellipse */
        y = (yr - yc) * Scale10;        /*  to origin */


        a = sqrt( x*x + y*y );          /* compute major and minor axes */
        b = a / Scale10;

        Ellipse10( xc, yc, a, b, n);    /* draw it */
}



���������������������������������������������������������������������������
Listing 8-1.  Filling a rectangle with horizontal lines.
���������������������������������������������������������������������������

/* Listing 8-1 */

FilledRectangle( x1, y1, x2, y2, n )
int     x1,y1;                  /* upper left corner */
int     x2,y2;                  /* lower right corner */
int     n;                      /* pixel value */
{
        int     y;

        for (y = y1; y< = y2; y++)      /* draw rectangle as a set of */
          Line( x1, y, x2, y, n );      /*  adjacent horizontal lines */
}



���������������������������������������������������������������������������
Listing 8-2.  A simple recursive region fill.
���������������������������������������������������������������������������

/* Listing 8-2 */

int     FillValue;      /* value of pixels in filled region */
int     BorderValue;    /* value of pixels in border */

PixelFill( x, y )
int     x,y;
{
        int     v;

        v = ReadPixel( x, y );
        if ( (v!=FillValue) && (v!=BorderValue) )
        {
          SetPixel( x, y, FillValue );

          PixelFill( x-1, y );
          PixelFill( x+1, y );
          PixelFill( x, y-1 );
          PixelFill( x, y+1 );
        }
}



���������������������������������������������������������������������������
Listing 8-3.  A line-adjacency fill routine.
���������������������������������������������������������������������������

/* Listing 8-3 */

#define UP      -1
#define DOWN    1


LineAdjFill( SeedX, SeedY, D, PrevXL, PrevXR )
int     SeedX,SeedY;           /* seed for current row of pixels */
int     D;                     /* direction searched to find current row */
int     PrevXL,PrevXR;         /* endpoints of previous row of pixels */
{
        int     x,y;
        int     xl,xr;
        int     v;

        y = SeedY;             /* initialize to seed coordinates */
        xl = SeedX;
        xr = SeedX;

        ScanLeft( &xl, &y );   /* determine endpoints of seed line */
                               /*  segment */
        ScanRight( &xr, &y );

        Line( xl, y, xr, y, FillValue );     /* fill line with FillValue */

/* find and fill adjacent line segments in same direction */

        for (x = xl; x <= xr; x++)    /* inspect adjacent rows of pixels */
        {
          v = ReadPixel( x, y+D );
          if ( (v != BorderValue) && (v != FillValue) )
            x = LineAdjFill( x, y+D, D, xl, xr );
        }

/* find and fill adjacent line segments in opposite direction */

        for (x = xl; x < PrevXL; x++)
        {
          v = ReadPixel( x, y-D );
          if ( (v != BorderValue) && (v != FillValue) )
            x = LineAdjFill( x, y-D, -D, xl, xr );
        }

        for (x = PrevXR; x < xr; x++)
        {
          v = ReadPixel( x, y-D );
          if ( (v != BorderValue) && (v != FillValue) )
            x = LineAdjFill( x, y-D, -D, xl, xr );
        }

        return( xr );
}

ScanLeft( x, y )
int     *x,*y;
{
        int     v;

        do
        {
          --(*x);                       /* move left one pixel */
          v = ReadPixel( *x, *y );      /* determine its value */
        }
        while ( (v != BorderValue) && (v != FillValue) );

        ++(*x);         /* x-coordinate of leftmost pixel in row */
}

ScanRight( x, y )
int     *x,*y;
{
        int     v;

        do
        {
          ++(*x);                       /* move right one pixel */
          v = ReadPixel( *x, *y );      /* determine its value */
        }
        while ( (v != BorderValue) && (v != FillValue) );

        --(*x);         /* x-coordinate of rightmost pixel in row */
}



���������������������������������������������������������������������������
Listing 8-4.  A region fill routine that traces a region's border.
���������������������������������������������������������������������������

/* Listing 8-4 */

#define BLOCKED         1
#define UNBLOCKED       2
#define TRUE            1
#define FALSE           0

struct  BPstruct                        /* table of border pixels */
{
        int     x,y;
        int     flag;
}
        BP[3000];                       /* (increase if necessary) */

int     BPstart;                        /* start of table */
int     BPend = 0;                      /* first empty cell in table */
int     FillValue;                      /* value of pixels in filled */
                                        /*  region */
int     BorderValue;                    /* value of pixels in border */


BorderFill( x, y )
int     x,y;
{
        do                              /* do until entire table is */
                                        /*  scanned */
        {
          TraceBorder( x, y );          /* trace border starting at x,y */
          SortBP( BP );                 /* sort the border pixel table */
          ScanRegion( &x, &y );         /* look for holes in the */
                                        /*  interior */
        }
        while (BPstart < BPend);

        FillRegion();                   /* use the table to fill the
                                         /* interior */
}

ScanRegion( x, y )
int     *x,*y;
{
        int     i = BPstart;
        int     xr;

        while (i < BPend)
        {
          if (BP[i].flag == BLOCKED)    /* skip pixel if blocked */
            ++i;

          else
          if (BP[i].y != BP[i+1].y)     /* skip pixel if last in line */
            ++i;

          else
          {                             /* if at least one pixel to */
                                        /*  fill ... */
            if (BP[i].x < BP[i+1].x-1)  /* scan the line */
            {
              xr = ScanRight( BP[i].x+1, BP[i].y );

              if (xr<BP[i+1].x)         /* if a border pixel is */
                                        /*  found ... */
              {
                *x = xr;                /* return its x,y coordinates */
                *y = BP[i].y;

                break;
              }
            }

            i += 2;                     /* advance past this pair of */
                                        /*  pixels */
          }
        }

        BPstart = i;
}

SortBP()                        /* uses Microsoft C library quicksort */
                                /*  routine */
{
        int     CompareBP();

        qsort( BP+BPstart, BPend-BPstart,
               sizeof(struct BPstruct), CompareBP );
}

CompareBP( arg1, arg2 )         /* returns -1 if arg1<arg2, 1 if */
                                /*  arg1>arg2 */
struct  BPstruct  *arg1,*arg2;
{
        int     i;

        i = arg1->y - arg2->y;          /* sort by y-coordinate */
        if (i != 0)
          return( (i < 0) ? -1 : 1 );   /* (return -1 if i<0, 1 if i>0) */

        i = arg1->x - arg2->x;          /* sort by x-coordinate */
        if (i != 0)
          return( (i < 0) ? -1 : 1 );

        i = arg1->flag - arg2->flag;    /* sort by flag */
          return( (i < 0) ? -1 : 1 );
}

FillRegion()
{
        int     i;


        for(i = 0; i < BPend;)
        {
          if (BP[i].flag == BLOCKED)    /* skip pixel if blocked */
            ++i;

          else
          if (BP[i].y != BP[i+1].y)     /* skip pixel if last in line */
            ++i;

          else
          {                             /* if at least one pixel to */
                                        /*  fill ... */
            if (BP[i].x < BP[i+1].x-1)  /* draw a line */
              Line( BP[i].x+1, BP[i].y, BP[i+1].x-1,
                    BP[i+1].y, FillValue );

            i += 2;
          }
        }
}

/* border tracing routine */

struct  BPstruct CurrentPixel;
int     D;                              /* current search direction */
int     PrevD;                          /* previous search direction */
int     PrevV;                          /* previous vertical direction */

TraceBorder( StartX, StartY )
int     StartX,StartY;
{
        int     NextFound;              /* flags */
        int     Done;

/* initialize */

        CurrentPixel.x = StartX;
        CurrentPixel.y = StartY;

        D = 6;                          /* current search direction */
        PrevD = 8;                      /* previous search direction */
        PrevV = 2;                      /* most recent vertical */
                                        /*  direction */

/* loop around the border until returned to the starting pixel */

        do
        {
          NextFound = FindNextPixel();
          Done =
           (CurrentPixel.x == StartX) && (CurrentPixel.y == StartY);
        }
        while (NextFound && !Done);

/* if only one pixel in border, add it twice to the table */

        if (!NextFound)                        /* pixel has no neighbors */
        {
          AppendBPList( StartX, StartY, UNBLOCKED );
          AppendBPList( StartX, StartY, UNBLOCKED );
        }

/* if last search direction was upward, add the starting pixel to the */
/*  table */

        else
        if ( (PrevD <= 3) && (PrevD >= 1) )
          AppendBPList( StartX, StartY, UNBLOCKED );
}

FindNextPixel()
{
        int     i;
        int     flag;


        for (i = -1; i <= 5; i++)
        {
          flag = FindBP( (D+i) & 7 );   /* search for next border pixel */

          if (flag)                     /* flag is TRUE if found */
          {
            D = (D+i) & 6;              /* (D+i) MOD 2 */
            break;                      /* exit from loop */
          }
        }

        return( flag );
}

FindBP( d )
int     d;                      /* direction to search for next border */
                                /*  pixel */
{
        int     x,y;

        x = CurrentPixel.x;
        y = CurrentPixel.y;

        NextXY( &x, &y, d );            /* get x,y of pixel in direction */
                                        /*  d */

        if ( BorderValue == ReadPixel( x, y ) )
        {
          AddBPList( d );               /* add pixel at x,y to table */
          CurrentPixel.x = x;           /* pixel at x,y becomes current */
                                        /*  pixel */
          CurrentPixel.y = y;
          return( TRUE );
        }

        else
          return( FALSE );
}

NextXY( x, y, Direction )
int     *x,*y;
int     Direction;
{
        switch( Direction )             /*   3 2 1   */
        {                               /*   4   0   */
          case 1:                       /*   5 6 7   */
          case 2:
          case 3:
                *y -= 1;                /* up */
                break;
          case 5:
          case 6:
          case 7:
                *y += 1;                /* down */
                break;
        }

        switch(Direction)
        {
          case 3:
          case 4:
          case 5:
                *x -= 1;                /* left */
                break;
          case 1:
          case 0:
          case 7:
                *x += 1;                /* right */
                break;
        }
}

AddBPList( d )
int     d;
{
        if (d == PrevD)
          SameDirection();

        else
        {
          DifferentDirection( d );
          PrevV = PrevD;                /* new previous vertical */
                                        /*  direction */
        }

        PrevD = d;                      /* new previous search direction */
}

SameDirection()
{
        if (PrevD == 0)                 /* moving right ... */
          BP[BPend-1].flag = BLOCKED;   /* block previous pixel */

        else
        if (PrevD != 4)                 /* if not moving horizontally */
          AppendBPList( CurrentPixel.x, CurrentPixel.y, UNBLOCKED );
}

DifferentDirection( d )
int     d;
{

/* previously moving left */

        if (PrevD == 4)
        {
          if (PrevV == 5)               /* if from above ... */
            BP[BPend-1].flag = BLOCKED; /* block rightmost in line */

          AppendBPList( CurrentPixel.x, CurrentPixel.y, BLOCKED );
        }

/* previously moving right */

        else
        if (PrevD == 0)                 /* previously moving right ... */
        {
          BP[BPend-1].flag = BLOCKED;   /* block rightmost in line */

          if (d == 7)                   /* if line started from above */
            AppendBPList( CurrentPixel.x, CurrentPixel.y, BLOCKED );
          else
            AppendBPList( CurrentPixel.x, CurrentPixel.y, UNBLOCKED );
        }

/* previously moving in some vertical direction */

        else
        {
          AppendBPList( CurrentPixel.x, CurrentPixel.y, UNBLOCKED );

/* add pixel twice if local vertical maximum or minimum */

          if ( ( (d>=1) && (d<=3) ) && ( (PrevD >= 5) && (PrevD <= 7) ) ||
               ( (d>=5) && (d<=7) ) && ( (PrevD >= 1) && (PrevD <= 3) ) )
            AppendBPList( CurrentPixel.x, CurrentPixel.y, UNBLOCKED );
        }
}

AppendBPList( p, q, f )
int     p,q;                    /* pixel x,y coordinates */
int     f;                      /* flag */
{
        BP[BPend].x = p;
        BP[BPend].y = q;
        BP[BPend].flag = f;

        ++BPend;                /* increment past last entry in table */
}

/* routine to scan a line for a border pixel */

int     Xmax;                   /* largest valid pixel x-coordinate */

ScanRight( x, y )
int     x,y;
{
        while ( ReadPixel( x, y ) != BorderValue )
        {
          ++x;                          /* increment x */
          if (x == Xmax)                /* if end of line in buffer ... */
            break;                      /* exit from the loop */
        }

        return( x );
}



���������������������������������������������������������������������������
Listing 8-5.  An assembly-language version of ScanRight().
���������������������������������������������������������������������������

                TITLE   'Listing 8-5'
                NAME    ScanRight10
                PAGE    55,132

;
; Name:         ScanRight10
;
; Function:     Scan for a pixel of a given value in 16-color EGA/VGA
;                graphics
;
; Caller:       Microsoft C:
;
;                       int ScanRight10(x,y);
;
;                       int x,y;                      /* starting pixel */
;
;                       extern  int BorderValue;      /* value of border */
                                                      /*  pixel */
;
;               Returns the x-coordinate of the rightmost border pixel.
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]

ByteOffsetShift EQU     3               ; used to convert pixels to byte
                                        ;  offset
BytesPerLine    EQU     80              ; 80 for most 16-color graphics
                                        ;  modes (40 for 320x200 16-color)


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr10:near

                PUBLIC  _ScanRight10
_ScanRight10    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    di
                push    si

; calculate pixel address of (0,y)

                mov     ax,ARGy         ; AX := y
                xor     bx,bx           ; BX := 0
                call    PixelAddr10     ; ES:BX -> buffer
                mov     di,bx           ; ES:DI -> buffer

; calculate offset of x in row

                mov     ax,ARGx
                mov     si,ax           ; SI,AX := x
                mov     cl,ByteOffsetShift
                shr     si,cl           ; SI := offset of x in row y
                add     di,si           ; DI := offset of x in buffer

; calculate a bit mask for the first byte to scan

                mov     cl,al
                and     cl,7            ; CL := x & 7
                mov     ch,0FFh
                shr     ch,cl           ; CH := bit mask for first scanned
                                        ;        byte

; configure the Graphics Controller

                mov     dx,3CEh         ; DX := Graphics Controller port
                                        ;        addr

                mov     ah,_BorderValue ; AH := pixel value for Color
                                        ;        Compare reg
                mov     al,2            ; AL := Color Compare Reg number
                out     dx,ax

                mov     ax,805h         ; AH := 00001000b (Read Mode 1)
                out     dx,ax           ; AL := Mode reg number

                mov     ax,0F07h        ; AH := 00001111b (Color Compare
                                        ;        reg value)
                out     dx,ax           ; AL := Color Compare reg number

; inspect the first byte for border pixels

                mov     al,es:[di]      ; AL := nonzero bits corresponding
                                        ;        to border pixels
                inc     di              ; ES:DI -> next byte to scan
                and     al,ch           ; apply bit mask
                jnz     L01             ; jump if border pixel(s) found

; scan remainder of line for border pixels

                mov     cx,BytesPerLine
                sub     cx,si           ; CX := BytesPerLine - (byte offset
                                        ;        of starting pixel)
                dec     cx              ; CX := # of bytes to scan

                repe    scasb           ; scan until nonzero byte read;
                                        ;  i.e., border pixel(s) found

; compute x value of border pixel

                mov     al,es:[di-1]    ; AL := last byte compared

L01:            sub     di,bx           ; DI := offset of byte past the one
                                        ;        which contains a border
                                        ;        pixel
                mov     cl,ByteOffsetShift
                shl     di,cl           ; DI := x-coordinate of 1st pixel
                                        ;        in byte

                mov     cx,8            ; CX := loop limit

L02:            shl     al,1            ; isolate first border pixel
                jc      L03

                loop    L02

L03:            sub     di,cx           ; DI := x-coordinate of border
                                        ;        pixel

; restore default Graphics Controller state and return to caller

                mov     ax,2            ; AH := 0 (default Color Compare
                                        ;        value)
                out     dx,ax           ; restore Color Compare reg

                mov     al,5            ; AH := 0, AL := 5
                out     dx,ax           ; restore Mode reg

                mov     ax,di           ; AX := return value

                pop     si              ; restore caller registers and
                                        ;  return
                pop     di
                mov     sp,bp

                pop     bp
                ret

_ScanRight10    ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

                EXTRN   _BorderValue:byte

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 9-1.  A routine that rotates an 8-by-8 character definition by
              90 degrees.
���������������������������������������������������������������������������

        mov     si,seg OldCharDef
        mov     ds,si
        mov     si,offset OldCharDef    ; DS:SI -> old character definition

        mov     di,seg NewCharDef
        mov     es,di
        mov     di,offset NewCharDef    ; ES:DI -> new character definition

        mov     bx,1            ; BH := 0
                                ; BL := bit mask

L01:    push    si              ; preserve SI
        mov     cx,8            ; CX := number of bits in each byte

L02:    lodsb                   ; AL := next byte in old
                                ;  character definition
        and     al,bl           ; mask one bit
        cmp     bh,al           ; set carry flag if mask bit
                                ;  is nonzero

        rcl     ah,1            ; rotate bit into AH
        loop    L02             ; loop across old character definition

        mov     al,ah
        stosb                   ; store next byte in new
                                ;  character definition
        pop     si              ; DS:SI -> start of old
                                ;  character definition
        shl     bl,1            ; BL := new bit mask
        jnz     L01             ; loop until bit mask is
                                ;   shifted out of BL



���������������������������������������������������������������������������
Listing 9-2.  A software character generator for 640-by-200 2-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 9-2'
                NAME    DisplayChar06
                PAGE    55,132

;
; Name:         DisplayChar06
;
; Function:     Display a character in 640x200 2-color mode
;
; Caller:       Microsoft C:
;
;                       void DisplayChar06(c,x,y,fgd,bkgd);
;
;                       int c;                  /* character code */
;
;                       int x,y;                /* upper left pixel */
;
;                       int fgd,bkgd;           /* foreground and */
;                                               /*  background pixel */
;                                               /*  values */
;

ARGc            EQU     word ptr [bp+4] ; stack frame addressing
ARGx            EQU     word ptr [bp+6]
ARGy            EQU     word ptr [bp+8]
ARGfgd          EQU     byte ptr [bp+10]
ARGbkgd         EQU     byte ptr [bp+12]

VARmask         EQU              [bp-8]
VARtoggle       EQU              [bp-10]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr06:near

                PUBLIC  _DisplayChar06
_DisplayChar06  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,4            ; stack space for local variables
                push    di
                push    si
                push    ds

; set up foreground pixel toggle mask

                mov     ah,ARGfgd       ; AH := 0 or 1 (foreground pixel
                                        ;        value)
                ror     ah,1            ; high-order bit of AH := 0 or 1
                cwd                     ; propagate high-order bit
                                        ;  through DX
                not     dx              ; DX :=     0 if foreground = 1
                                        ;    or FFFFh if foreground = 0
                mov     VARtoggle,dx

; calculate first pixel address

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr06     ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                xor     cl,7            ; CL := # bits to rotate right

                mov     ax,0FF00h
                ror     ax,cl           ; AX := bit mask in proper position
                mov     VARmask,ax

; set up video buffer addressing

                mov     dx,2000h        ; increment for video buffer
                                        ;  interleave
                mov     di,80-2000h     ; increment from last to first
                                        ;  interleave

                test    bx,2000h        ; set zero flag if BX in 1st
                                        ;  interleave
                jz      L01

                xchg    di,dx           ; exchange increment values if 1st
                                        ;  pixel lies in 1st interleave

; set up character definition table addressing

L01:            push    bx              ; preserve buffer address

                mov     ax,40h
                mov     ds,ax           ; DS := segment of BIOS Video
                                        ;        Display Data area
                mov     ch,ds:[85h]     ; CH := POINTS (pixel rows in
                                        ;        character)

                xor     ax,ax
                mov     ds,ax           ; DS := absolute zero

                mov     ax,ARGc         ; AL := character code
                cmp     al,80h
                jae     L02

                mov     bx,43h*4        ; DS:BX -> int 43h vector if char
                                        ;  < 80h
                jmp     short L03

L02:            mov     bx,1Fh*4        ; DS:BX -> int 1Fh vector if char
                                        ;  >= 80h
                sub     al,80h          ; put character code in range of
                                        ;  table

L03:            lds     si,ds:[bx]      ; DS:SI -> start of character table
                mul     ch              ; AX := offset into char def table
                                        ;  (POINTS * char code)
                add     si,ax           ; SI := addr of char def

                pop     bx              ; restore buffer address

                test    cl,cl           ; test # bits to rotate
                jnz     L20             ; jump if character is not byte-
                                        ;  aligned


; routine for byte-aligned characters

                mov     ah,VARtoggle    ; AH := foreground toggle mask
                xchg    ch,cl           ; CX := POINTS

L10:            lodsb                   ; AL := bit pattern for next
                                        ;        pixel row
                xor     al,ah           ; toggle pixels if foreground = 0
                mov     es:[bx],al      ; store pixels in buffer

                add     bx,dx           ; BX := next row in buffer
                xchg    di,dx           ; swap buffer increments
                loop    L10
                jmp     short Lexit


; routine for non-byte-aligned characters

L20:            mov     ax,VARmask
                and     es:[bx],ax      ; mask character pixels in buffer

                xor     ah,ah
                lodsb                   ; AX := bit pattern for next
                                        ;        pixel row
                xor     al,VARtoggle    ; toggle pixels if foreground = 0

                ror     ax,cl           ; rotate pixels into position
                or      es:[bx],ax      ; store pixels in buffer

                add     bx,dx           ; BX := next row in buffer
                xchg    di,dx           ; swap buffer increments
                dec     ch
                jnz     L20

Lexit:          pop     ds              ; restore caller registers and
                                        ;  return
                pop     si
                pop     di
                mov     sp,bp
                pop     bp
                ret

_DisplayChar06  ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 9-3.  A software character generator for 320-by-200 4-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 9-3'
                NAME    DisplayChar04
                PAGE    55,132

;
; Name:         DisplayChar04
;
; Function:     Display a character in 320x200 4-color graphics mode
;
; Caller:       Microsoft C:
;
;                       void DisplayChar04(c,x,y,fgd,bkgd);
;
;                       int c;                  /* character code */
;
;                       int x,y;                /* upper left pixel */
;
;                       int fgd,bkgd;           /* foreground and */
;                                               /*  background pixel */
;                                               /*  values */
;

ARGc            EQU     word ptr [bp+4] ; stack frame addressing
ARGx            EQU     word ptr [bp+6]
ARGy            EQU     word ptr [bp+8]
ARGfgd          EQU              [bp+10]
ARGbkgd         EQU              [bp+12]

VARshift        EQU     word ptr [bp-8]
VARincr         EQU     word ptr [bp-10]


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr04:near

                PUBLIC  _DisplayChar04
_DisplayChar04  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,4            ; stack space for local variables
                push    di
                push    si
                push    ds

; propagate pixel values

                mov     bx,offset DGROUP:PropagatedPixel
                mov     al,ARGfgd
                xlat                    ; propagate foreground pixel value
                mov     ah,al
                mov     ARGfgd,ax
                mov     al,ARGbkgd
                xlat                    ; propagate background pixel value
                mov     ah,al
                mov     ARGbkgd,ax

; calculate first pixel address

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr04     ; ES:BX -> buffer
                                        ; CL := # bits to shift left
                                        ;        to mask pixel
                mov     ch,0FCh
                shl     ch,cl           ; CH := bit mask for right side
                                        ;        of char

                xor     cl,6            ; CL := 6 - CL (# bits to rotate
                                        ;        char into position)
                mov     VARshift,cx

; set up video buffer addressing

                mov     di,2000h        ; increment for video buffer
                                        ;  interleave
                mov     VARincr,80-2000h ; increment from last to first
                                         ;  interleave

                test    bx,2000h        ; set zero flag if BX in 1st
                                        ;  interleave
                jz      L01
                xchg    VARincr,di      ; exchange increment values if 1st
                                        ;  pixel lies in 1st interleave

; set up character definition table addressing

L01:            push    bx              ; preserve buffer address

                mov     ax,40h
                mov     ds,ax           ; DS := segment of BIOS Video
                                        ;        Display Data area
                mov     ch,ds:[85h]     ; CH := POINTS (pixel rows in
                                        ;        character)

                xor     ax,ax
                mov     ds,ax           ; DS := absolute zero

                mov     ax,ARGc         ; AL := character code
                cmp     al,80h
                jae     L02

                mov     bx,43h*4        ; DS:BX -> int 43h vector if char
                                        ;  < 80h
                jmp     short L03

L02:            mov     bx,1Fh*4        ; DS:BX -> int 1Fh vector if char
                                        ;  >= 80h
                sub     al,80h          ; put character code in range of
                                        ;  table

L03:            lds     si,ds:[bx]      ; DS:SI -> start of character table
                mul     ch              ; AX := offset into char def table
                                        ;        (POINTS * char code)
                add     si,ax           ; SI := addr of char def

                pop     bx              ; restore buffer address

                xchg    ch,cl           ; CH := # bits to rotate
                                        ; CL := POINTS

                test    ch,ch           ; test # bits to rotate
                jnz     L20             ; jump if character is not byte-
                                        ;  aligned


; routine for byte-aligned characters

L10:            lodsb                   ; AL := bit pattern for next pixel
                                        ;        row
                xor     dx,dx           ; DX := initial value for doubled
                                        ;        bits
                mov     ah,8            ; AH := # of bits in pattern

L11:            shr     al,1            ; cf := lo-order bit of AL
                rcr     dx,1            ; hi-order bit of CX := cf
                sar     dx,1            ; double hi-order bit of DX
                dec     ah              ; loop 8 times
                jnz     L11

                mov     ax,dx           ; AX,DX := doubled bit pattern
                and     ax,ARGfgd       ; AX := foreground pixels
                not     dx
                and     dx,ARGbkgd      ; DX := background pixels

                or      ax,dx           ; AX := eight pixels
                xchg    ah,al           ; put bytes in proper order
                mov     es:[bx],ax      ; update video buffer

                add     bx,di           ; BX := next row in buffer
                xchg    di,VARincr      ; swap buffer increments

                loop    L10
                jmp     short Lexit


; routine for non-byte-aligned characters

L20:            xor     ch,ch           ; CX := POINTS

L21:            push    cx              ; preserve CX

                mov     cx,VARshift     ; CH := mask for right side of char
                                        ; CL := # bits to rotate

                lodsb                   ; AL := bit pattern for next pixel
                                        ;        row
                xor     dx,dx           ; DX := initial value for doubled
                                        ;        bits
                mov     ah,8            ; AH := # of bits in pattern

L22:            shr     al,1            ; DX := double bits in AL
                rcr     dx,1            ;  (same as above)

                sar     dx,1
                dec     ah
                jnz     L22

                xchg    dh,dl           ; DH := bits for right half of char
                                        ; DL := bits for left half of char
                mov     ax,dx
                and     ax,ARGfgd       ; AX := foreground pixels
                not     dx
                and     dx,ARGbkgd      ; DX := background pixels

                or      dx,ax           ; DX := eight pixels
                ror     dx,cl           ; DH := left and right side pixels
                                        ; DL := middle pixels
                mov     al,ch
                xor     ah,ah           ; AX := mask for left and middle
                                        ;        bytes of char
                and     es:[bx],ax      ; zero pixels in video buffer

                not     ax
                and     ax,dx
                or      es:[bx],ax      ; update pixels in left and middle
                                        ;  bytes

                mov     al,ch           ; AL := mask for right-hand byte
                not     al
                and     es:[bx+2],al    ; mask pixels in right-hand byte in
                                        ;  buffer
                and     ch,dl
                or      es:[bx+2],ch    ; update pixels in right-hand byte

                add     bx,di           ; BX := next row in buffer
                xchg    di,VARincr      ; swap buffer increments

                pop     cx              ; restore CX
                loop    L21

Lexit:          pop     ds              ; restore caller registers and
                                        ;  return
                pop     si
                pop     di
                mov     sp,bp
                pop     bp
                ret

_DisplayChar04  ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

PropagatedPixel DB      00000000b       ; 0
                DB      01010101b       ; 1
                DB      10101010b       ; 2
                DB      11111111b       ; 3

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 9-4.  A software character generator for Hercules monochrome
              graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 9-4'
                NAME    DisplayCharHGC
                PAGE    55,132

;
; Name:         DisplayCharHGC
;
; Function:     Display a character in Hercules 720x348 monochrome
;                graphics mode
;
; Caller:       Microsoft C:
;
;                       void DisplayCharHGC(c,x,y,fgd,bkgd);
;
;                       int c;                  /* character code */
;
;                       int x,y;                /* upper left pixel */
;
;                       int fgd,bkgd;           /* foreground and */
;                                               /*  background pixel */
;                                               /*  values */
;

ARGc            EQU     word ptr [bp+4] ; stack frame addressing
ARGx            EQU     word ptr [bp+6]
ARGy            EQU     word ptr [bp+8]
ARGfgd          EQU     byte ptr [bp+10]
ARGbkgd         EQU     byte ptr [bp+12]

VARmask         EQU              [bp-8]
VARtoggle       EQU              [bp-10]
VAR9bits        EQU     byte ptr [bp-12]


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddrHGC:near

                PUBLIC  _DisplayCharHGC
_DisplayCharHGC PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,6            ; stack space for local variables
                push    di
                push    si
                push    ds

; calculate first pixel address

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddrHGC    ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                xor     cl,7            ; CL := # bits to rotate right

; set up 8- or 9-bit mask

                mov     ax,40h
                mov     ds,ax           ; DS := segment of BIOS Video
                                        ;        Display Data area

                mov     ax,0FF00h       ; AX := 8-bit mask
                mov     VAR9bits,0      ; zero this flag

                cmp     byte ptr ds:[4Ah],90    ; does CRT_COLS = 90?
                je      L01             ; jump if characters are 8
                                        ;  pixels wide

                mov     ah,7Fh          ; AX := 9-bit mask
                cmp     ARGc,0C0h
                jb      L01             ; jump if character code ...

                cmp     ARGc,0DFh
                ja      L01             ; ... outside of range 0C0-0DFh

                inc     VAR9bits        ; set flag to extend to 9 bits

L01:            ror     ax,cl           ; AX := bit mask in proper position
                mov     VARmask,ax

; set up foreground pixel toggle mask

                mov     ah,ARGfgd       ; AH := 0 or 1 (foreground pixel
                                        ;        value)
                ror     ah,1            ; high-order bit of AH := 0 or 1

                cwd                     ; propagate high-order bit
                                        ;  through DX
                not     dx              ; DX :=     0 if foreground = 1
                                        ;    or FFFFh if foreground = 0
                mov     ax,VARmask
                not     ax
                and     dx,ax           ; zero unused bits of toggle mask
                                        ;  in DX
                mov     VARtoggle,dx

; set up character definition table addressing

                push    bx              ; preserve buffer address

                mov     ch,ds:[85h]     ; CH := POINTS (pixel rows in
                                        ;        character)

                xor     ax,ax
                mov     ds,ax           ; DS := absolute zero

                mov     ax,ARGc         ; AL := character code
                cmp     al,80h
                jae     L02

                mov     bx,43h*4        ; DS:BX -> int 43h vector if char
                                        ;  < 80h
                jmp     short L03

L02:            mov     bx,1Fh*4        ; DS:BX -> int 1Fh vector if char
                                        ;  >= 80h
                sub     al,80h          ; put character code in range of
                                        ;  table

L03:            lds     si,ds:[bx]      ; DS:SI -> start of character table
                mul     ch              ; AX := offset into char def table
                                        ;        (POINTS * char code)
                add     si,ax           ; SI := addr of char def

                pop     bx              ; restore buffer address

; mask and set pixels in the video buffer

L20:            mov     ax,VARmask
                and     es:[bx],ax      ; mask character pixels in buffer

                xor     ah,ah
                lodsb                   ; AX := bit pattern for next pixel
                                        ;        row
                cmp     VAR9bits,0
                je      L21             ; jump if character is 8 pixels
                                        ;  wide

                ror     ax,1            ; copy lo-order bit of AX into ...
                rcl     al,1            ;  hi-order bit

L21:            ror     ax,cl           ; rotate pixels into position
                xor     ax,VARtoggle    ; toggle pixels if foreground = 0
                or      es:[bx],ax      ; store pixels in buffer
                add     bx,2000h        ; increment to next portion of
                                        ;  interleave
                jns     L22
                add     bx,90-8000h     ; increment to first portion of
                                        ;  interleave

L22:            dec     ch
                jnz     L20

Lexit:          pop     ds              ; restore caller registers and
                                        ;  return
                pop     si
                pop     di
                mov     sp,bp
                pop     bp
                ret

_DisplayCharHGC ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 9-5.  A character generator for MCGA and VGA 320-by-200
              256-color mode.
���������������������������������������������������������������������������

                TITLE   'Listing 9-5'
                NAME    DisplayChar13
                PAGE    55,132

;
; Name:         DisplayChar13
;
; Function:     Display a character in MCGA/VGA 320x200 256-color mode
;
; Caller:       Microsoft C:
;
;                       void DisplayChar13(c,x,y,fgd,bkgd);
;
;                       int c;                  /* character code */
;

;                       int x,y;                /* upper left pixel */
;
;                       int fgd,bkgd;           /* foreground and */
;                                               /*  background pixel */
;                                               /*  values */
;

ARGc            EQU     word ptr [bp+4] ; stack frame addressing
ARGx            EQU     word ptr [bp+6]
ARGy            EQU     word ptr [bp+8]
ARGfgd          EQU     byte ptr [bp+10]
ARGbkgd         EQU     byte ptr [bp+12]

BytesPerLine    EQU     320

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr13:near

                PUBLIC  _DisplayChar13
_DisplayChar13  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    di
                push    si
                push    ds

; calculate first pixel address

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr13     ; ES:BX -> buffer
                mov     di,bx           ; ES:DI -> buffer

; set up character definition table addressing

                mov     ax,40h
                mov     ds,ax           ; DS := segment of BIOS Video
                                        ;        Display Data area
                mov     cx,ds:[85h]     ; CX := POINTS (pixel rows in
                                        ;        character)

                xor     ax,ax
                mov     ds,ax           ; DS := absolute zero

                mov     ax,ARGc         ; AL := character code
                mov     bx,43h*4        ; DS:BX -> int 43h vector if
                                        ;  char < 80h
                lds     si,ds:[bx]      ; DS:SI -> start of character table
                mul     cl              ; AX := offset into char def table
                                        ;        (POINTS * char code)
                add     si,ax           ; SI := addr of char def

; store the character in the video buffer

                mov     bl,ARGfgd       ; BL := foreground pixel value
                mov     bh,ARGbkgd      ; BH := background pixel value

L10:            push    cx              ; preserve CX across loop
                mov     cx,8            ; CX := character width in pixels
                lodsb
                mov     ah,al           ; AH := bit pattern for next pixel
                                        ;        row

L11:            mov     al,bl           ; AL := foreground pixel value
                shl     ah,1            ; carry flag := high-order bit
                jc      L12             ; jump if bit pattern specifies a
                                        ;  foreground pixel (bit = 1)
                mov     al,bh           ; AL := background pixel value

L12:            stosb                   ; update one pixel in the buffer
                loop    L11

                add     di,BytesPerLine-8 ; increment buffer address to
                                          ;  next row of pixels
                pop     cx
                loop    L10             ; loop down character

                pop     ds              ; restore caller registers and
                                        ;  return
                pop     si
                pop     di
                mov     sp,bp
                pop     bp
                ret

_DisplayChar13  ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 9-6.  A software character generator for native EGA and VGA
              graphics modes.
���������������������������������������������������������������������������

                TITLE   'Listing 9-6'
                NAME    DisplayChar10
                PAGE    55,132

;
; Name:         DisplayChar10
;

; Function:     Display a character in native EGA and VGA graphics modes
;
; Caller:       Microsoft C:
;
;                       void DisplayChar10(c,x,y,fgd,bkgd);
;
;                       int c;                  /* character code */
;
;                       int x,y;                /* upper left pixel */
;
;                       int fgd,bkgd;           /* foreground and */
;                                               /*  background pixel */
;                                               /*  values */
;

ARGc            EQU     word ptr [bp+4] ; stack frame addressing
ARGx            EQU     word ptr [bp+6]
ARGy            EQU     word ptr [bp+8]
ARGfgd          EQU     byte ptr [bp+10]
ARGbkgd         EQU     byte ptr [bp+12]

VARshift        EQU              [bp-8]

BytesPerLine    =       80              ; (or 40 in 320x200 16-color mode)
RMWbits         =       18h             ; Read-Modify-Write bits

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _DisplayChar10
_DisplayChar10  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,2            ; stack space for local variable
                push    di
                push    si
                push    ds

; calculate first pixel address

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr10     ; ES:BX -> buffer
                                        ; CL := # bits to shift left to
                                        ;        mask pixel
                inc     cx
                and     cl,7            ; CL := # bits to shift to mask
                                        ;        char

                mov     ch,0FFh
                shl     ch,cl           ; CH := bit mask for right side of
                                        ;        char
                mov     VARshift,cx

                push    es              ; preserve video buffer segment
                mov     si,bx           ; SI := video buffer offset

; set up character definition table addressing

                mov     ax,40h
                mov     ds,ax           ; DS := segment of BIOS Video
                                        ;        Display Data area
                mov     cx,ds:[85h]     ; CX := POINTS (pixel rows in
                                        ;        character)

                xor     ax,ax
                mov     ds,ax           ; DS := absolute zero

                mov     ax,ARGc         ; AL := character code
                mov     bx,43h*4        ; DS:BX -> int 43h vector
                les     di,ds:[bx]      ; ES:DI -> start of character table
                mul     cl              ; AX := offset into char def table
                                        ;        (POINTS * char code)
                add     di,ax           ; DI := addr of char def

                pop     ds              ; DS:SI -> video buffer

; set up Graphics Controller registers

                mov     dx,3CEh         ; Graphics Controller address
                                        ;  reg port

                mov     ax,0A05h        ; AL :=  Mode register number
                                        ; AH :=  Write Mode 2 (bits 0-1)
                                        ;         Read Mode 1 (bit 4)
                out     dx,ax

                mov     ah,RMWbits      ; AH := Read-Modify-Write bits
                mov     al,3            ; AL := Data Rotate/Function
                                        ;        Select reg
                out     dx,ax

                mov     ax,0007         ; AH := Color Don't Care bits
                                        ; AL := Color Don't Care reg number
                out     dx,ax           ; "don't care" for all bit planes

; select output routine depending on whether character is byte-aligned

                mov     bl,ARGfgd       ; BL := foreground pixel value
                mov     bh,ARGbkgd      ; BH := background pixel value

                cmp     byte ptr VARshift,0   ; test # bits to shift
                jne     L20             ; jump if character is not byte-
                                        ;  aligned


; routine for byte-aligned characters

                mov     al,8            ; AL := Bit Mask register number

L10:            mov     ah,es:[di]      ; AH := pattern for next row of
                                        ;        pixels
                out     dx,ax           ; update Bit Mask register
                and     [si],bl         ; update foreground pixels

                not     ah
                out     dx,ax
                and     [si],bh         ; update background pixels

                inc     di              ; ES:DI -> next byte in char def
                                        ;  table
                add     si,BytesPerLine ; increment to next line in video
                                        ;  buffer
                loop    L10
                jmp     short Lexit


; routine for non-byte-aligned characters

L20:            push    cx              ; preserve loop counter
                mov     cx,VARshift     ; CH := mask for left side of
                                        ;        character
                                        ; CL := # bits to shift left
; left side of character

                mov     al,es:[di]      ; AL := bits for next row of pixels
                xor     ah,ah
                shl     ax,cl           ; AH := bits for left side of char

                                        ; AL := bits for right side of char
                push    ax              ; save bits for right side on stack
                mov     al,8            ; AL := Bit Mask Register number
                out     dx,ax           ; set bit mask for foreground
                                        ;  pixels

                and     [si],bl         ; update foreground pixels

                not     ch              ; CH := mask for left side of char
                xor     ah,ch           ; AH := bits for background pixels
                out     dx,ax           ; set bit mask

                and     [si],bh         ; update background pixels

; right side of character

                pop     ax
                mov     ah,al           ; AH := bits for right side of char
                mov     al,8
                out     dx,ax           ; set bit mask

                inc     si              ; DS:SI -> right side of char in
                                        ;  buffer

                and     [si],bl         ; update foreground pixels

                not     ch              ; CH := mask for right side of char
                xor     ah,ch           ; AH := bits for background pixels
                out     dx,ax           ; set bit mask

                and     [si],bh         ; update background pixels

; increment to next row of pixels in character

                inc     di              ; ES:DI -> next byte in char def
                                        ;  table
                dec     si
                add     si,BytesPerLine ; DS:SI -> next line in video
                                        ;  buffer

                pop     cx
                loop    L20

; restore default Graphics Controller registers

Lexit:          mov     ax,0FF08h       ; default Bit Mask
                out     dx,ax

                mov     ax,0005         ; default Mode register
                out     dx,ax

                mov     ax,0003         ; default Data Rotate/Function
                                        ;  Select
                out     dx,ax

                mov     ax,0F07h        ; default Color Don't Care
                out     dx,ax

                pop     ds              ; restore caller registers and
                                        ;  return
                pop     si
                pop     di
                mov     sp,bp
                pop     bp
                ret

_DisplayChar10  ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 9-7.  A software character generator for Hercules InColor
              graphics modes.
���������������������������������������������������������������������������

                TITLE   'Listing 9-7'
                NAME    DisplayCharInC
                PAGE    55,132

;
; Name:         DisplayCharInC
;
; Function:     Display a character in InColor 720x348 16-color mode
;
; Caller:       Microsoft C:
;
;                       void DisplayCharInC(c,x,y,fgd,bkgd);
;

;                       int c;                  /* character code */
;
;                       int x,y;                /* upper left pixel */
;
;                       int fgd,bkgd;           /* foreground and */
;                                               /*  background pixel */
;                                               /*  values */
;

ARGc            EQU     word ptr [bp+4] ; stack frame addressing
ARGx            EQU     word ptr [bp+6]
ARGy            EQU     word ptr [bp+8]
ARGfgd          EQU     byte ptr [bp+10]
ARGbkgd         EQU     byte ptr [bp+12]

VARmask         EQU     word ptr [bp-8]
VAR9bits        EQU     byte ptr [bp-10]

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddrHGC:near

                PUBLIC  _DisplayCharInC
_DisplayCharInC PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,4            ; stack space for local variables
                push    di
                push    si
                push    ds

; calculate first pixel address

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddrHGC    ; ES:BX -> buffer
                                        ; CL := # bits to shift left to
                                        ;        mask pixel
                xor     cl,7            ; CL := # bits to rotate right

                push    es              ; preserve video buffer segment
                mov     si,bx           ; DI := video buffer offset

; set up flag for 8-bit or 9-bit characters

                mov     ax,40h
                mov     ds,ax           ; DS := segment of BIOS Video
                                        ;        Display Data area

                mov     ax,0FF00h       ; AX := 8-bit mask
                mov     VAR9bits,0      ; zero this flag

                cmp     byte ptr ds:[4Ah],90    ; does CRT_COLS = 90?
                je      L01             ; jump if characters are 8
                                        ;  pixels wide

                mov     ah,7Fh          ; AX := 9-bit mask
                cmp     ARGc,0C0h
                jb      L01             ; jump if character code ...

                cmp     ARGc,0DFh
                ja      L01             ; ... outside of range 0C0-0DFh

                inc     VAR9bits        ; set flag to extend to 9 bits

L01:            ror     ax,cl           ; AX := bit mask in proper position
                mov     VARmask,ax

; set up character definition table addressing

                mov     ax,40h
                mov     ds,ax           ; DS := segment of BIOS Video
                                        ;        Display Data area
                mov     ch,ds:[85h]     ; CH := POINTS (pixel rows in
                                        ;        character)

                xor     ax,ax
                mov     ds,ax           ; DS := absolute zero

                mov     ax,ARGc         ; AL := character code
                cmp     al,80h
                jae     L02

                mov     bx,43h*4        ; DS:BX -> int 43h vector if
                                        ;  char < 80h
                jmp     short L03

L02:            mov     bx,1Fh*4        ; DS:BX -> int 1Fh vector if char
                                        ;  >= 80h
                sub     al,80h          ; put character code in range of
                                        ;  table

L03:            les     di,ds:[bx]      ; ES:DI -> start of character table
                mul     ch              ; AX := offset into char def table
                                        ;        (POINTS * char code)
                add     di,ax           ; DI := addr of char def

                pop     ds              ; DS:SI -> video buffer

; set up control registers

                mov     dx,3B4h         ; control register I/O port

                push    cx              ; preserve CX
                mov     ah,ARGbkgd      ; AH := background pixel value
                mov     cl,4
                shl     ah,cl           ; AH bits 4-7 := background pixel
                                        ;                 value
                or      ah,ARGfgd       ; AH bits 0-3 := foreground pixel
                                        ;                 value
                pop     cx              ; restore CX

                mov     al,1Ah          ; AL := Read/Write Color reg number
                out     dx,ax           ; set Read/Write Color value

; mask and set pixels in the video buffer

L20:            xor     bh,bh
                mov     bl,es:[di]      ; BX := bit pattern for next
                                        ;        pixel row

                inc     di              ; increment pointer to char def
                                        ;  table
                cmp     VAR9bits,0

                je      L21             ; jump if character is 8 pixels
                                        ;  wide

                ror     bx,1            ; copy lo-order bit of BX into ...
                rcl     bl,1            ;  hi-order bit

L21:            ror     bx,cl           ; rotate pixels into position

                mov     ax,5F19h        ; AH bit 6 := 1 (mask polarity)
                                        ; AH bits 4-5 := 01b (write mode 1)
                                        ; AH bits 0-3 := 1111b (don't care
                                          ;                 bits)
                                        ; AL := 19h (Read/Write Control
                                        ;        reg)
                out     dx,ax           ; set up Read/Write Control reg

                or      [si],bl         ; update foreground pixels
                or      [si+1],bh

                mov     ah,6Fh          ; set up write mode 2
                out     dx,ax

                or      bx,VARmask      ; BX := background pixel bit
                                        ;        pattern
                or      [si],bl         ; update background pixels
                or      [si+1],bh

                add     si,2000h        ; increment to next portion of
                                        ;  interleave
                jns     L22

                add     si,90-8000h     ; increment to first portion of
                                        ;  interleave

L22:            dec     ch
                jnz     L20

; restore default InColor register values

                mov     ax,4019h        ; default Read/Write Control reg
                out     dx,ax

                mov     ax,071Ah        ; default Read/Write Color reg
                out     dx,ax

                pop     ds              ; restore caller registers and
                                        ;  return
                pop     si
                pop     di
                mov     sp,bp
                pop     bp
                ret

_DisplayCharInC ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 10-1a.  Using character generator RAM on the EGA and VGA.
���������������������������������������������������������������������������

                TITLE   'Listing 10-1a'
                NAME    CGenModeSet
                PAGE    55,132

;
; Name:         CGenModeSet
;
;               Direct access to EGA and VGA alpha character generator RAM
;
; Caller:       Microsoft C:
;
;                       void CGenModeSet();
;

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _CGenModeSet
_CGenModeSet    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si

; Program the Sequencer

                cli                     ; disable interrupts
                mov     dx,3C4h         ; Sequencer port address
                mov     si,offset DGROUP:SeqParms
                mov     cx,4

L01:            lodsw                   ; AH := value for Sequencer
                                        ;        register
                                        ; AL := register number
                out     dx,ax           ; program the register
                loop    L01
                sti                     ; enable interrupts

; Program the Graphics Controller

                mov     dl,0CEh         ; DX := 3CEH (Graphics Controller
                                        ;             port address)
                mov     si,offset DGROUP:GCParms
                mov     cx,3

L02:            lodsw                   ; program the Graphics Controller
                out     dx,ax
                loop    L02

                pop     si
                pop     bp
                ret

_CGenModeSet    ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

; Format of the parameters is:  Lo-order byte:  Register number
;                               Hi-order byte:  Value for reg

SeqParms        DW      0100h           ; synchronous reset
                DW      0402h           ; CPU writes only to map 2
                DW      0704h           ; sequential addressing
                DW      0300h           ; clear synchronous reset

GCParms         DW      0204h           ; select map 2 for CPU reads
                DW      0005h           ; disable odd-even addressing
                DW      0006h           ; map starts at A000:0000

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 10-1b.  Restoring character generator RAM on the EGA and VGA.
���������������������������������������������������������������������������

                TITLE   'Listing 10-1b'
                NAME    CGenModeClear
                PAGE    55,132
;
; Name:         CGenModeClear
;
;               Restore EGA or VGA alphanumeric mode after accessing
;                character generator RAM
;
; Caller:       Microsoft C:
;
;                       void CGenModeClear();
;

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _CGenModeClear
_CGenModeClear  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si

; Program the Sequencer

                cli                     ; disable interrupts
                mov     dx,3C4h         ; Sequencer port address
                mov     si,offset DGROUP:SeqParms
                mov     cx,4

L01:            lodsw                   ; AH := value for Sequencer
                                                 register
                                        ; AL := register number
                out     dx,ax           ; program the register
                loop    L01
                sti                     ; enable interrupts

; Program the Graphics Controller

                mov     dl,0CEh         ; DX := 3CEH (Graphics Controller
                                        ;             port address)
                mov     si,offset DGROUP:GCParms
                mov     cx,3

L02:            lodsw                   ; program the Graphics Controller
                out     dx,ax
                loop    L02

                mov     ah,0Fh          ; AH := INT 10H function number
                int     10h             ; get video mode
                cmp     al,7
                jne     L03             ; jump if not monochrome mode

                mov     ax,0806h        ; program Graphics Controller
                out     dx,ax           ;  to start map at B000:0000

L03:            pop     si
                pop     bp
                ret

_CGenModeClear  ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

; Format of the parameters is:  Lo-order byte:  Register number
;                               Hi-order byte:  Value for reg

SeqParms        DW      0100h           ; synchronous reset
                DW      0302h           ; CPU writes to maps 0 and 1
                DW      0304h           ; odd-even addressing
                DW      0300h           ; clear synchronous reset

GCParms         DW      0004h           ; select map 0 for CPU reads
                DW      1005h           ; enable odd-even addressing
                DW      0E06h           ; map starts at B800:0000

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 10-2a.  Loading character definitions on an EGA or VGA.
���������������������������������������������������������������������������

                TITLE   'Listing 10-2a'
                NAME    CGenRead1
                PAGE    55,132

;
; Name:         CGenRead1
;
;               Read 256 character definitions into EGA or VGA character
;                RAM
;
; Caller:       Microsoft C:
;
;                       void CGenRead1(f,p);
;
;                       int     f;     /* file handle */
;                       int     p;     /* bytes per character definition */
;

ARGf            EQU     [bp+4]
ARGp            EQU     [bp+6]

CGenRAMSeg      EQU     0A000h         ; start of character generator RAM
CGenRAMOffset   EQU     0
CGenDefSize     EQU     32             ; size in bytes of one character def

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _CGenRead1

_CGenRead1      PROC    near

                push    bp             ; preserve registers
                mov     bp,sp
                push    ds
                push    si
                push    di

; zero character definition RAM

                mov     di,CGenRAMSeg
                mov     es,di          ; ES := char gen RAM segment
                mov     di,CGenRAMOffset

                mov     cx,256*CGenDefSize/2    ; CX := number of words to
                                                ;        zero
                xor     ax,ax
                rep     stosw

; load character definitions from file

                mov     cx,256         ; assume 256 character defs in the
                                       ;  file

                mov     bx,ARGf        ; BX := file handle
                mov     si,ARGp        ; CX := bytes per character
                                       ;        definition
                push    es
                pop     ds
                mov     dx,CGenRAMOffset  ; DS:DX -> start of character gen
                                          ;  RAM

L01:            xchg    cx,si          ; CX := number of bytes to read
                                       ; SI := loop counter
                mov     ah,3Fh         ; AH := INT 21H function number
                int     21h
                add     dx,CGenDefSize ; DS:DX -> next character def in RAM
                xchg    cx,si          ; CX := loop counter
                                       ; SI := number of bytes to read
                loop    L01

                pop     di             ; restore registers and exit
                pop     si
                pop     ds
                pop     bp
                ret

_CGenRead1      ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 10-2b.  Calling CGenRead1 from a C program.
���������������������������������������������������������������������������

/* Listing 10-2b */

#include        <fcntl.h>
#include        <stdio.h>

main(argc,argv)
int     argc;
char    **argv;
{
        int     i;
        int     FileHandle;
        int     Points;                /* bytes per character definition */
        long    lseek();
        long    FileSize;

        if (argc != 2)                 /* verify filename */
        {
          printf( "\nNo filename specified\n" );
          exit( 1 );
        }

        FileHandle = open( argv[1], O_RDONLY );         /* open the file */

        if ( FileHandle == -1 )
        {
          printf( "\nCan't open '%s'\n", argv[1] );
          exit( 2 );
        }

        CGenModeSet();       /* make character generator RAM addressable */

        FileSize = lseek( FileHandle, 0L, SEEK_END );   /* get file size */
        Points = FileSize / 256;             /* determine character size */

        lseek( FileHandle, 0L, SEEK_SET );   /* start of file */

        CGenRead1( FileHandle, Points );

        CGenModeClear();     /* restore previous alphanumeric mode */
}



���������������������������������������������������������������������������
Listing 10-3a.  Using the BIOS to load character definitions.
���������������������������������������������������������������������������

                TITLE   'Listing 10-3a'
                NAME    CGenRead2
                PAGE    55,132
;
; Name:         CGenRead2
;
;               Use video BIOS to read 256 character definitions into EGA
;                or VGA character RAM
;
; Caller:       Microsoft C:
;
;                       void CGenRead2(f);
;
;                       int     f;      /* file handle */
;

ARGf            EQU     [bp+4]

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _CGenRead2
_CGenRead2      PROC    near

                push    bp              ; preserve registers
                mov     bp,sp

; load character definitions from file

                mov     cx,256*32       ; assume 256 32-byte character defs
                                        ;  in the file
                mov     bx,ARGf         ; BX := file handle
                mov     dx,offset DGROUP:CharBuf  ; DS:DX -> start of
                                                  ;  buffer

                mov     ah,3Fh          ; AH := INT 21H function number
                int     21h             ; read the file
                                        ; AX := number of bytes read

; call video BIOS to load character generator RAM

                push    ds
                pop     es
                mov     bp,offset DGROUP:CharBuf  ; ES:BP -> character defs
                mov     bl,0            ; BL := block of char gen RAM
                                        ;        to load
                mov     bh,ah           ; AH := bytes per character
                                        ;        (number of bytes read)
                                        ;        / 256
                mov     cx,256          ; number of character defs to store
                xor     dx,dx           ; first character number
                mov     ax,1100h        ; AH := 11H (INT 10H function
                                        ;            number)
                                        ; AL := 0 (subfunction number)
                int     10h

                pop     bp              ; restore BP and exit
                ret

_CGenRead2      ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

CharBuf         DB      256*32 dup(?)

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 10-3b.  Calling CGenRead2 from a C program.
���������������������������������������������������������������������������

/* Listing 10-3b */

#include        <fcntl.h>

main(argc,argv)
int     argc;
char    **argv;
{
        int     i;
        int     FileHandle;


        if (argc != 2)                                /* verify filename */
        {
          printf( "\nNo filename specified\n" );
          exit( 1 );
        }

        FileHandle = open( argv[1], O_RDONLY );       /* open the file */

        if ( FileHandle == -1 )
        {
          printf( "\nCan't open '%s'\n", argv[1] );
          exit( 2 );
        }


        CGenRead2( FileHandle );    /* call video BIOS to load file into */
                                    /*  character generator RAM */
}



���������������������������������������������������������������������������
Listing 10-4.  Using a ROM BIOS character definition table.
���������������������������������������������������������������������������

        mov     ax,1102h        ; AH := INT 10H function number
                                ; AL := 02h (load ROM BIOS 8x8 characters)
        mov     bl,0            ; BL := character generator RAM bank
        int     10h             ; load alphanumeric character set



���������������������������������������������������������������������������
Listing 10-5.  Loading font pages on an MCGA.
���������������������������������������������������������������������������

                TITLE   'Listing 10-5'
                NAME    SetFontPages
                PAGE    55,132

;
; Name:         SetFontPages
;
;               Update MCGA Font Pages
;
; Caller:       Microsoft C:
;
;                       void SetFontPages(n0,n1);
;
;                       int     n0,n1;  /* font page values */
;

ARGn0           EQU     [bp+4]
ARGn1           EQU     [bp+6]

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _SetFontPages
_SetFontPages   PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,1103h        ; AH := INT 10H function number
                                        ; AL := 3 (Set Block Specifier)
                mov     bl,ARGn1        ; BL := value for bits 2-3
                shl     bl,1
                shl     bl,1            ; BL bits 2-3 := n1
                or      bl,ARGn0        ; BL bits 0-1 := n0
                int     10h             ; load font pages

                pop     bp
                ret

_SetFontPages   ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 10-6.  Configuring an HGC+ or InColor Card for updating
               character generator RAM.
���������������������������������������������������������������������������

        mov     dx,3B4h
        mov     ax,0114h        ; AH bit 0 := 1 (enable RAM character
                                ;                 generator)
                                ; AL := 14h (xMode register number)
        out     dx,ax

        mov     dl,0BFh         ; DX := 3BFh (Config Switch register)
        mov     al,1            ; AL bit 0 := 1 (make RAM at B000:4000
                                ;                 addressable)
        out     dx,al

        .
        .                       ; (update character generator RAM)
        .

        mov     dx,3BFh         ; DX := 3BFh (Config Switch register)
        mov     al,0            ; AL bit 0 := 0 (exclude RAM at
                                ;                B000:4000 from memory map)
        out     dx,al



���������������������������������������������������������������������������
Listing 10-7.  Programming the Sequencer Character Map Select register
               on the EGA and VGA.
���������������������������������������������������������������������������

; using the video BIOS

        mov     ax,1103h        ; AH := INT 10H function number
                                ; AL := 3
        mov     bl,CharMapValue ; BL := value for Character Map Select
                                ;        register
        int     10h


; programming the register directly

        mov     dx,3C4h         ; DX := Sequencer I/O port
        mov     ax,100h         ; AH bit 1 := 0 (synchronous reset)
                                ; AL := 0 (Sequencer Reset register number)
        cli                     ; disable interrupts
        out     dx,ax           ; reset Sequencer

        mov     ah,CharMapValue ; AH := value for Character Map Select
                                ;        register
        mov     al,3            ; AL := 3 (Char Map Select register number)
        out     dx,ax           ; update this register

        mov     ax,300h         ; AH bit 1 := 1 (clear synchronous reset)
                                ; AL := 0 (Reset register number)
        out     dx,ax           ; clear the reset
        sti                     ; enable interrupts



���������������������������������������������������������������������������
Listing 10-8.  Zeroing bit 3 of the Color Plane Enable register. This
               causes bit 3 of a character's attribute byte to have no
               effect on its displayed attribute.
���������������������������������������������������������������������������

mov     ax,1000h                ; AH := 10H (INT 10H function number)
                                ; AL := 0 (set specified register)
mov     bx,0712h                ; BH := 0111b (Color Plane Enable value)
                                ; BL := 12H (Color Plane Enable reg number)
int     10h                     ; update Color Plane Enable register



���������������������������������������������������������������������������
Listing 10-9.  Establishing an 80-by-43 alphanumeric mode on an EGA.
���������������������������������������������������������������������������

; establish 80x25 alphanumeric mode (350-line vertical resolution)

        mov     ax,3            ; AH := 0 (INT 10H function number)
        int     10h             ; AL := 3 (80x25 16-color mode)

; load video BIOS 8x8 characters into alphanumeric character generator

        mov     ax,1112h        ; AH := INT 10H function number
                                ; AL := 8x8 character set load
        mov     bl,0            ; BL := block to load
        int     10h             ; load 8x8 characters into RAM

; set cursor position in character matrix

        mov     ax,40h
        mov     es,ax           ; ES -> video BIOS data area
        mov     dx,es:[63h]     ; DX := CRTC address port from 0040:0063
                                ;        (3B4H or 3D4H)
        mov     ax,060Ah        ; AH := 6 (Cursor Start value)
                                ; AL := 0AH (Cursor Start reg number)
        out     dx,ax           ; update CRTC Cursor Start register

        mov     ax,000Bh        ; AH := 0 (Cursor End value)
                                ; AL := 0BH (Cursor End reg number)
        out     dx,ax           ; update CRTC Cursor End register

; use alternate video BIOS print screen routine

        mov     ah,12h          ; AH := INT 10H function number
        mov     bl,20h          ; BL := subfunction number
        int     10h             ; update INT 5 vector (print screen)



���������������������������������������������������������������������������
Listing 10-10.  Establishing an 80-by-50 alphanumeric mode on a VGA.
���������������������������������������������������������������������������

; establish 80x25 alphanumeric mode with 400-line vertical resolution

        mov     ax,1202h                ; AH := 12h (INT 10H function
                                        ;             number)
                                        ; AL := 2 (select 400 scan lines)
        mov     bl,30h                  ; subfunction number
        int     10h

        mov     ax,3                    ; AH := 0 (INT 10H function number)
        int     10h                     ; AL := 3 (80x25 16-color mode)

; load video BIOS 8x8 characters into alphanumeric character generator

        mov     ax,1112h                ; AH := INT 10H function number
                                        ; AL := 8x8 character set load
        mov     bl,0                    ; BL := block to load
        int     10h                     ; load 8x8 characters into RAM



���������������������������������������������������������������������������
Listing 10-11.  Establishing an 80-by-40 alphanumeric mode on an MCGA.
���������������������������������������������������������������������������

; establish 80x25 alphanumeric mode

        mov     ax,3            ; AH := 0 (INT 10H function number)
                                ; AL := 3 (80x25 16-color mode)
        int     10h

; zero the bit patterns in character generator RAM

        mov     di,0A000h
        mov     es,di
        xor     di,di           ; ES:DI -> character generator RAM
        xor     ax,ax           ; AH := 0 (bit pattern)
                                ; AL := 0 (initial character code)
        mov     cx,256*16       ; CX := number of words

L01:    stosw                   ; store character code and zero
        inc     al              ; AL := next character code
        loop    L01

; load video BIOS 8x8 characters into alphanumeric character generator

        mov     ax,1102h        ; AH := INT 10H function number
                                ; AL := 8x8 character set load
        mov     bl,0            ; BL := block to load
        int     10h             ; load 8x8 characters into RAM

        mov     ax,1103h        ; AH := INT 10H function number
                                ; AL := character generator load
        mov     bl,0            ; BL := blocks to load
        int     10h             ; load characters into character generator

; program CRT Controller to display 8x10 characters

        mov     dx,3D4h         ; DX := MCGA I/O port address
        mov     ax,409h         ; AL := 9 (register number)
                                ; AH := 4 (value for register)
        out     dx,ax           ; update Scan Lines register

        mov     al,0Ah          ; AL := 0AH (register number)
        out     dx,ax           ; update Cursor Start register

        mov     al,0Bh          ; AL := 0BH (register number)
        out     dx,ax           ; update Cursor End register

; update status variables in video BIOS data segment

        mov     ax,40h
        mov     ds,ax           ; DS -> video BIOS data segment

        mov     word ptr ds:[4Ch],80*40*2 ; update CRT_LEN in BIOS data
                                          ;  area
        mov     byte ptr ds:[84h],40-1  ; update ROWS
        mov     word ptr ds:[85h],10    ; update POINTS



���������������������������������������������������������������������������
Listing 10-12.  Programming the EGA alphanumeric character size.
���������������������������������������������������������������������������

                TITLE   'Listing 10-12'
                NAME    AlphaModeSet
                PAGE    55,132

;
; Name:         AlphaModeSet
;
;               Program the CRTC in 80-column EGA alphanumeric modes
;
; Caller:       Microsoft C:
;
;                       void AlphaModeSet(w,h,c);
;
;                       int     w;      /* width of character matrix */
;                       int     h;      /* height of character matrix */
;                       int     c;      /* character code size */
;

ARGw            EQU     byte ptr [bp+4]        ; must be 8 or 9 pixels wide
ARGh            EQU     byte ptr [bp+6]        ; must be 2-32 pixels high
ARGc            EQU     byte ptr [bp+8]        ; must be 8 or 9 bits

CRT_MODE        EQU     49h             ; addresses in video BIOS data area
CRT_COLS        EQU     4Ah
ADDR_6845       EQU     63h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _AlphaModeSet
_AlphaModeSet   PROC    near

                push    bp             ; preserve caller registers
                mov     bp,sp
                push    si

; Program the CRTC

                mov     bx,40h
                mov     es,bx          ; ES := video BIOS data segment

                mov     bl,ARGw        ; BL := character width
                mov     bh,ARGh        ; BH := character height
                call    SetCRTC

; Program the Sequencer and Attribute Controller for 8 or 9 dots per
;  character

                mov     dx,3C4h
                mov     ax,0100h       ; AH bit 1 := 0 (synchronous reset)
                                       ; AL := 0 (Reset register number)
                cli                    ; disable interrupts
                out     dx,ax          ; Sequencer synchronous reset

                mov     bx,1           ; BH,BL := values for 8-wide chars:
                                       ;  BH := 0 (value for Horiz Pel Pan)
                                       ;  BL := 1 (value for Clocking Mode)
                cmp     ARGw,8
                je      L01            ; jump if 8-wide characters
                mov     bx,0800h       ; BH,BL := values for 9-wide
                                       ;           characters

L01:            mov     ah,bl          ; AH := value for Clocking Mode reg
                mov     al,1           ; AL := Clocking Mode reg number
                out     dx,ax          ; program the Sequencer

                mov     ax,0300h       ; AH := 3 (disable reset)
                                       ; AL := 0 (Sequencer register
                                       ;           number)
                out     dx,ax          ; disable Sequencer reset
                sti                    ; enable interrupts

                mov     bl,13h         ; BL := Horizontal Pel Pan reg
                                       ;        number
                mov     ax,1000h       ; AH := 10H (INT 10H function
                                       ;             number)
                                       ; AL := 0 (set specified register)
                int     10h            ; program Attribute Controller

; Program the Attribute Controller for 8- or 9-bit character codes

                mov     ax,1000h       ; AH := 10H (INT 10H function
                                       ;             number)
                                       ; AL := 0 (set specified register)
                mov     bx,0F12h       ; BH := 0FH (Color Plane Enable
                                       ;             value)
                                       ; BL := 12H (Color Plane Enable
                                       ;             reg #)
                cmp     ARGc,8
                je      L02            ; jump if 8-bit character codes

                mov     bh,7           ; BH bit 3 := 0 (ignore bit 3 of all
                                       ;                 attributes)
L02:            int     10h            ; update Color Plane Enable register

; update video BIOS data area

                cmp     byte ptr es:[CRT_MODE],7
                jne     L03            ; jump if not monochrome mode

                mov     ax,720         ; AX := displayed pixels per row
                div     ARGw           ; AL := displayed character columns
                mov     es:[CRT_COLS],al

L03:            pop     si
                pop     bp
                ret

_AlphaModeSet   ENDP


SetCRTC         PROC    near           ; Caller:      BH = character height
                                       ;              BL = character width
                push    dx
                mov     dx,es:[ADDR_6845]  ; CRTC I/O port

; establish CRTC vertical timing and cursor position in character matrix

                push    bx             ; preserve height and width
                mov     ax,1110h       ; AH := 11H (INT 10H function
                                       ;             number)
                                       ; AL := 0 (user alpha load)
                xor     cx,cx          ; CX := 0 (store no characters)
                int     10h            ; call BIOS to program CRTC for
                                       ;  height of characters

                pop     ax             ; AH := character height
                push    ax             ; preserve height and width
                sub     ah,2           ; AH := starting scan line for
                                       ;        cursor
                mov     al,0Ah         ; AL := 0AH (Cursor Start reg
                                       ;             number)
                out     dx,ax          ; update CRTC Cursor Start register

                mov     ax,000Bh       ; AH := 0 (Cursor End value)
                                       ; AL := 0BH (Cursor End reg number)
                out     dx,ax          ; update CRTC Cursor End register

; establish CRTC horizontal timing

                pop     bx             ; BX := character height and width
                cmp     byte ptr es:[CRT_MODE],7

                jne     L10            ; exit if not monochrome mode

                xor     bh,bh          ; BX := character width
                sub     bl,8           ; BX := 0 or 1
                neg     bx             ; BX := 0 or 0FFFFH
                and     bx,14          ; BX := 0 or 14 (offset into table)
                mov     si,bx          ; SI := offset into table

                add     si,offset DGROUP:HorizParms   ; DS:SI -> parameters
                call    UpdateCRTC

L10:            pop     dx
                ret

SetCRTC         ENDP


UpdateCRTC      PROC    near           ; Caller:     DX = CRTC address port
                                       ;             DS:SI -> parameters
                                       ; Destroys:   AX,CX

                mov     cx,7           ; CX := number of registers to
                                       ;        update

L20:            lodsw                  ; AH := data for CRTC register in AL
                out     dx,ax          ; update the register
                loop    L20

                ret

UpdateCRTC      ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

HorizParms      DW      6C00h,5901h,6002h,2403h,5B04h,6A05h,2D13h  ; 8-wide
                DW      6000h,4F01h,5602h,3A03h,5104h,6005h,2813h  ; 9-wide

_DATA           ENDS
                END



���������������������������������������������������������������������������
Listing 10-13.  Programming the VGA alphanumeric character size.
���������������������������������������������������������������������������

                TITLE   'Listing 10-13'
                NAME    AlphaModeSet
                PAGE    55,132

;
; Name:         AlphaModeSet
;
;               Program the CRTC in 80-column VGA alphanumeric modes
;
; Caller:       Microsoft C:
;
;                       void AlphaModeSet(w,h,c);
;
;                       int     w;      /* width of character matrix */
;                       int     h;      /* height of character matrix */
;                       int     c;      /* character code size */
;

ARGw            EQU     byte ptr [bp+4]        ; must be 8 or 9 pixels wide
ARGh            EQU     byte ptr [bp+6]        ; must be 2-32 pixels high
ARGc            EQU     byte ptr [bp+8]        ; must be 8 or 9 bits

CRT_COLS        EQU     4Ah            ; addresses in video BIOS data area
ADDR_6845       EQU     63h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _AlphaModeSet
_AlphaModeSet   PROC    near

                push    bp             ; preserve caller registers
                mov     bp,sp
                push    si

; Program the CRTC

                mov     bx,40h
                mov     es,bx          ; ES := video BIOS data segment

                mov     bl,ARGw        ; BL := character width
                mov     bh,ARGh        ; BH := character height
                call    SetCRTC

; Program the Sequencer and Attribute Controller for 8 or 9 dots per
;  character

                mov     dx,3C4h
                mov     ax,0100h       ; AH bit 1 := 0 (synchronous reset)
                                       ; AL := 0 (Reset register number)
                cli                    ; disable interrupts
                out     dx,ax          ; Sequencer synchronous reset

                mov     bx,1           ; BH,BL := values for 8-wide chars:
                                       ;  BH := 0 (value for Horiz Pel Pan)
                                       ;  BL := 1 (value for Clocking Mode)
                cmp     ARGw,8
                je      L01            ; jump if 8-wide characters

                mov     bx,0800h       ; BH,BL := values for 9-wide
                                       ;           characters

L01:            mov     ah,bl          ; AH := value for Clocking Mode reg
                mov     al,1           ; AL := Clocking Mode reg number
                out     dx,ax          ; program the Sequencer

                mov     ax,0300h       ; AH := 3 (disable reset)
                                       ; AL := 0 (Sequencer register
                                       ;           number)
                out     dx,ax          ; disable Sequencer reset
                sti                    ; enable interrupts

                mov     bl,13h         ; BL := Horizontal Pel Pan reg
                                       ;        number
                mov     ax,1000h       ; AH := 10H (INT 10H function
                                       ;             number)
                                       ; AL := 0 (set specified register)
                int     10h            ; program Attribute Controller

; Program the Attribute Controller for 8- or 9-bit character codes

                mov     ax,1000h       ; AH := 10H (INT 10H function
                                       ;             number)
                                       ; AL := 0 (set specified register)
                mov     bx,0F12h       ; BH := 0FH (Color Plane Enable
                                       ;             value)
                                       ; BL := 12H (Color Plane Enable
                                       ;             reg #)
                cmp     ARGc,8
                je      L02            ; jump if 8-bit character codes

                mov     bh,7           ; BH bit 3 := 0 (ignore bit 3 of all
                                       ;                 attributes)
L02:            int     10h            ; update Color Plane Enable register

; update video BIOS data area

                mov     ax,720         ; AX := displayed pixels per row
                div     ARGw           ; AL := displayed character columns
                mov     es:[CRT_COLS],al

                pop     si
                pop     bp
                ret

_AlphaModeSet   ENDP


SetCRTC         PROC    near           ; Caller:      BH = character height
                                       ;              BL = character width
                push    dx
                mov     dx,es:[ADDR_6845]  ; CRTC I/O port

; establish CRTC vertical timing and cursor position in character matrix

                push    bx             ; preserve char height and width
                mov     ax,1110h       ; AH := 11H (INT 10H function
                                       ;             number)
                                       ; AL := 0 (user alpha load)
                xor     cx,cx          ; CX := 0 (store no characters)
                int     10h            ; call BIOS to program CRTC
                pop     bx

; enable I/O writes to CRTC registers

                mov     al,11h         ; AL := Vertical Retrace End reg
                                       ;        number
                out     dx,al
                inc     dx
                in      al,dx          ; AL := current value of this
                                       ;        register
                dec     dx

                mov     ah,al          ; AH := current value
                mov     al,11h         ; AL := register number
                push    ax             ; save on stack

                and     ah,01111111b   ; zero bit 7
                out     dx,ax          ; update this register

; establish CRTC horizontal timing

                xor     bh,bh          ; BX := character width
                sub     bl,8           ; BX := 0 or 1
                neg     bx             ; BX := 0 or 0FFFFH
                and     bx,14          ; BX := 0 or 14 (offset into table)
                mov     si,bx          ; SI := offset into table

                add     si,offset DGROUP:HorizParms   ; DS:SI -> parameters
                call    UpdateCRTC

; write-protect CRTC registers

                pop     ax             ; AX := previous VR End register
                                       ;        data
                out     dx,ax          ; restore this register

                pop     dx
                ret

SetCRTC         ENDP


UpdateCRTC      PROC    near           ; Caller:     DX = CRTC address port
                                       ;             DS:SI -> parameters
                                       ; Destroys:   AX,CX

                mov     cx,7           ; CX := number of registers to
                                       ;        update

L10:            lodsw                  ; AH := data for CRTC register in AL
                out     dx,ax          ; update the register
                loop    L10

                ret

UpdateCRTC      ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

HorizParms      DW      6A00h,5901h,5A02h,8D03h,6304h,8805h,2D13h  ; 8-wide
                DW      5F00h,4F01h,5002h,8203h,5504h,8105h,2813h  ; 9-wide

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 10-14.  Programming the alphanumeric character size on
                the HGC+ and InColor Card.
���������������������������������������������������������������������������

                TITLE   'Listing 10-14'
                NAME    AlphaModeSet
                PAGE    55,132

;
; Name:         AlphaModeSet
;
; Function:     Program the CRTC in alphanumeric modes on HGC+ or InColor
;                Card
;
; Caller:       Microsoft C:
;
;                       void AlphaModeSet(w,h,c);
;
;                       int     w;      /* width of character matrix */
;                       int     h;      /* height of character matrix */
;                       int     c;      /* character code size */
;

ARGw            EQU     byte ptr [bp+4]        ; must be 8 or 9 pixels wide
ARGh            EQU     byte ptr [bp+6]        ; must be 4-16 pixels high
ARGc            EQU     byte ptr [bp+8]        ; must be 8 or 12 bits

CRT_COLS        EQU     4Ah
CRT_LEN         EQU     4Ch
CRT_MODE_SET    EQU     65h
ROWS            EQU     84h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _AlphaModeSet
_AlphaModeSet   PROC    near

                push    bp             ; preserve caller registers
                mov     bp,sp
                push    ds
                push    si

; Set Configuration Switch to bring RAM starting at B000:4000 into
;  memory map

                mov     dx,3BFh        ; DX := Configuration Switch port
                mov     al,1           ; AL bit 1 := 0 (exclude 2nd 32K of
                                       ;                 video buffer)
                                       ; AL bit 0 := 1 (make RAM at
                out     dx,ax          ;             B000:4000 addressable)

; Blank the screen to avoid interference during CRTC programming

                mov     dx,3B8h        ; DX := CRTC Mode Control Register
                                       ;        port
                xor     al,al          ; AL bit 3 := 0 (disable video
                                       ;                signal)
                out     dx,al          ; blank the screen

; Program the CRTC

                mov     bh,ARGw        ; BH := character width
                mov     bl,ARGh        ; BL := character height
                call    SetHercCRTC

; Set the xModeReg

                mov     dx,3B4h        ; DX := CRTC address port
                mov     ax,114h        ; AH bit 0 := 1 (enable RAM-based
                                       ;              character generator)
                                       ; AL := 14h (xModeReg number)
                cmp     ARGw,9
                je      L01            ; jump if 9-wide characters

                or      ah,2           ; AH bit 1 := 1 (8-wide characters)

L01:            cmp     ARGc,8
                je      L02            ; jump if 8-bit character codes

                or      ah,4           ; AH bit 2 := 1 (12-bit character
                                       ;                 codes)

L02:            out     dx,ax          ; update the register

; update video BIOS data area

                mov     ax,40h
                mov     ds,ax          ; DS := video BIOS data segment

                mov     ax,720         ; AX := displayed pixels per row
                div     ARGw           ; AL := displayed character columns
                mov     ds:[CRT_COLS],al

                mov     ax,350         ; AX := number of displayed scan
                                       ;        lines
                div     ARGh           ; AL := displayed character rows
                dec     al             ; AL := (character rows) - 1
                mov     ds:[ROWS],al

                inc     al
                mul     byte ptr ds:[CRT_COLS]
                shl     ax,1           ; AX := rows * columns * 2
                mov     ds:[CRT_LEN],ax

; re-enable display and exit

                mov     dx,3B8h        ; DX := CRT Mode Control port
                mov     al,ds:[CRT_MODE_SET]    ; restore previous value
                out     dx,al

                pop     si
                pop     ds
                pop     bp
                ret

_AlphaModeSet   ENDP


SetHercCRTC     PROC    near           ; Caller:     BH = character width
                                       ;             BL = character height

                push    dx
                mov     dx,3B4h        ; DX := CRTC Address Reg port 3B4h

; establish cursor position in character matrix

                mov     ah,bl
                dec     ah             ; AH := value for Max Scan Line reg
                mov     al,9           ; AL := Max Scan Line register
                                       ;        number
                out     dx,ax

                mov     al,0Bh         ; AL := Cursor End reg number
                out     dx,ax          ; set cursor to end on last line of
                                       ;  character matrix

                sub     ax,101h        ; AH := second-to-last line
                                       ; AL := 0AH (Cursor Start reg
                                       ;             number)
                out     dx,ax          ; set cursor to start on second-to-
                                       ;  last line

; compute offsets into parameter tables

                sub     bx,0804h       ; BH := 0 or 1
                                       ; BL := 0 through 12
                add     bx,bx
                add     bx,bx          ; BH := 0 or 4
                                       ; BL := 0 through 48
; establish CRTC horizontal timing

                push    bx             ; preserve BX
                mov     bl,bh
                xor     bh,bh          ; BX := 0 or 4
                add     bx,offset DGROUP:HorizParms   ; DS:BX -> parameters

                mov     al,0           ; AL := first CRTC reg to update
                call    UpdateCRTC

; establish vertical timing

                pop     bx
                xor     bh,bh          ; BX := 0 through 48
                add     bx,offset DGROUP:VertParms    ; DS:BX -> parameters

                mov     al,4           ; AL := first CRTC reg to update
                call    UpdateCRTC

                pop     dx             ; restore DX
                ret

SetHercCRTC     ENDP

UpdateCRTC      PROC    near           ; Caller:     AL = first reg number
                                       ;             DX = CRTC address port
                                       ;             DS:BX -> parameters
                                       ; Destroys:   AX,CX

                mov     cx,4           ; CX := number of registers to
                                       ;        update

L10:            mov     ah,[bx]        ; AH := data for CRTC register in AL
                out     dx,ax          ; update the register
                inc     ax             ; AL := next register number

                inc     bx             ; DS:BX -> next value in table
                loop    L10
                ret

UpdateCRTC      ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

HorizParms      DB      6Dh,5Ah,5Ch,0Fh         ; 8 pixels wide
                DB      61h,50h,52h,0Fh         ; 9 pixels wide


VertParms       DB      5Ch,02h,58h,59h         ; 4 scan lines high
                DB      4Ah,00h,46h,46h         ; 5
                DB      3Dh,04h,3Ah,3Bh         ; 6
                DB      34h,06h,32h,33h         ; 7
                DB      2Dh,02h,2Bh,2Ch         ; 8
                DB      28h,01h,26h,27h         ; 9
                DB      24h,00h,23h,23h         ; 10
                DB      20h,07h,1Fh,20h         ; 11
                DB      1Dh,0Ah,1Dh,1Dh         ; 12
                DB      1Bh,06h,1Ah,1Bh         ; 13
                DB      19h,06h,19h,19h         ; 14
                DB      17h,0Ah,17h,17h         ; 15
                DB      16h,02h,15h,16h         ; 16

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 10-15.  Creating a tiled graphics window on the EGA or VGA.
���������������������������������������������������������������������������

/* Listing 10-15 */

#define Points          14      /* displayed scan lines per character */
#define StartCharCode   0x80    /* first character code in "window" */
#define CGenDefSize     32      /* (use 16 for Hercules) */


char far *CRT_MODE = 0x00400049;        /* BIOS video mode number */
int  far *CRT_COLS = 0x0040004A;        /* characters per row */

char far *VideoBuffer;                  /* pointer to video buffer */
char far *CharDefTable = 0xA0000000;    /* pointer to char def RAM */
                                        /* (use 0xB0004000 for Hercules) */
main()
{
        int     i;
        int     CharCode;
        int     CharOffset;

        int     CharScanLine;
        int     CharDefOffset;
        int     Row,Column;

        /* establish alphanumeric mode */

        if (*CRT_MODE == 7)             /* set video buffer pointer */
          VideoBuffer = 0xB0000000;
        else
          VideoBuffer = 0xB8000000;

        AlphaModeSet( 8, Points, 8 );

        /* establish a tiled graphics window in the upper left corner */

        CharCode = StartCharCode;

        for ( Row = 0; Row < 4; Row++ )
          for ( Column = 0; Column < 32; Column++ )
          {
            CharOffset = (Row*(*CRT_COLS) + Column) * 2;
            VideoBuffer[CharOffset] = CharCode++;
          }

        /* clear the window */

        CGenModeSet();       /* make character generator RAM addressable */

        for (CharCode = StartCharCode; CharCode < 256; CharCode++ )
          for ( CharScanLine = 0; CharScanLine < Points; CharScanLine++ )
          {
            CharDefOffset = CharCode * CGenDefSize + CharScanLine;
            CharDefTable[CharDefOffset] = 0;
          }

        /* draw a few lines */

        for ( i = 0; i < 256; i++ )         /* horizontal lines */
        {
          SetPixel( i, 0 );
          SetPixel( i, 4*Points-1 );
        }

        for ( i = 0; i < 4*Points-1; i++ )  /* vertical lines */
        {
          SetPixel( 0, i );
          SetPixel( 255, i );
        }

        for( i = 0; i < Points*4; i++ )     /* diagonal lines */
        {
          SetPixel( i, i );
          SetPixel( 255-i, i );
        }

        CGenModeClear();                /* restore alphanumeric mode */
}

SetPixel( x, y )
int     x,y;            /* pixel coordinates */
{
        int     CharCode;
        int     CharScanLine;
        int     BitMask;
        int     CharDefOffset;

        CharCode = StartCharCode + (y/Points)*32 + x/8;
        CharScanLine = y % Points;            /* y MOD Points */
        BitMask = 0x80 >> (x % 8);            /* 10000000b SHR (x MOD 8) */

        CharDefOffset = CharCode*CGenDefSize + CharScanLine;
        CharDefTable[CharDefOffset] |= BitMask; /* OR the pixel */
}



���������������������������������������������������������������������������
Listing 10-16.  A routine to set pixels in a tiled graphics window on
                the MCGA.
���������������������������������������������������������������������������

SetPixel( x, y )
int     x,y;            /* pixel coordinates */
{
        int     CharCode;
        int     CharScanLine;
        int     BitMask;
        int     CharDefOffset;

        /* the window is 32 characters across */
        CharCode = StartCharCode + (y/Points)*32 + x/8;

        CharScanLine = y % Points;            /* y MOD Points */
        BitMask = 0x80 >> (x % 8);            /* 10000000b SHR (x MOD 8) */

        CharDefOffset = CharCode*2 + CharScanLine*512 + 1;
        CharDefTable[CharDefOffset] |= BitMask; /* OR the pixel */
}



���������������������������������������������������������������������������
Listing 11-1.  A routine to copy a block of pixels from the CGA video
               buffer to system RAM.
���������������������������������������������������������������������������

                TITLE   'Listing 11-1'
                NAME    GetBitBlock06
                PAGE    55,132

;
; Name:         GetBitBlock06
;
; Function:     Copy bit block from video buffer to system RAM
;                in 640x200 2-color mode
;
; Caller:       Microsoft C:
;
;                       int GetBitBlock06(x0,y0,x1,y1,buf);
;
;                            int x0,y0;    /* upper left corner of bit */
;                                          /*  block */
;                            int x1,y1;       /* lower right corner */
;                            char far *buf;   /* buffer */
;
; Notes:        Returns size of bit block in system RAM.

ARGx0           EQU     word ptr [bp+4]
ARGy0           EQU     word ptr [bp+6]
ARGx1           EQU     word ptr [bp+8]
ARGy1           EQU     word ptr [bp+10]
ADDRbuf         EQU              [bp+12]

VARPixelRows    EQU     word ptr [bp-2]
VARPixelRowLen  EQU     word ptr [bp-4]
VARincr         EQU     word ptr [bp-6]

ByteOffsetShift EQU     3               ; reflects number of pixels per
                                        ;  byte


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr06:near

                PUBLIC  _GetBitBlock06
_GetBitBlock06  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,6            ; establish stack frame
                push    ds
                push    si
                push    di

; compute dimensions of bit block

                mov     ax,ARGx1
                sub     ax,ARGx0

                mov     cx,0FF07h       ; CH := unshifted bit mask
                                        ; CL := AND mask for AL
                and     cl,al           ; CL := number of pixels in last
                                        ;        byte of row
                xor     cl,7            ; CL := number of bits to shift
                shl     ch,cl           ; CH := bit mask for last byte of
                                        ;        row
                mov     cl,ch
                push    cx              ; save on stack

                mov     cl,ByteOffsetShift
                shr     ax,cl
                inc     ax              ; AX := number of bytes per row
                push    ax              ; save on stack

                mov     ax,ARGy1
                sub     ax,ARGy0
                inc     ax              ; AX := number of pixel rows
                push    ax              ; save on stack

; establish addressing

                mov     ax,ARGy0
                mov     bx,ARGx0
                call    PixelAddr06     ; ES:BX -> x0,y0 in video buffer
                xor     cl,7            ; CL := number of bits to shift
                                        ;        left
                push    es
                pop     ds
                mov     si,bx           ; DS:SI -> video buffer

                mov     bx,2000h        ; BX := increment from 1st to 2nd
                                        ;        interleave in CGA video
                                        ;        buffer
                test    si,2000h
                jz      L01             ; jump if x0,y0 is in 1st
                                        ;  interleave

                mov     bx,80-2000h     ; increment from 2nd to 1st
                                        ;  interleave

L01:            mov     VARincr,bx      ; initialize this variable

                les     di,ADDRbuf      ; ES:DI -> buffer in system RAM

; build 5-byte bit block header

                pop     ax
                mov     VARPixelRows,ax
                stosw                   ; byte 0-1 := number of pixel rows
                pop     ax
                mov     VARPixelRowLen,ax
                stosw                   ; byte 2-3 := bytes per pixel row
                pop     ax
                mov     ch,al           ; CH := bit mask for last byte
                stosb                   ; byte 4 := bit mask for last byte

; copy from video buffer to system RAM

L02:            mov     bx,VARPixelRowLen
                push    si              ; preserve SI at start of pixel row

L03:            lodsw                   ; AL := next byte in video buffer
                                        ; AH := (next byte) + 1
                dec     si              ; DS:SI -> (next byte) + 1
                rol     ax,cl           ; AL := next 4 pixels in row
                stosb                   ; copy to system RAM
                dec     bx              ; loop across row
                jnz     L03

                and     es:[di-1],ch    ; mask last byte of row
                pop     si              ; DS:SI -> start of row
                add     si,VARincr      ; DS:SI -> start of next row
                xor     VARincr,2000h XOR (80-2000H)  ; update increment

                dec     VARPixelRows
                jnz     L02             ; loop down rows

                mov     ax,di
                sub     ax,ADDRbuf      ; AX := return value (size of bit
                                        ;        block in system RAM)

                pop     di              ; restore registers and exit
                pop     si
                pop     ds
                mov     sp,bp
                pop     bp
                ret

_GetBitBlock06  ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 11-2.  A routine to copy a block of pixels from system RAM to
               the CGA video buffer.
���������������������������������������������������������������������������

                TITLE   'Listing 11-2'
                NAME    StoreBitBlock06
                PAGE    55,132

;
; Name:         StoreBitBlock06
;
; Function:     Copy bit block from video buffer to system RAM
;                in 640x200 2-color mode
;
; Caller:       Microsoft C:
;
;                       void StoreBitBlock06(buf,x,y);
;
;                            char far *buf;   /* buffer */
;                            int x,y;         /* upper left corner of */
;                                             /*  bit block */
;

ADDRbuf         EQU     dword ptr [bp+4]
ARGx            EQU     word ptr [bp+8]
ARGy            EQU     word ptr [bp+10]

VARPixelRows    EQU     word ptr [bp-2]
VARPixelRowLen  EQU     word ptr [bp-4]
VARincr         EQU     word ptr [bp-6]


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                EXTRN   PixelAddr06:near

                PUBLIC  _StoreBitBlock06
_StoreBitBlock06 PROC   near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,6            ; establish stack frame
                push    ds
                push    si
                push    di

; establish addressing

                mov     ax,ARGy
                mov     bx,ARGx
                call    PixelAddr06     ; ES:BX -> byte offset of x,y
                xor     cl,7            ; CL := number of bits to shift
                                        ;        right

                mov     di,bx           ; ES:DI -> x,y in video buffer
                mov     bx,2000h        ; BX := increment from 1st to 2nd
                                        ;        interleave in CGA video
                                        ;        buffer
                test    di,2000h
                jz      L01             ; jump if x,y is in 1st interleave

                mov     bx,80-2000h     ; increment from 2nd to 1st
                                        ;  interleave

L01:            mov     VARincr,bx      ; initialize this variable

                mov     bx,StoreBitBlockOp  ; BX := subroutine address

                lds     si,ADDRbuf      ; ES:DI -> buffer in system RAM

; obtain dimensions of bit block from header

                lodsw                   ; AX := number of pixel rows
                mov     VARPixelRows,ax
                lodsw                   ; AX := bytes per pixel row
                mov     VARPixelRowLen,ax
                lodsb                   ; AL := bit mask for last byte in
                                        ;        row
                mov     ch,al

                jmp     bx              ; jump to subroutine

ReplaceBitBlock:
                cmp     cx,0FF00h       ; if mask = 0FFH and bits to
                                        ;  shift = 0
                jnz     L15             ;  jump if not byte-aligned

; routine for byte-aligned bit blocks

                mov     cx,VARPixelRowLen

L10:            push    di              ; preserve DI and CX
                push    cx
                rep     movsb           ; copy one pixel row into video
                                        ;  buffer
                pop     cx              ; restore DI and CX
                pop     di
                add     di,VARincr      ; ES:DI -> next pixel row in buffer
                xor     VARincr,2000h XOR (80-2000h) ; update increment
                dec     VARPixelRows
                jnz     L10             ; loop down pixel rows

                jmp     Lexit

; routine for all other bit blocks

L15:            not     ch              ; CH := mask for end of row
                mov     dx,0FF00h
                ror     dx,cl           ; DX := rotated mask for each byte

                mov     bx,VARPixelRowLen
                dec     bx              ; BX := bytes per row - 1

L16:            push    di
                test    bx,bx
                jz      L18             ; jump if only one byte per row

                push    bx

L17:            and     es:[di],dx      ; mask next 8 pixels in video
                                        ;  buffer
                lodsb                   ; AL := pixels in bit block
                xor     ah,ah
                ror     ax,cl           ; AX := pixels rotated into
                                        ;        position
                or      es:[di],ax      ; set pixels in video buffer
                inc     di              ; ES:DI -> next byte in bit block
                dec     bx
                jnz     L17

                pop     bx

L18:            mov     al,ch
                mov     ah,0FFh         ; AX := mask for last pixels in row
                ror     ax,cl           ; AX := mask rotated into position
                and     es:[di],ax      ; mask last pixels in video buffer
                lodsb                   ; AL := last byte in row
                xor     ah,ah
                ror     ax,cl           ; AX := pixels rotated into
                                        ;        position
                or      es:[di],ax      ; set pixels in video buffer

                pop     di
                add     di,VARincr      ; ES:DI -> next pixel row in buffer
                xor     VARincr,2000h XOR (80-2000h)
                dec     VARPixelRows
                jnz     L16             ; loop down pixel rows

                jmp     Lexit

XORBitBlock:
                mov     bx,VARPixelRowLen

L20:            push    di
                push    bx

L21:            lodsb                   ; AL := pixels in bit block
                xor     ah,ah
                ror     ax,cl           ; AX := pixels rotated into
                                        ;        position
                xor     es:[di],ax      ; XOR pixels into video buffer
                inc     di              ; ES:DI -> next byte in bit block
                dec     bx
                jnz     L21

                pop     bx
                pop     di
                add     di,VARincr      ; ES:DI -> next pixel row in buffer
                xor     VARincr,2000h XOR (80-2000h)
                dec     VARPixelRows
                jnz     L20             ; loop down pixel rows

                jmp     Lexit

ANDBitBlock:
                not     ch              ; CH := mask for end of row

                mov     bx,VARPixelRowLen
                dec     bx              ; BX := bytes per row - 1

L30:            push    di
                test    bx,bx
                jz      L32             ; jump if only one byte per row

                push    bx

L31:            lodsb                   ; AL := pixels in bit block
                mov     ah,0FFh
                ror     ax,cl           ; AX := pixels rotated into
                                        ;        position
                and     es:[di],ax      ; AND pixels into video buffer
                inc     di              ; ES:DI -> next byte in bit block
                dec     bx
                jnz     L31

                pop     bx

L32:            lodsb                   ; AL := last byte in row
                or      al,ch           ; mask last pixels in row
                mov     ah,0FFh
                ror     ax,cl           ; AX := pixels rotated into
                                        ;        position
                and     es:[di],ax      ; AND pixels into video buffer

                pop     di
                add     di,VARincr      ; ES:DI -> next pixel row in buffer
                xor     VARincr,2000h XOR (80-2000h)
                dec     VARPixelRows
                jnz     L30             ; loop down pixel rows

                jmp     Lexit


ORBitBlock:
                mov     bx,VARPixelRowLen

L40:            push    di
                push    bx

L41:            lodsb                   ; AL := pixels in bit block
                xor     ah,ah
                ror     ax,cl           ; AX := pixels rotated into
                                        ;        position
                or      es:[di],ax      ; OR pixels into video buffer
                inc     di              ; ES:DI -> next byte in bit block
                dec     bx
                jnz     L41

                pop     bx
                pop     di
                add     di,VARincr      ; ES:DI -> next pixel row in buffer
                xor     VARincr,2000h XOR (80-2000h)
                dec     VARPixelRows
                jnz     L40             ; loop down pixel rows


Lexit:          pop     di              ; restore registers and exit
                pop     si
                pop     ds
                mov     sp,bp
                pop     bp
                ret

_StoreBitBlock06 ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

StoreBitBlockOp DW      ReplaceBitBlock ; address of selected subroutine
                                        ;  (replace, XOR, AND, OR)

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 11-3.  A routine to copy a block of pixels from the EGA or VGA
               video buffer to system RAM in native graphics modes.
���������������������������������������������������������������������������

                TITLE   'Listing 11-3'
                NAME    GetBitBlock10
                PAGE    55,132

;
; Name:         GetBitBlock10
;
; Function:     Copy bit block from video buffer to system RAM
;                in native EGA and VGA graphics modes
;
; Caller:       Microsoft C:
;
;                       int GetBitBlock10(x0,y0,x1,y1,buf);
;
;                            int x0,y0;       /* upper left corner of */
;                                             /*  bit block */
;                            int x1,y1;       /* lower right corner */
;                            char far *buf;   /* buffer */
;
; Notes:        Returns size of bit block in system RAM.
;


ARGx0           EQU     word ptr [bp+4]
ARGy0           EQU     word ptr [bp+6]
ARGx1           EQU     word ptr [bp+8]
ARGy1           EQU     word ptr [bp+10]
ADDRbuf         EQU              [bp+12]

VARPixelRows    EQU     word ptr [bp-2]
VARPixelRowLen  EQU     word ptr [bp-4]

BytesPerRow     EQU     80
ByteOffsetShift EQU     3               ; reflects number of pixels per
                                        ;  byte

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _GetBitBlock10
_GetBitBlock10  PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,4            ; establish stack frame
                push    ds
                push    si
                push    di

; compute dimensions of bit block

                mov     ax,ARGx1
                sub     ax,ARGx0
                mov     cx,0FF07h       ; CH := unshifted bit mask
                                        ; CL := AND mask for AL
                and     cl,al           ; CL := number of pixels in last
                                        ;        byte of row
                xor     cl,7            ; CL := number of bits to shift
                shl     ch,cl           ; CH := bit mask for last byte of
                                        ;        row
                mov     cl,ch
                push    cx              ; save on stack

                mov     cl,ByteOffsetShift
                shr     ax,cl
                inc     ax              ; AX := number of bytes per row
                push    ax              ; save on stack

                mov     ax,ARGy1
                sub     ax,ARGy0
                inc     ax              ; AX := number of pixel rows
                push    ax              ; save on stack

; establish addressing

                mov     ax,ARGy0
                mov     bx,ARGx0
                call    PixelAddr10     ; ES:BX -> x0,y0 in video buffer
                xor     cl,7            ; CL := number of bits to shift
                                        ;        left
                push    es
                pop     ds
                mov     si,bx           ; DS:SI -> video buffer

                les     di,ADDRbuf      ; ES:DI -> buffer in system RAM

; build 5-byte bit block header

                pop     ax
                mov     VARPixelRows,ax
                stosw                   ; byte 0-1 := number of pixel rows
                pop     ax
                mov     VARPixelRowLen,ax
                stosw                   ; byte 2-3 := bytes per pixel row
                pop     ax
                mov     ch,al           ; CH := bit mask for last byte in
                                        ;        row
                stosb                   ; byte 4 := bit mask for last byte

; set up Graphics Controller

                mov     dx,3CEh         ; DX := Graphics Controller address
                                        ;        port

                mov     ax,0005         ; AH := 0 (read mode 0, write mode
                                        ;           0)
                                        ; AL := 5 (Mode register number)
                out     dx,ax           ; set up read mode 0

                mov     ax,0304h        ; AH := 3 (first bit plane to read)
                                        ; AL := 4 (Read Map Select reg
                                        ;           number)

; copy from video buffer to system RAM

L01:            out     dx,ax           ; select next memory map to read
                push    ax              ; preserve memory map number
                push    VARPixelRows    ; preserve number of pixel rows
                push    si              ; preserve offset of x0,y0

L02:            mov     bx,VARPixelRowLen
                push    si              ; preserve SI at start of pixel row

L03:            lodsw                   ; AL := next byte in video buffer
                                        ; AH := (next byte) + 1
                dec     si              ; DS:SI -> (next byte) + 1
                rol     ax,cl           ; AL := next 4 pixels in row
                stosb                   ; copy to system RAM
                dec     bx              ; loop across row
                jnz     L03

                and     es:[di-1],ch    ; mask last byte in row
                pop     si              ; DS:SI -> start of row
                add     si,BytesPerRow  ; DS:SI -> start of next row

                dec     VARPixelRows
                jnz     L02             ; loop down rows

                pop     si              ; DS:SI -> start of bit block
                pop     VARPixelRows    ; restore number of pixel rows
                pop     ax              ; AH := last map read
                                        ; AL := Read Map Select reg number
                dec     ah
                jns     L01             ; loop across bit planes

                mov     ax,di
                sub     ax,ADDRbuf      ; AX := return value (size of bit
                                        ;        block in system RAM)

                pop     di              ; restore registers and exit
                pop     si
                pop     ds
                mov     sp,bp
                pop     bp
                ret

_GetBitBlock10  ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 11-4.  A routine to copy a block of pixels from system RAM to
               the EGA or VGA video buffer in native graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 11-4'
                NAME    StoreBitBlock10
                PAGE    55,132

;
; Name:         StoreBitBlock10
;
; Function:     Copy bit block from video buffer to system RAM
;                in native EGA and VGA graphics modes
;
; Caller:       Microsoft C:
;
;                       void StoreBitBlock10(buf,x,y);
;
;                            char far *buf;   /* buffer */
;                            int x,y;         /* upper left corner of */
;                                             /*  bit block */
;


ADDRbuf         EQU     dword ptr [bp+4]
ARGx            EQU     word ptr [bp+8]
ARGy            EQU     word ptr [bp+10]

VARPixelRows    EQU     word ptr [bp-2]
VARPixelRowLen  EQU     word ptr [bp-4]
VARRowCounter   EQU     word ptr [bp-6]
VARStartMask    EQU     word ptr [bp-8]
VAREndMaskL     EQU     word ptr [bp-10]
VAREndMaskR     EQU     word ptr [bp-12]

BytesPerRow     EQU     80              ; logical width of video buffer
ByteOffsetShift EQU     3               ; reflects number of pixels per
                                        ;  byte
RMWbits         EQU     18h             ; selects replace, XOR, AND, or OR

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr10:near

                PUBLIC  _StoreBitBlock10
_StoreBitBlock10 PROC   near

                push    bp              ; preserve caller registers
                mov     bp,sp
                sub     sp,12           ; establish stack frame
                push    ds
                push    si
                push    di

; establish addressing

                mov     ax,ARGy
                mov     bx,ARGx
                call    PixelAddr10     ; ES:BX -> byte offset of x,y
                inc     cl
                and     cl,7            ; CL := number of bits to shift
                                        ;        left

                mov     di,bx           ; ES:DI -> x,y in video buffer

                lds     si,ADDRbuf      ; ES:DI -> buffer in system RAM

; obtain dimensions of bit block from header

                lodsw                   ; AX := number of pixel rows
                mov     VARPixelRows,ax
                lodsw                   ; AX := bytes per pixel row
                mov     VARPixelRowLen,ax
                lodsb                   ; AL := bit mask for last byte in
                                        ;        row
                mov     ch,al

; set up Graphics Controller

                mov     dx,3CEh         ; DX := Graphics Controller I/O
                                        ;        port

                mov     ah,RMWbits      ; AH := value for Data Rotate/
                mov     al,3            ;        Function Select register
                out     dx,ax           ; update this register

                mov     ax,0805h        ; AH := 8 (read mode 1, write mode
                                        ;           0)
                                        ; AL := 5 (Mode register number)
                out     dx,ax           ; set up read mode 0

                mov     ax,0007         ; AH := 0 (don't care for all maps;
                                        ;           CPU reads always return
                                                    0FFH)
                                        ; AL := 7 (Color Don't Care reg
                                        ;           number)
                out     dx,ax           ; set up Color Don't Care reg

                mov     ax,0FF08h       ; AH := 0FFH (value for Bit Mask
                                        ;              reg)
                out     dx,ax           ; set up Bit Mask reg

                mov     dl,0C4h         ; DX := 3C4H (Sequencer I/O port)
                mov     ax,0802h        ; AH := 1000B (value for Map Mask
                                        ;               reg)
                                        ; AL := 2 (Map Mask register
                                        ;           number)

                cmp     cx,0FF00h       ; if mask <> 0FFH or bits to shift
                jne     L15             ;  <> 0 jump if not byte-aligned

; routine for byte-aligned bit blocks

                mov     cx,VARPixelRowLen

L10:            out     dx,ax           ; enable one bit plane for writes
                push    ax              ; preserve Map Mask value
                push    di              ; preserve video buffer offset of
                                        ;  x,y
                mov     bx,VARPixelRows

L11:            push    di              ; preserve DI and CX
                push    cx

L12:            lodsb                   ; AL := next byte of pixels
                and     es:[di],al      ; update bit plane
                inc     di
                loop    L12
                pop     cx              ; restore DI and CX
                pop     di
                add     di,BytesPerRow  ; ES:DI -> next pixel row in buffer
                dec     bx
                jnz     L11             ; loop down pixel rows

                pop     di              ; ES:DI -> video buffer offset of
                                        ;  x,y
                pop     ax              ; AH := current Map Mask reg value
                shr     ah,1            ; AH := new Map Mask value
                jnz     L10             ; loop across all bit planes

                jmp     Lexit

; routine for non-aligned bit blocks

L15:            push    ax              ; preserve Map Mask reg values

                mov     bx,0FFh         ; BH := 0 (mask for first byte in
                                        ;           row)
                                        ; BL := 0FFh
                mov     al,ch           ; AL := mask for last byte in pixel
                                        ;        row
                cbw                     ; AH := 0FFh (mask for last-1 byte)

                cmp     VARPixelRowLen,1
                jne     L16             ; jump if more than one byte per
                                        ;  row

                mov     bl,ch
                mov     ah,ch           ; AH := mask for last-1 byte
                xor     al,al           ; AL := 0 (mask for last byte)

L16:            shl     ax,cl           ; shift masks into position
                shl     bx,cl

                mov     bl,al           ; save masks along with ...
                mov     al,8            ; Bit Mask register number
                mov     VAREndMaskL,ax
                mov     ah,bl
                mov     VAREndMaskR,ax
                mov     ah,bh
                mov     VARStartMask,ax

                mov     bx,VARPixelRowLen
                pop     ax              ; restore Map Mask reg values

; set pixels row by row in the bit planes

L17:            out     dx,ax           ; enable one bit plane for writes
                push    ax              ; preserve Map Mask value
                push    di              ; preserve video buffer offset of
                                        ;  x,y
                mov     dl,0CEh         ; DX := 3CEH (Graphics Controller
                                        ;              port)

                mov     ax,VARPixelRows
                mov     VARRowCounter,ax  ; initialize loop counter

; set pixels at start of row in currently enabled bit plane

L18:            push    di              ; preserve offset of start of pixel
                                        ;  row
                push    si              ; preserve offset of row in bit
                                        ;  block
                push    bx              ; preserve bytes per pixel row

                mov     ax,VARStartMask
                out     dx,ax           ; set Bit Mask reg for first byte
                                        ;  of row

                lodsw                   ; AH := 2nd byte of pixels
                                        ; AL := 1st byte of pixels
                dec     si              ; DS:SI -> 2nd byte of pixels
                test    cl,cl
                jnz     L19             ; jump if not left-aligned

                dec     bx              ; BX := bytes per row - 1
                jnz     L20             ; jump if at least 2 bytes per row
                jmp     short L22       ; jump if only one byte per row

L19:            rol     ax,cl           ; AH := left part of 1st byte,
                                        ;        right part of 2nd byte
                                        ; AL := right part of 1st byte,
                                        ;        left part of 2nd byte
                and     es:[di],ah      ; set pixels for left part of first
                                        ;  byte
                inc     di
                dec     bx              ; BX := bytes per row - 2

L20:            push    ax              ; preserve pixels
                mov     ax,0FF08h
                out     dx,ax           ; set Bit Mask reg for succeeding
                                        ;  bytes
                pop     ax

                dec     bx
                jng     L22             ; jump if only 1 or 2 bytes in
                                        ;  pixel row

; set pixels in middle of row

L21:            and     es:[di],al      ; set pixels in right part of
                inc     di              ;  current byte and left part of
                                        ;  next byte

                lodsw                   ; AH := next+1 byte of pixels
                dec     si              ; AL := next byte of pixels
                rol     ax,cl           ; AH := left part of next byte,
                                        ;        right part of next+1 byte
                                        ; AL := right part of next byte,
                                        ;        left part of next+1 byte
                dec     bx
                jnz     L21             ; loop across pixel row

; set pixels at end of row

L22:            mov     bx,ax           ; BH := right part of last byte,
                                        ;        left part of last-1 byte
                                        ; BL := left part of last byte,
                                        ;        right part of last-1 byte
                mov     ax,VAREndMaskL  ; AH := mask for last-1 byte
                                        ; AL := Bit Mask reg number

                out     dx,ax           ; set Bit Mask register
                and     es:[di],bl      ; set pixels for last-1 byte

                mov     ax,VAREndMaskR  ; mask for last byte in pixel row
                out     dx,ax           ; last byte in pixel row
                and     es:[di+1],bh    ; set pixels for last byte

                pop     bx              ; BX := bytes per pixel row
                pop     si
                add     si,bx           ; DS:SI -> next row in bit block
                pop     di
                add     di,BytesPerRow  ; ES:DI -> next pixel row in buffer
                dec     VARRowCounter
                jnz     L18             ; loop down pixel rows

                pop     di              ; ES:DI -> video buffer offset of
                                        ;  x,y
                pop     ax              ; AX := current Map Mask value
                mov     dl,0C4h         ; DX := 3C4H
                shr     ah,1            ; AH := next Map Mask value
                jnz     L17             ; loop across bit planes

; restore Graphics Controller and Sequencer to their default states

Lexit:          mov     ax,0F02h        ; default Map Mask value
                out     dx,ax

                mov     dl,0CEh         ; DX := 3CEh
                mov     ax,0003         ; default Data Rotate/Function
                                        ;  Select
                out     dx,ax

                mov     ax,0005         ; default Mode value
                out     dx,ax

                mov     ax,0F07h        ; default Color Compare value
                out     dx,ax

                mov     ax,0FF08h       ; default Bit Mask value
                out     dx,ax

                pop     di              ; restore registers and exit
                pop     si
                pop     ds
                mov     sp,bp
                pop     bp
                ret

_StoreBitBlock10 ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 11-5.  XORing a circle into the video buffer.
���������������������������������������������������������������������������

/* Listing 11-5 */

main()
{
        int     xc      = 400;          /* center of circle */
        int     yc      = 125;
        int     a,b;                    /* semimajor and semiminor axes */
        int     n = 12;                 /* pixel value */
        int     i;
        float   ScaleFactor = 1.37;     /* for 640x350 16-color mode */

        for( i=0; i<10; i++ )
          for( a=0; a<100; a++ )
          {
            b = (float) a / ScaleFactor;      /* scale semiminor axis */
            Ellipse( xc, yc, a, b, n );       /* draw a circle */
            Ellipse( xc, yc, a, b, n );       /* draw it again */
          }
}



���������������������������������������������������������������������������
Listing 11-6.  XORing a rectangle into the video buffer.
���������������������������������������������������������������������������

/* Listing 11-6 */

#define Xmax    640

main()
{
        int     x0      = 0;            /* corners of box at 0,0 and */
                                        /*  150,100 */
        int     y0      = 0;
        int     x1      = 150;
        int     y1      = 100;
        int     n = 12;                 /* pixel value */


        while( x1 < Xmax )                      /* slide box right */
          XORBox( x0++, y0, x1++, y1, n );

        while( x0 > 0 )                         /* slide box left */
          XORBox( --x0, y0, --x1, y1, n );
}


XORBox ( x0, y0, x1, y1, n )
int     x0,y0,x1,y1;            /* pixel coordinates of opposite corners */
int     n;                      /* pixel value */
{
        Rectangle( x0, y0, x1, y1, n );         /* draw the box */
        Rectangle( x0, y0, x1, y1, n );         /* erase the box */
}

Rectangle( x0, y0, x1, y1, n )
int     x0,y0,x1,y1;
int     n;
{
        Line( x0, y0, x0, y1, n );
        Line( x0, y0, x1, y0, n );
        Line( x1, y1, x0, y1, n );
        Line( x1, y1, x1, y0, n );
}



���������������������������������������������������������������������������
Listing 11-7.  XORing a line into the video buffer.
���������������������������������������������������������������������������

/* Listing 11-7 */

#define Xmax    640                     /* screen dimensions in */
                                        /*  640x350 mode */
#define Ymax    350

main()
{
        int     x0      = 150;          /* fixed endpoint at 150,100 */
        int     y0      = 100;
        int     x       = 0;            /* moving endpoint at 0,0 */
        int     y       = 0;
        int     n       = 12;           /* pixel value */


        for( ; x<Xmax; x++ )                    /* move right */
          XORLine( x0, y0, x, y, n );

        for( --x; y<Ymax; y++ )                 /* move down */
          XORLine( x0, y0, x, y, n );

        for( --y; x>=0; --x )                   /* move left */
          XORLine( x0, y0, x, y, n );

        for( x++; y>=0; --y )                   /* move up */
          XORLine( x0, y0, x, y, n );
}


XORLine ( x0, y0, x1, y1, n )
int     x0,y0,x1,y1;            /* endpoints */
int     n;                      /* pixel value */
{
        Line( x0, y0, x1, y1, n );            /* the line is onscreen */
        Line( x0, y0, x1, y1, n );            /* the line is erased */
}



���������������������������������������������������������������������������
Listing 11-8.  A program to move a block of pixels using the
               overlapping technique.
���������������������������������������������������������������������������

char far buf[(32/4)*21+5];      /* bit block buffer large enough */
                                /*  to contain a 32- by 21-pixel block */

Line(  1, 10, 21, 10, Fgd );    /* a right-pointing arrowhead */
Line( 21,  0, 31, 10, Fgd );    /*  in a 32- by 21-pixel bit block */
Line( 21, 20, 31, 10, Fgd );
Line( 21,  0, 21, 20, Fgd );

GetBitBlock( 0, 0, 31, 20, buf );       /* copy the bit block to */
                                        /*  system RAM */
for ( i = 0; i < 300; i++ )
  StoreBitBlock( buf, i, 0 );           /* slide rightward */



���������������������������������������������������������������������������
Listing 12-1.  Handling Vertical Interrupts on the EGA and VGA.
���������������������������������������������������������������������������

                TITLE   'Listing 12-1'
                NAME    VREGA
                PAGE    55,132

;
; Name:         VREGA
;
; Function:     Vertical Interrupt Service routine for EGA and VGA
;
; Caller:       Microsoft C:
;
;                       int EnableISR0A();      /* returns 0 if */
;                                               /*  installed ok */
;
;                       void DisableISR0A();
;

CRT_MODE        EQU     49h             ; addresses in video BIOS data area
ADDR_6845       EQU     63h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

ISR0A           PROC    far             ; Interrupt handler for INT 0Ah

                push    ax              ; preserve registers
                push    dx
                push    ds

                mov     ax,seg DGROUP
                mov     ds,ax           ; DS -> DGROUP

; determine whether a Vertical Interrupt has occurred

                mov     dx,3C2h         ; DX := I/O port for
                                        ;        Input Status Register Zero
                in      al,dx
                test    al,80h          ; test bit 7 of the Status Reg
                                        ;  value
                jnz     L10             ; jump if vertical interrupt

; not a Vertical Interrupt so chain to previous interrupt handler

                pushf                   ; simulate an INT
                call    ds:PrevISR0A    ;  to the previous INT 0Ah handler
                jmp     short Lexit

; handle a Vertical Interrupt

L10:            mov     dx,Port3x4      ; DX := 3B4h or 3D4h

                in      al,dx           ; AL := value of CRTC address reg
                push    ax              ; preserve this value

                mov     ax,DefaultVREnd ; AH := default value for VR End
                                        ;        reg
                                        ; AL := 11h (register number)
                and     ah,11101111b    ; AH bit 4 := 0 (clear interrupt
                                        ;                 latch)
                out     dx,ax           ; update VR End register
                jmp     $+2             ; wait for CRTC to respond

; send End of Interrupt to Intel 8259A Programmable Interrupt Controller
;  to allow subsequent IRQ2 interrupts to occur

                mov     al,20h          ; 8259A I/O port
                out     20h,al          ; send nonspecific EOI to 8259A
                jmp     $+2             ; wait for PIC to respond
                sti                     ; enable interrupts

; do something useful ...

                inc     word ptr _VRcount       ; increment a counter

; enable CRTC to generate another interrupt

                cli                     ; disable interrupts
                mov     ax,DefaultVREnd ; AH := default value for VR End
                                        ;        reg
                                        ; AL := 11h (register number)
                and     ah,11011111b    ; AH bit 5 := 0 (enable vertical
                                        ;                 int)
                or      ah,00010000b    ; AH bit 4 := 1 (enable int latch)
                out     dx,ax
                jmp     $+2

                pop     ax
                out     dx,al           ; restore previous Address reg
                                        ;  value

Lexit:          pop     ds              ; restore registers and exit
                pop     dx
                pop     ax
                iret


ISR0A           ENDP

;
; EnableISR0A -- enable Vertical Interrupt Handler
;
                PUBLIC  _EnableISR0A
_EnableISR0A    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si
                push    di

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area

; save default CRTC register values

                mov     dx,es:[ADDR_6845]  ; DX := CRTC Address port
                mov     Port3x4,dx      ; save port address

                mov     ax,1A00h        ; AH := 1AH (INT 10H function
                                        ;             number)
                                        ; AL := 0 (read Display
                                        ;           Combination)
                int     10h             ; AL := 1AH if function 1AH
                                        ;        supported
                                        ; BL := active video subsystem
                cmp     al,1Ah
                jne     L20             ; jump if not a VGA

                cmp     bl,7
                je      L21             ; jump if VGA

                cmp     bl,8
                je      L21             ; jump if VGA

                mov     ax,0FFFFh       ; return 0FFFFh if neither EGA nor
                                        ;  VGA
                jmp     short L23

; get default value for EGA Vertical Retrace End register

L20:            mov     al,es:[CRT_MODE]  ; AL := video BIOS mode number
                mov     bx,offset DGROUP:EGADefaultVals
                xlat                    ; AL := default value for VR End
                                        ;        reg
                jmp     short L22

; get default value for VGA Vertical Retrace End register

L21:            mov     al,VREndReg     ; AL := VR End register number
                out     dx,al
                inc     dx              ; DX := 3B5H or 3D5H
                in      al,dx           ; AL := current value for register

L22:            mov     VREndValue,al   ; save this value

; save old interrupt 0Ah vector

                mov     ax,350Ah        ; AH := 35H (INT 21h function
                                        ;             number)
                                        ; AL := 0AH (interrupt number)
                int     21h             ; ES:BX := previous INT 0AH vector

                mov     word ptr PrevISR0A,bx
                mov     word ptr PrevISR0A+2,es ; save previous vector

; update interrupt 0AH vector with address of this handler

                push    ds              ; preserve DS
                mov     dx,offset ISR0A
                push    cs
                pop     ds              ; DS:DX -> ISR0A
                mov     ax,250Ah        ; AH := 25H (INT 21H function
                                        ;             number)
                                        ; AL := 0AH (interrupt number)
                int     21h             ; update INT 0AH vector
                pop     ds              ; restore DS

; enable IRQ2 by zeroing bit 2 of the 8259A's mask register

                cli                     ; clear interrupts
                mov     dx,21h          ; DX := 8259A mask register
                in      al,dx           ; AL := mask register value
                and     al,11111011b    ; reset bit 2
                out     dx,al

; enable vertical interrupts

                mov     dx,Port3x4      ; DX := 3B4H or 3D4H
                mov     ax,DefaultVREnd

                and     ah,11001111b
                out     dx,ax           ; clear bits 4 and 5 of VR End reg
                jmp     $+2             ; wait for CRTC to respond
                or      ah,00010000b
                out     dx,ax           ; set bit 4
                jmp     $+2
                sti                     ; enable interrupts

                xor     ax,ax           ; AX := 0 (return value)

L23:            pop     di              ; restore registers and exit
                pop     si
                mov     sp,bp
                pop     bp
                ret

_EnableISR0A    ENDP

;
; DisableISR0A -- disable Vertical Interrupt Handler
;
                PUBLIC  _DisableISR0A
_DisableISR0A   PROC    near


                push    bp
                mov     bp,sp
                push    si
                push    di
                push    ds

; disable vertical interrupts

                cli                     ; disable interrupts
                mov     dx,Port3x4
                mov     ax,DefaultVREnd
                out     dx,ax           ; restore Vertical Retrace End reg
                jmp     $+2
                sti                     ; enable interrupts

; restore previous interrupt 0Ah handler

                lds     dx,PrevISR0A    ; DS:DX := previous INT 0AH vector
                mov     ax,250Ah        ; AH := 25H (INT 21H function
                                        ;             number)
                                        ; AL := 0AH (interrupt number)
                int     21h


                pop     ds              ; restore registers and exit
                pop     di
                pop     si
                mov     sp,bp
                pop     bp
                ret

_DisableISR0A   ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

                EXTRN   _VRcount:word   ; declared in C caller

PrevISR0A       DD      ?               ; save area for old int 0Ah vector
Port3x4         DW      ?               ; 3B4h or 3D4h

DefaultVREnd    LABEL   word
VREndReg        DB      11h             ; Vertical Retrace End register
                                           number
VREndValue      DB      ?               ; default value for VR End register

EGADefaultVals  DB      2Bh,2Bh,2Bh,2Bh,24h,24h,23h,2Eh ; default values
                DB      00h,00h,00h,00h,00h,24h,23h,2Eh ;  for EGA VR End
                                                        ;  reg
                DB      2Bh

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 12-2.  Using a Vertical Interrupt handler in a C program.
���������������������������������������������������������������������������

/* Listing 12-2 */

int     VRcount = 0;            /* vertical interrupt counter */

main()
{
        if ( EnableISR0A() )
        {
          printf( "\nCan't enable vertical interrupt handler\n" );
          exit( 1 );
        }

        while (VRcount < 600)
          printf( "\015Number of vertical interrupts:  %d", VRcount );

        DisableISR0A();
}



���������������������������������������������������������������������������
Listing 12-3.  Handling Vertical Interrupts on the MCGA.
���������������������������������������������������������������������������

                TITLE   'Listing 12-3'
                NAME    VRMCGA
                PAGE    55,132

;
; Name:         VRMCGA
;
; Function:     Vertical Interrupt Service routine for MCGA
;
; Caller:       Microsoft C:
;
;                       int EnableISR0A();      /* returns 0 if */
;                                               /*  installed ok */
;
;                       void DisableISR0A();
;

ADDR_6845       EQU     63h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

ISR0A           PROC    far             ; Interrupt handler for INT 0Ah

                push    ax              ; preserve registers
                push    dx
                push    ds

                mov     ax,seg DGROUP
                mov     ds,ax           ; DS -> DGROUP

                mov     dx,Port3x4      ; DX := CRTC Address reg number
                in      al,dx
                push    ax              ; preserve CRTC Address reg value

; determine whether a Vertical Interrupt has occurred

                mov     al,IContReg     ; AL := register number
                out     dx,al
                jmp     $+2             ; wait for MCGA to respond
                inc     dx              ; DX := 3D5H
                in      al,dx           ; AL := current Interrupt Control
                                        ;        register value
                dec     dx
                test    al,40h          ; test bit 6
                jnz     L10             ; jump if Vertical Interrupt

; not a Vertical Interrupt so chain to previous interrupt handler

                pushf                   ; simulate an INT to the
                call    ds:PrevISR0A    ;  previous INT 0Ah handler
                jmp     short Lexit

; handle a Vertical Interrupt

L10:            mov     ax,DefaultICont ; AH := default value for
                                        ;        Interrupt Control register
                                        ; AL := 11h (register number)
                and     ah,11101111b    ; AH bit 4 := 0 (clear interrupt
                                        ;                 latch)
                out     dx,ax           ; update Interrupt Control reg
                jmp     $+2             ; wait for MCGA to respond

; send End of Interrupt to Programmable Interrupt Controller
;  to allow subsequent IRQ2 interrupts to occur

                mov     al,20h          ; PIC I/O port
                out     20h,al          ; send nonspecific EOI to PIC
                jmp     $+2             ; wait for PIC to respond
                sti                     ; enable interrupts

; do something useful ...

                inc     word ptr _VRcount       ; increment a counter

; enable CRTC to generate another interrupt

                cli                     ; disable interrupts
                mov     ax,DefaultICont ; AH := default value for
                                        ;        Interrupt Control register
                                        ; AL := 11h (register number)
                and     ah,11011111b    ; AH bit 5 := 0 (enable Vert Int)
                or      ah,00010000b    ; AH bit 4 := 1 (enable int latch)
                out     dx,ax
                jmp     $+2

Lexit:          pop     ax
                out     dx,al           ; restore previous 3D4H value

                pop     ds              ; restore registers and exit
                pop     dx
                pop     ax
                iret

ISR0A           ENDP

;
; EnableISR0A -- enable Vertical Interrupt Handler
;
                PUBLIC  _EnableISR0A
_EnableISR0A    PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si
                push    di

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area

; save default CRTC register values

                mov     dx,es:[ADDR_6845]  ; DX := CRTC Address port
                mov     Port3x4,dx      ; save port address

                mov     ax,1A00h        ; AH := 1AH (INT 10H function
                                        ;             number)
                                        ; AL := 0 (read Display
                                        ;           Combination)
                int     10h             ; AL := 1AH if function 1AH
                                        ;        supported
                                        ; BL := active video subsystem
                cmp     al,1Ah
                jne     L20             ; jump if not an MCGA

                cmp     bl,0Bh
                je      L21             ; jump if MCGA

                cmp     bl,0Ch
                je      L21             ; jump if MCGA

L20:            mov     ax,0FFFFh       ; return 0FFFFh if not an MCGA
                jmp     short L23

; get default value for MCGA Interrupt Control register

L21:            mov     al,IContReg     ; AL := Interrupt Control reg
                                        ;        number
                cli
                out     dx,al
                jmp     $+2
                inc     dx              ; DX := 3D5H
                in      al,dx           ; AL := current value for register
                sti

                mov     IContValue,al   ; save this value

; save old interrupt 0Ah vector

                mov     ax,350Ah        ; AH := 35H (INT 21h function
                                        ;             number)
                                        ; AL := 0AH (interrupt number)
                int     21h             ; ES:BX := previous INT 0AH vector

                mov     word ptr PrevISR0A,bx
                mov     word ptr PrevISR0A+2,es ; save previous vector

; update interrupt 0AH vector with address of this handler

                push    ds              ; preserve DS
                mov     dx,offset ISR0A
                push    cs
                pop     ds              ; DS:DX -> ISR0A
                mov     ax,250Ah        ; AH := 25H (INT 21H function
                                        ;             number)
                                        ; AL := 0AH (interrupt number)
                int     21h             ; update INT 0AH vector
                pop     ds              ; restore DS

; enable IRQ2 by zeroing bit 2 of the PIC's mask register

                cli                     ; clear interrupts
                mov     dx,21h          ; DX := PIC mask register
                in      al,dx           ; AL := mask register value
                and     al,11111011b    ; reset bit 2
                out     dx,al

; enable Vertical Interrupts

                mov     dx,Port3x4      ; DX := CRTC Address port
                mov     ax,DefaultICont

                and     ah,11001111b
                out     dx,ax           ; clear bits 4 and 5 of Int Control
                                        ;  reg
                jmp     $+2             ; wait for MCGA to respond
                or      ah,00010000b
                out     dx,ax           ; set bit 4
                jmp     $+2
                sti                     ; enable interrupts

                xor     ax,ax           ; AX := 0 (return value)

L23:            pop     di              ; restore registers and exit
                pop     si
                mov     sp,bp
                pop     bp
                ret

_EnableISR0A    ENDP

;
; DisableISR0A -- disable Vertical Interrupt handler
;
                PUBLIC  _DisableISR0A
_DisableISR0A   PROC    near

                push    bp
                mov     bp,sp
                push    si
                push    di
                push    ds

; disable Vertical Interrupts

                cli                     ; disable interrupts
                mov     dx,Port3x4
                mov     ax,DefaultICont
                out     dx,ax           ; restore Interrupt Control
                                        ;  register
                jmp     $+2
                sti                     ; enable interrupts

; restore previous interrupt 0Ah handler

                lds     dx,PrevISR0A    ; DS:DX := previous INT 0AH vector
                mov     ax,250Ah        ; AH := 25H (INT 21H function
                                        ;             number)
                                        ; AL := 0AH (interrupt number)
                int     21h

                pop     ds              ; restore registers and exit
                pop     di
                pop     si
                mov     sp,bp
                pop     bp
                ret

_DisableISR0A   ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

                EXTRN   _VRcount:word   ; declared in C caller

PrevISR0A       DD      ?               ; save area for old int 0Ah vector
Port3x4         DW      ?               ; 3B4h or 3D4h

DefaultICont    LABEL   word
IContReg        DB      11h             ; Interrupt Control register number
IContValue      DB      ?               ; default value for Int Control reg

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 12-4.  Setting the screen origin on the EGA and VGA.
���������������������������������������������������������������������������

                TITLE   'Listing 12-4'
                NAME    ScreenOrigin
                PAGE    55,132

;
; Name:         ScreenOrigin
;
; Function:     Set screen origin on EGA and VGA
;
; Caller:       Microsoft C:
;
;                       void ScreenOrigin(x,y);
;
;                               int     x,y;    /* pixel x,y coordinates */
;

ARGx            EQU     [bp+4]
ARGy            EQU     [bp+6]


CRT_MODE        EQU     49h             ; addresses in video BIOS data area
ADDR_6845       EQU     63h
POINTS          EQU     85h
BIOS_FLAGS      EQU     89h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _ScreenOrigin

_ScreenOrigin   PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si
                push    di

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area
                mov     cl,es:[CRT_MODE]

                mov     ax,ARGx         ; AX := pixel x-coordinate
                mov     bx,ARGy         ; BX := pixel y-coordinate

                cmp     cl,7
                ja      L01             ; jump if graphics mode

                je      L02             ; jump if monochrome alpha
                test    byte ptr es:[BIOS_FLAGS],1
                jnz     L02             ; jump if VGA
                jmp     short L03

; setup for graphics modes (8 pixels per byte)

L01:            mov     cx,8            ; CL := 8 (displayed pixels per
                                        ;           byte)
                                        ; CH := 0
                div     cl              ; AH := bit offset in byte
                                        ; AL := byte offset in pixel row
                mov     cl,ah           ; CL := bit offset (for Horiz Pel
                                        ;                    Pan)
                xor     ah,ah
                xchg    ax,bx           ; AX := y
                                        ; BX := byte offset in pixel row

                mul     word ptr _BytesPerRow
                                        ; AX := byte offset of start of row
                jmp     short L05

; setup for VGA alphanumeric modes and EGA monochrome alphanumeric mode
;  (9 pixels per byte)

L02:                                    ; routine for alpha modes
                mov     cx,9            ; CL := 9 (displayed pixels per
                                        ;           byte)
                                        ; CH := 0
                div     cl              ; AH := bit offset in byte
                                        ; AL := byte offset in pixel row
                dec     ah              ; AH := -1, 0-7
                jns     L04             ; jump if bit offset 0-7
                mov     ah,8            ; AH := 8
                jmp     short L04
; setup for EGA color alphanumeric modes (8 pixels per byte)

L03:            mov     cx,8            ; CL := 8 (displayed pixels per
                                        ;           byte)
                                        ; CH := 0
                div     cl              ; AH := bit offset in byte
                                        ; AL := byte offset in pixel row

L04:            mov     cl,ah           ; CL := value for Horiz Pel Pan reg
                xor     ah,ah
                xchg    ax,bx           ; AX := y
                                        ; BX := byte offset in row
                div     byte ptr es:[POINTS]  ; AL := character row
                                              ; AH := scan line in char
                                              ;        matrix
                xchg    ah,ch           ; AX := character row
                                        ; CH := scan line (value for Preset
                                        ;                   Row Scan
                                        ;                   register)
                mul     word ptr _BytesPerRow   ; AX := byte offset of char
                                                         row
                shr     ax,1            ; AX := word offset of character
                                                 row

L05:            call    SetOrigin

                pop     di              ; restore registers and exit
                pop     si
                mov     sp,bp
                pop     bp
                ret

_ScreenOrigin   ENDP


SetOrigin       PROC    near            ; Caller:  AX = offset of character
                                        ;                row
                                        ;          BX = byte offset within
                                        ;                row
                                        ;          CH = Preset Row Scan
                                        ;                value
                                        ;          CL = Horizontal Pel Pan
                                        ;                value

                add     bx,ax           ; BX := buffer offset

                mov     dx,es:[ADDR_6845]  ; CRTC I/O port (3B4H or 3D4H)
                add     dl,6            ; video status port (3BAH or 3DAH)

; update Start Address High and Low registers

L20:            in      al,dx           ; wait for start of vertical
                                        ;  retrace
                test    al,8
                jz      L20

L21:            in      al,dx           ; wait for end of vertical retrace
                test    al,8
                jnz     L21

                cli                     ; disable interrupts
                sub     dl,6            ; DX := 3B4H or 3D4H

                mov     ah,bh           ; AH := value for Start Address
                                        ;        High
                mov     al,0Ch          ; AL := Start Address High reg
                                        ;        number
                out     dx,ax           ; update this register

                mov     ah,bl           ; AH := value for Start Address Low
                inc     al              ; AL := Start Address Low reg
                                        ;        number
                out     dx,ax           ; update this register
                sti                     ; enable interrupts

                add     dl,6            ; DX := video status port

L22:            in      al,dx           ; wait for start of vertical
                                        ;  retrace
                test    al,8
                jz      L22

                cli                     ; disable interrupts

                sub     dl,6            ; DX := 3B4H or 3D4H
                mov     ah,ch           ; AH := value for Preset Row Scan
                                        ;        reg
                mov     al,8            ; AL := Preset Row Scan reg number
                out     dx,ax           ; update this register

                mov     dl,0C0h         ; DX := 3C0h (Attribute Controller
                                        ;              port)
                mov     al,13h OR 20h   ; AL bit 0-4 := Horiz Pel Pan reg
                                        ;                number
                                        ; AL bit 5 := 1
                out     dx,al           ; write Attribute Controller
                                        ;  Address reg
                                        ;   (The Attribute Controller
                                        ;    address flip-flop.)
                mov     al,cl           ; AL := value for Horiz Pel Pan reg
                out     dx,al           ; update this register

                sti                     ; reenable interrupts
                ret

SetOrigin       ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

                EXTRN   _BytesPerRow:word       ; bytes per pixel row

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing 12-5.  A routine to perform smooth pixel-by-pixel panning on
               an EGA or VGA.
���������������������������������������������������������������������������

Pan( x0, y0, x1, y1 )
int     x0,y0;          /* starting pixel coordinates */
int     x1,y1;          /* ending pixel coordinates */
{
        int     i = x0;
        int     j = y0;
        int     Xinc,Yinc;      /* horizontal and vertical increments */


        if ( x0 < x1 )          /* compute signs of increments */
          Xinc = 1;
        else
          Xinc = -1;

        if ( y0 < y1 )
          Yinc = 1;
        else
          Yinc = -1;

        while ( (i != x1) || (j != y1) )
        {
          if (i != x1)                  /* compute next screen origin */
            i += Xinc;
          if (j != y1)
            j += Yinc;

          ScreenOrigin( i, j );         /* move screen origin */
        }
}



���������������������������������������������������������������������������
Listing 12-6a.  Redimensioning the video buffer.
���������������������������������������������������������������������������

                TITLE   'Listing 12-6a'
                NAME    BufferDims
                PAGE    55,132

;
; Name:         BufferDims
;
; Function:     Set video buffer dimensions on EGA
;
; Caller:       Microsoft C:
;
;                       void BufferDims(x,y);
;
;                             int     x,y;    /* horizontal and vertical */
;                                             /*  dimensions in pixels */
;

ARGx            EQU     word ptr [bp+4]
ARGy            EQU     word ptr [bp+6]


CRT_MODE        EQU     49h             ; addresses in video BIOS data area
CRT_COLS        EQU     4Ah
CRT_LEN         EQU     4Ch
ADDR_6845       EQU     63h
ROWS            EQU     84h
POINTS          EQU     85h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _BufferDims
_BufferDims     PROC    near

                push    bp              ; preserve BP
                mov     bp,sp

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area

; determine width of displayed character matrix (8 or 9 pixels)

                mov     bx,8            ; BX := 8 pixels wide
                cmp     byte ptr es:[CRT_MODE],7  ; check BIOS mode number
                jne     L01             ; jump if not monochrome

                inc     bx              ; BX := 9 pixels wide

; update video BIOS data area

L01:            mov     ax,ARGx         ; AX := number of pixels per row
                div     bl              ; AL := number of character columns
                mov     es:[CRT_COLS],al
                mov     bh,al           ; BH := number of character columns

                mov     ax,ARGy         ; DX:AX := number of pixel rows
                div     byte ptr es:[POINTS]  ; AL := number of character
                                              ;        rows
                dec     al
                mov     es:[ROWS],al

                inc     al
                mul     bh              ; AX := character rows * character
                                        ;        cols
                mov     es:[CRT_LEN],ax ; update video BIOS data area

; update CRTC Offset register

                mov     ah,bh
                shr     ah,1            ; AH := number of words per row
                mov     al,13h          ; AL := CRTC Offset register number
                mov     dx,es:[ADDR_6845]  ; DX := 3B4H or 3D4H
                out     dx,ax

                pop     bp              ; restore BP and exit
                ret

_BufferDims     ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 12-6b.  Creating a 160-by-102 alphanumeric mode.
���������������������������������������������������������������������������

/* Listing 12-6b */

#define CharColumns     160             /* desired character dimensions */
#define CharRows        102

#define CharacterWidth  8               /* 8 for EGA color modes */
                                        /* 9 for EGA monochrome or VGA */

int     BytesPerRow = CharColumns * 2;  /* for 80-column alphanumeric */
                                        /*  modes */

main()
{
        int     i;
        int far *POINTS = 0x00400085;   /* (in video display data area) */

        BufferDims( CharColumns * CharacterWidth, CharRows * (*POINTS) );

        for( i = 0; i < CharColumns / 10; i++ ) /* display a long line */
          printf("0123456789");

        Pan( 0, 0, 80 * CharacterWidth, 0 );    /* pan right */
        Pan( 80 * CharacterWidth, 0, 0, 0 );    /* pan left */
        Pan( 0, 0, 0, 50 * (*POINTS) );         /* pan down */
        Pan( 0, 50 * (*POINTS), 0, 0);          /* pan up */
}



���������������������������������������������������������������������������
Listing 12-7a.  Splitting the screen on the EGA.
���������������������������������������������������������������������������

                TITLE   'Listing 12-7a'
                NAME    SplitScreen
                PAGE    55,132

;
; Name:         SplitScreen
;
; Function:     Horizontal split screen on EGA
;
; Caller:       Microsoft C:
;
;                       void SplitScreen(n);
;
;                               int     n;      /* scan line at which */
;                                               /*  to split screen */
;

ARGn            EQU     word ptr [bp+4]


ADDR_6845       EQU     63h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _SplitScreen
_SplitScreen    PROC    near

                push    bp              ; preserve BP
                mov     bp,sp

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area
                mov     dx,es:[ADDR_6845]  ; DX := CRTC address port

; wait for vertical retrace

                add     dl,6            ; DX := 3BAH or 3DAH (CRT status
                                        ;                      port)
L01:            in      al,dx           ; wait for end of vertical retrace
                test    al,8
                jnz     L01

L02:            in      al,dx           ; wait for start of vertical
                                        ;  retrace
                test    al,8
                jz      L02
                sub     dl,6            ; DX := CRTC address port

; isolate bits 0-7 and bit 8 of the Line Compare value

                mov     ax,ARGn         ; AX := scan line value
                mov     bh,ah
                and     bh,1            ; BH bit 0 := Line Compare bit 8
                mov     cl,4
                shl     bh,cl           ; BH bit 4 := Line Compare bit 8

; program the CRTC registers

                mov     ah,al           ; AH := low-order 8 bits of value
                mov     al,18h          ; AL := Line Compare register
                                        ;        number
                out     dx,ax           ; update Line Compare register

                mov     ah,1Fh          ; default value for EGA 350-line
                                        ;  modes (use 11h in EGA 200-line
                                        ;          modes)
                and     ah,11101111b    ; AH bit 4 := 0
                or      ah,bh           ; AH bit 4 := Line Compare bit 8
                mov     al,7            ; AL := Overflow register number
                out     dx,ax           ; update Overflow register

                pop     bp              ; restore BP and exit
                ret

_SplitScreen    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 12-7b.  Splitting the screen on the VGA.
���������������������������������������������������������������������������

                TITLE   'Listing 12-7b'
                NAME    SplitScreen
                PAGE    55,132

;
; Name:         SplitScreen
;
; Function:     Horizontal split screen on VGA
;
; Caller:       Microsoft C:
;
;                       void SplitScreen(n);
;
;                               int     n;      /* scan line at which */
;                                               /*  to split screen */
;

ARGn            EQU     word ptr [bp+4]


ADDR_6845       EQU     63h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _SplitScreen

_SplitScreen    PROC    near

                push    bp              ; preserve BP
                mov     bp,sp

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area
                mov     dx,es:[ADDR_6845]  ; DX := CRTC address port

; wait for vertical retrace

                add     dl,6            ; DX := 3BAH or 3DAH (CRT status
                                        ;                      port)
L01:            in      al,dx           ; wait for end of vertical retrace
                test    al,8
                jnz     L01

L02:            in      al,dx           ; wait for start of vertical
                                        ;  retrace
                test    al,8
                jz      L02
                sub     dl,6            ; DX := CRTC address port

; isolate bits 0-7, bit 8, and bit 9 of the Line Compare value

                mov     ax,ARGn         ; AX := scan line value
                mov     bh,ah           ; BH bits 0-1 := bits 8-9 of
                                        ;  Line Compare value

                mov     bl,bh
                and     bx,0201h        ; BH bit 1 := Line Compare bit 9
                                        ; BL bit 0 := Line Compare bit 0
                mov     cl,4
                shl     bx,cl           ; BH bit 5 := Line Compare bit 9
                                        ; BL bit 4 := Line Compare bit 8
                shl     bh,1            ; BH bit 6 := Line Compare bit 9

; update the CRTC registers

                mov     ah,al           ; AH := low-order 8 bits of value
                mov     al,18h          ; AL := Line Compare register
                                        ;        number
                out     dx,ax           ; update Line Compare register

                mov     al,7            ; AL := Overflow register number
                out     dx,al
                inc     dx
                in      al,dx           ; AL := current Overflow reg value
                dec     dx

                mov     ah,al
                and     ah,11101111b    ; AH bit 4 := 0
                or      ah,bl           ; AH bit 4 := Line Compare bit 8
                mov     al,7            ; AL := Overflow register number
                out     dx,ax           ; update Overflow register

                mov     al,9            ; AL := Max Scan Line register
                                        ;        number
                out     dx,al
                inc     dx
                in      al,dx           ; AL := current Max Scan Line reg
                                        ;        value
                dec     dx

                mov     ah,al
                and     ah,10111111b    ; AH bit 6 := 0
                or      ah,bh           ; AH bit 6 := Line Compare bit 9
                mov     al,9            ; AL := Max Scan Line reg number
                out     dx,ax           ; update Max Scan Line register

                pop     bp              ; restore BP and exit
                ret

_SplitScreen    ENDP

_TEXT           ENDS
                END



���������������������������������������������������������������������������
Listing 12-8.  Smooth vertical scrolling of a split screen on
               the EGA or VGA.
���������������������������������������������������������������������������

#define MaxScanLine     349             /* (depends on video mode) */

for ( i = MaxScanLine; i >= 0; --i )    /* scroll up */
  SplitScreen( i );

for ( i = 0; i < MaxScanLine; i++ )     /* scroll down */
  SplitScreen( i );

SplitScreen( 0x3FF );                   /* restore default value */



���������������������������������������������������������������������������
Listing 12-9a.  Getting the light pen's location from the CRTC.
���������������������������������������������������������������������������

                TITLE   'Listing 12-9a'
                NAME    GetLightPen
                PAGE    55,132

;
; Name:         GetLightPen
;
; Function:     Get light pen position
;
; Caller:       Microsoft C:
;
;                       int GetLightPen();      /* returns buffer offset */
;

ADDR_6845       EQU     63h

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _GetLightPen
_GetLightPen    PROC    near

                push    bp
                mov     bp,sp

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area

                mov     dx,es:[ADDR_6845]  ; DX := 3B4H or 3D4H

                add     dl,7            ; DX := 3BBH or 3DBH
                xor     al,al           ; AL := 0
                out     dx,al           ; reset CRTC light pen latch
                jmp     $+2             ; ensure that CRTC has time to
                                        ;  respond

                dec     dx              ; DX := 3BAH or 3DAH

L01:            in      al,dx           ; wait for light pen to be
                                        ;  triggered
                test    al,2
                jz      L01

                cli                     ; disable interrupts
                sub     dl,6            ; DX := 3B4H or 3D4H
                mov     al,10h          ; AL := Light Pen High register
                                        ;        number
                out     dx,al
                inc     dx
                in      al,dx
                mov     ah,al           ; AH := Light Pen High value
                dec     dx

                mov     al,11h          ; AL := Light Pen Low register
                                        ;        number
                out     dx,al
                inc     dx
                in      al,dx           ; AX := offset at which light pen
                                        ;        was triggered
                sti                     ; reenable interrupts

                pop     bp
                ret

_GetLightPen    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 12-9b.  Using GetLightPen in a C program.
���������������������������������������������������������������������������

/* Listing 12-9b */

main()
{
      int     BufferOffset,Row,Column;
      int far *CRT_START = 0x0040004E;
      char far *CRT_COLS = 0x0040004A;

      printf( "\nCRT_COLS = %d", (int) (*CRT_COLS) );

      for( ; ; )
      {
        BufferOffset = GetLightPen();

        printf( "\nLight pen offset:  %4xh", BufferOffset );

        BufferOffset = BufferOffset - 7;         /* empirical correction */

        BufferOffset = BufferOffset - (*CRT_START)/2; /* offset relative */
                                                      /*  to start of */
                                                      /*  screen */

        Row = BufferOffset / (int)(*CRT_COLS);        /* character row */
        Column = BufferOffset % (int)(*CRT_COLS);     /* character /*
                                                      /*  column */

        printf( "  Column = %d  Row = %d", Column, Row );
      }
}



���������������������������������������������������������������������������
Listing 12-10.  Identifying the current video mode on a Hercules adapter.
���������������������������������������������������������������������������

                TITLE   'Listing 12-10'
                NAME    GetHercMode
                PAGE    55,132

; Name:         GetHercMode
;
; Function:     Determine video mode on Hercules adapters by estimating the
;                size of the displayed portion of the video buffer.
;
; Caller:       Microsoft C:
;
;                       int GetHercMode(n);     /* returns approximate */
;                                               /*  size of displayed  */
;                                               /*  portion of video   */
                                                /*  buffer in words    */

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                PUBLIC  _GetHercMode
_GetHercMode    PROC    near

                push    bp              ; preserve BP
                mov     bp,sp

; reset CRTC light pen latch

                mov     dx,3BBh         ; DX := light pen reset port
                out     dx,al           ; OUT to this port clears the latch
                                        ;  (the value in AL doesn't matter)

; wait for start of next vertical retrace

                dec     dx              ; DX := 3BAH (CRT status port)
L01:            in      al,dx           ; wait for start of vertical
                                        ;  retrace
                test    al,80h
                jnz     L01

L02:            in      al,dx           ; wait for end of vertical retrace
                test    al,80h
                jz      L02

                cli                     ; disable interrupts
L03:            in      al,dx           ; wait for start of vertical
                                        ;  retrace
                test    al,80h
                jnz     L03

; latch the current CRTC address counter in the Light Pen registers

                dec     dx              ; DX := 3B9H
                out     dx,al           ; OUT to this port loads the latch
                sti                     ; reenable interrupts

; return the value in the Light Pen registers

                mov     dl,0B4h         ; DX := 3B4H (CRTC address port)
                mov     al,10h          ; AL := Light Pen High register
                                        ;        number
                out     dx,al
                inc     dx
                in      al,dx           ; read this register
                dec     dx
                mov     ah,al           ; AH := current Light Pen High
                                        ;        value

                mov     al,11h          ; AL := Light Pen Low register
                                        ;        number
                out     dx,al
                inc     dx
                in      al,dx           ; AX := current light pen latch
                                        ;        value
                                        ;  (i.e., value of CRTC address
                                        ;   counter at start of vertical
                                        ;   retrace)
                pop     bp
                ret

_GetHercMode    ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 13-1a.  The SetPixel subroutine (Microsoft C small-model
                calling conventions).
���������������������������������������������������������������������������

                TITLE   'Listing 13-1a'
                NAME    SetPixel
                PAGE    55,132

;
; Name:         SetPixel
;
; Function:     Set the value of a pixel in native EGA graphics modes.
;
; Caller:       Microsoft C (small memory model):
;
;                       void SetPixel(x,y,n);
;
;                       int x,y;                /* pixel coordinates */
;                       int n;                  /* pixel value */
;
; Notes:        This is the same routine as in Chapter 5.
;

ARGx            EQU     word ptr [bp+4] ; stack frame addressing
ARGy            EQU     word ptr [bp+6]
ARGn            EQU     byte ptr [bp+8]

RMWbits         EQU     0               ; read-modify-write bits


_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr:near

                PUBLIC  _SetPixel
_SetPixel       PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp

                mov     ax,ARGy         ; AX := y
                mov     bx,ARGx         ; BX := x
                call    PixelAddr       ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

; set Graphics Controller Bit Mask register

                shl     ah,cl           ; AH := bit mask in proper position
                mov     dx,3CEh         ; GC address register port
                mov     al,8            ; AL := Bit Mask register number
                out     dx,ax

; set Graphics Controller Mode register

                mov     ax,0005h        ; AL :=  Mode register number
                                        ; AH :=  Write Mode 0 (bits 0,1)
                                        ;        Read Mode 0 (bit 3)
                out     dx,ax

; set Data Rotate/Function Select register

                mov     ah,RMWbits      ; AH := Read-Modify-Write bits
                mov     al,3            ; AL := Data Rotate/Function Select
                                        ;        reg
                out     dx,ax

; set Set/Reset and Enable Set/Reset registers

                mov     ah,ARGn         ; AH := pixel value
                mov     al,0            ; AL := Set/Reset reg number
                out     dx,ax

                mov     ax,0F01h        ; AH := value for Enable Set/Reset
                                        ;  (all bit planes enabled)
                                        ; AL := Enable Set/Reset reg number
                out     dx,ax

; set the pixel value

                or      es:[bx],al      ; load latches during CPU read
                                        ; update latches and bit planes
                                        ;  during CPU write

; restore default Graphics Controller registers

                mov     ax,0FF08h       ; default Bit Mask
                out     dx,ax

                mov     ax,0005         ; default Mode register
                out     dx,ax

                mov     ax,0003         ; default Function Select
                out     dx,ax

                mov     ax,0001         ; default Enable Set/Reset
                out     dx,ax

                mov     sp,bp           ; restore caller registers and
                                        ;  return
                pop     bp
                ret

_SetPixel       ENDP

_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 13-1b.  Calling SetPixel() from a C program.
���������������������������������������������������������������������������

/* Listing 13-1b */

/* draws an n-leaved rose of the form  rho = a * cos(n * theta) */

#define Leaves          (double)11      /* n must be an odd number */

#define Xmax            640
#define Ymax            350
#define PixelValue      14
#define ScaleFactor     (double) 1.37

main()
{
        int     x,y;                    /* pixel coordinates */
        double  a;                      /* length of the semi-axis */

        double  rho,theta;              /* polar coordinates */
        double  pi = 3.14159265358979;
        double  sin(),cos();

        void SetPixel();


        a = (Ymax/2) - 1;             /* a reasonable choice for a */

        for (theta=0.0; theta < pi; theta+=0.001)
        {
          rho = a * cos( Leaves * theta );      /* apply the formula */

          x = rho * cos( theta );       /* convert to rectangular coords */
          y = rho * sin( theta ) / ScaleFactor;

                                        /* plot the point */
          SetPixel( x + Xmax/2, y + Ymax/2, PixelValue );
        }
}



���������������������������������������������������������������������������
Listing 13-2a.  The SETPEL subroutine (Microsoft FORTRAN calling
                conventions).
���������������������������������������������������������������������������

                TITLE   'Listing 13-2a'
                NAME    SETPEL
                PAGE    55,132

;
; Name:         SETPEL
;
; Function:     Set the value of a pixel in native EGA graphics modes.
;
; Caller:       Microsoft Fortran
;
;                       integer*2       x,y,n
;                       call SETPEL(x,y,n)
;

ADDRx           EQU     dword ptr [bp+14]       ; x, y, and n are
ADDRy           EQU     dword ptr [bp+10]       ;  referenced by 32-bit
                                                ;  addresses
ADDRn           EQU     dword ptr [bp+6]

RMWbits         EQU     0


SETPEL_TEXT     SEGMENT byte public 'CODE'
                ASSUME  cs:SETPEL_TEXT

                EXTRN   PixelAddr:far

                PUBLIC  SETPEL
SETPEL          PROC    far

                push    bp
                mov     bp,sp

; get parameters via 32-bit addresses on stack

                les     bx,ADDRn                ; ES:BX -> n
                push    es:[bx]                 ; preserve n on stack

                les     bx,ADDRy
                mov     ax,es:[bx]              ; AX := y

                les     bx,ADDRx
                mov     bx,es:[bx]              ; BX := x

                call    PixelAddr               ; compute pixel address

                shl     ah,cl

; program the Graphics Controller

                mov     dx,3CEh
                mov     al,8
                out     dx,ax

                mov     ax,0005h
                out     dx,ax

                mov     ah,RMWbits
                mov     al,3
                out     dx,ax

                pop     ax                      ; AX := n
                mov     ah,al                   ; AH := n
                mov     al,0
                out     dx,ax

                mov     ax,0F01h
                out     dx,ax

; update the pixel, restore the default Graphics Controller state, and
;  return

                or      es:[bx],al              ; update the pixel

                mov     ax,0FF08h               ; restore default Graphics
                out     dx,ax                   ;  Controller values

                mov     ax,0005
                out     dx,ax

                mov     ax,0003
                out     dx,ax

                mov     ax,0001
                out     dx,ax

                mov     sp,bp                   ; restore registers and
                                                ;  exit
                pop     bp
                ret     12                      ; discard caller's parms

SETPEL          ENDP

SETPEL_TEXT     ENDS
                END



���������������������������������������������������������������������������
Listing 13-2b.  Calling SETPEL() from a FORTRAN program.
���������������������������������������������������������������������������

c Listing 13-2b
c
c draws an n-leaved rose of the form  rho = a * cos(n*theta)

        real*8          Leaves /11/
        real*8          ScaleFactor /1.37/

        integer*2       Xmax /640/, Ymax /350/, PixelValue /14/

        integer*2       x,y
        real*8          a
        real*8          rho,theta
        real*8          pi /3.14159265358979/
        real*8          sin,cos

        a = (Ymax/2) - 1

        do 100 theta = 0.0, pi, 0.001

        rho = a * cos( Leaves * theta )

        x = rho * cos( theta )
        y = rho * sin( theta ) / ScaleFactor

100     call SETPEL( x + Xmax/2, y + Ymax/2, PixelValue )

        stop
        end



���������������������������������������������������������������������������
Listing 13-3a.  The SETPEL subroutine (Turbo Pascal calling conventions).
���������������������������������������������������������������������������

                TITLE   'Listing 13-3a'
                NAME    SETPEL
                PAGE    55,132

;
; Name:         SETPEL
;
; Function:     Set the value of a pixel in 320x200 4-color mode
;
; Caller:       Turbo Pascal
;
;               PROCEDURE SETPEL(VAR x,y:INTEGER; PixelValue:INTEGER);
;                EXTERNAL 'setpel.bin';
;
; Notes:        The code segment is named _TEXT so that PixelAddr may be
;               linked in the same segment.
;

ADDRx           EQU     dword ptr [bp+10]       ; x and y are VAR so their
ADDRy           EQU     dword ptr [bp+6]        ;  addresses are passed
ARGn            EQU     byte ptr [bp+4]         ; n's value is passed on
                                                ;  the stack

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr:near

SETPEL          PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    ds

; make this routine addressable through SI

                call    L01             ; push offset of L01

L01:            pop     si              ; CS:SI -> L01
                sub     si,offset L01

; get parameters via 32-bit addresses on stack

                lds     di,ADDRy        ; DS:DI -> y
                mov     ax,[di]         ; AX := y

                lds     di,ADDRx
                mov     bx,[di]         ; BX := x

                call    PixelAddr       ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     al,ARGn         ; AL := pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position

; jump through the variable SetPixelOp to the appropriate routine

                mov     di,cs:SetPixelOp[si]    ; DI := address
                add     di,si           ; DI := relocated address
                jmp     di
                                        ; routine to replace pixel value

ReplacePixel:   not     ah              ; AH := inverse bit mask
                and     es:[bx],ah      ; zero the pixel value
                or      es:[bx],al      ; set the pixel value
                jmp     short L02

                                        ; routine to AND pixel value

ANDPixel:       not     ah              ; AH := inverse bit mask
                or      al,ah           ; AL := all 1's except pixel value
                and     es:[bx],al
                jmp     short L02


ORPixel:        or      es:[bx],al      ; routine to OR pixel value
                jmp     short L02


XORPixel:       xor     es:[bx],al      ; routine to XOR pixel value

L02:            pop     ds              ; restore regs and exit
                mov     sp,bp
                pop     bp
                ret     l0              ; discard parameters and return

SETPEL          ENDP


SetPixelOp      DW      ReplacePixel    ; contains addr of pixel operation


_TEXT           ENDS

                END



���������������������������������������������������������������������������
Listing 13-3b.  Calling SETPEL() from a Turbo Pascal program.
���������������������������������������������������������������������������

{ Listing 13-3b }

PROGRAM rose;    { draws an n-leaved rose of the form
                    rho = a * cos(n*theta) }

CONST
     Leaves             = 11.0;                   { must be an odd number }

     Xmax               = 320;
     Ymax               = 200;
     PixelValue         = 3;
     ScaleFactor        = 1.20;
     Pi                 = 3.14159265358979;

VAR
     x,y:               INTEGER;                      { pixel coordinates }
     a:                 REAL;                   { length of the semi-axis }
     rho,theta:         REAL;                         { polar coordinates }

PROCEDURE SETPEL(VAR x,y:INTEGER;
                  PixelValue:INTEGER); EXTERNAL 'setpel.bin';

BEGIN
     GraphColorMode;                           { set 320x200 4-color mode }
     a := (Ymax/2) - 1;                       { a reasonable choice for a }

     theta := 0.0;
     WHILE theta < Pi DO
     BEGIN
          rho := a * Cos(Leaves * theta);             { apply the formula }

          x := Trunc(rho * Cos(theta));   { convert to rectangular coords }
          y := Trunc(rho * Sin(theta) / ScaleFactor);

          x := x + Trunc(Xmax/2);                      { center on screen }
          y := y + Trunc(Ymax/2);

          SETPEL(x,y,PixelValue);                        { plot the point }

          theta := theta + 0.001;
     END
END.



���������������������������������������������������������������������������
Listing 13-4a.  The SETPEL subroutine (BASICA calling convention).
���������������������������������������������������������������������������

                TITLE   'Listing 13-4a'
                NAME    SETPEL
                PAGE    55,132

;
; Name:         SETPEL
;
; Function:     Set the value of a pixel in 320x200 4-color mode
;
; Caller:       IBM BASICA or Microsoft GWBASIC
;
; Notes:        The code segment is named _TEXT so that PixelAddr may be
;               linked in the same segment.
;

ADDRx           EQU     word ptr [bp+10]
ADDRy           EQU     word ptr [bp+8]
ADDRn           EQU     word ptr [bp+6]

CGROUP          GROUP   _TEXT,END_TEXT

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:CGROUP,ds:CGROUP
                EXTRN   PixelAddr:near

; header for BASIC BLOAD

                DB      0FDh
                DW      2 dup(0)
                DW      (offset CGROUP:BLEnd)-7 ; size of subroutine

; start of subroutine
SETPEL          PROC    far
                push    bp              ; preserve caller registers
                mov     bp,sp
                push    es

; make this routine addressable through SI

                call    L01             ; push offset of L01
L01:            pop     si              ; CS:SI -> L01
                sub     si,offset L01

; get parameters via 16-bit addresses on stack
                mov     di,ADDRy        ; DS:DI -> y
                mov     ax,[di]         ; AX := y
                mov     di,ADDRx
                mov     bx,[di]         ; BX := x
                call    PixelAddr       ; AH := bit mask
                                        ; ES:BX -> buffer
                                        ; CL := # bits to shift left

                mov     di,ADDRn
                mov     al,[di]         ; AL := pixel value
                shl     ax,cl           ; AH := bit mask in proper position
                                        ; AL := pixel value in proper
                                        ;        position
; jump through the variable SetPixelOp to the appropriate routine
                mov     di,SetPixelOp[si]       ; DI := address
                add     di,si           ; DI := relocated address
                jmp     di
                                        ; routine to replace pixel value
ReplacePixel:   not     ah              ; AH := inverse bit mask
                and     es:[bx],ah      ; zero the pixel value
                or      es:[bx],al      ; set the pixel value
                jmp     short L02

                                        ; routine to AND pixel value
ANDPixel:       not     ah              ; AH := inverse bit mask
                or      al,ah           ; AL := all 1s except pixel value
                and     es:[bx],al
                jmp     short L02
ORPixel:        or      es:[bx],al      ; routine to OR pixel value
                jmp     short L02

XORPixel:       xor     es:[bx],al      ; routine to XOR pixel value

L02:            pop     es              ; restore registers
                mov     sp,bp
                pop     bp
                ret     6               ; discard parameters and return
SETPEL          ENDP
SetPixelOp      DW      ReplacePixel    ; contains addr of pixel operation
_TEXT           ENDS

END_TEXT        SEGMENT byte public 'CODE'
BLEnd           LABEL   BYTE            ; this segment is linked after
                                        ;  _TEXT, so this label can be used
                                        ;  to compute the size of the
                                        ;  _TEXT segment
END_TEXT        ENDS
                END



���������������������������������������������������������������������������
Listing 13-4b.  Calling SETPEL from a BASICA program.
���������������������������������������������������������������������������

100 ' Listing 13-4b
110 ' Draws an n-leaved rose of the form  rho = a * cos(n*theta)
120 DEFINT A-Z
130 LEAVES = 11
140 XMAX = 320 : YMAX = 200
150 PIXELVALUE = 2
160 SCALEFACTOR# = 1.2
170 PI# = 3.14159265358979#
180 X = 0 : Y = 0                       ' pixel coordinates
190 A# = 0                              ' length of the semi-axis
200 RHO# = 0 : THETA# = 0               ' polar coordinates
210 '
220 SETPEL = 0
230 DIM SPAREA(256)                     ' reserve RAM for the subroutine
240 SETPEL = VARPTR(SPAREA(1))          ' address of subroutine
250 BLOAD "setpel.bin",SETPEL           ' load subroutine into RAM
260 '
270 SCREEN 1 : COLOR 0,0 : CLS          ' set 320x200 4-color mode
280 A# = (YMAX / 2) - 1                 ' a reasonable choice for A
290 THETA# = 0
300 WHILE (THETA# < PI#)
310  RHO# = A# * COS(LEAVES * THETA#)   ' apply the formula
320  X = RHO# * COS(THETA#)             ' convert to rectangular coords
330  Y = RHO# * SIN(THETA#) / SCALEFACTOR#
340  X = X + XMAX/2                     ' center on screen
350  Y = Y + YMAX/2
360  CALL SETPEL(X,Y,PIXELVALUE)        ' plot the point
370  THETA# = THETA# + .001
380  WEND
390 END



���������������������������������������������������������������������������
Listing 13-5.  A RAM-resident routine to write pixels in 640-by-350
               graphics mode.
���������������������������������������������������������������������������

                TITLE   'Listing 13-5'
                NAME    SetPixel
                PAGE    55,132
;
; Name:         SetPixel
;
; Function:     Set the value of a pixel in native EGA graphics modes.
;
; Caller:       Memory-resident routine invoked via interrupt 60H:
;
;                       mov ax,PixelX      ; pixel x-coordinate
;                       mov bx,PixelY      ; pixel y-coordinate
;                       mov cx,PixelValue  ; pixel value

; Notes:        - Assemble and link to create SETPIXEL.EXE.
;               - Execute once to make SetPixel resident in memory and to
;                  point the INT 60H vector to the RAM-resident code.
;               - Requires MS-DOS version 2.0 or later.
;

RMWbits         EQU     0

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT

                EXTRN   PixelAddr:near

                PUBLIC  SetPixel
SetPixel        PROC    near            ; RAM-resident interrupt 60H
                                        ;  handler

                sti                     ; enable interrupts
                push    ax              ; preserve caller registers on
                push    bx              ;  caller's stack
                push    cx
                push    dx

                push    cx              ; preserve pixel value on stack

                call    PixelAddr       ; compute pixel address
                shl     ah,cl

                mov     dx,3CEh         ; program the Graphics Controller
                mov     al,8            ; AL := Bit Mask register number
                out     dx,ax
                mov     ax,0005h
                out     dx,ax

                mov     ah,RMWbits      ; AH := Read-Modify-Write bits
                mov     al,3            ; AL := Data Rotate/Function Select
                                        ;        reg
                out     dx,ax
                pop     ax
                mov     ah,al           ; AH := pixel value
                mov     al,0
                out     dx,ax

                mov     ax,0F01h
                out     dx,ax

                or      es:[bx],al      ; set the pixel value

                mov     ax,0FF08h       ; restore default Graphics
                out     dx,ax           ;  Controller values

                mov     ax,0005
                out     dx,ax

                mov     ax,0003
                out     dx,ax

                mov     ax,0001
                out     dx,ax

                pop     dx              ; restore caller registers and
                                        ;  return
                pop     cx
                pop     bx
                pop     ax
                iret

SetPixel        ENDP

_TEXT           ENDS


TRANSIENT_TEXT  SEGMENT para
                ASSUME  cs:TRANSIENT_TEXT,ss:STACK

Install         PROC    near

                mov     ax,2560h        ; AH := 25H (INT 21H function
                                        ;            number)
                                        ; AL := 60H (interrupt number)
                mov     dx,seg _TEXT
                mov     ds,dx
                mov     dx,offset _TEXT:SetPixel   ; DS:DX -> interrupt
                                                   ;  handler

                int     21h             ; point INT 60H vector to
                                        ;  SetPixel routine

                mov     dx,cs           ; DX := segment of start of
                                        ;  transient (discardable)
                                        ;  portion of program
                mov     ax,es           ; ES := Program Segment Prefix
                sub     dx,ax           ; DX := size of RAM-resident
                                        ;  portion
                mov     ax,3100h        ; AH := 31H (INT 21H function
                                        ;  number)
                                        ; AL := 0 (return code)
                int     21h             ; Terminate but Stay Resident

Install         ENDP

TRANSIENT_TEXT  ENDS

STACK           SEGMENT para stack 'STACK'

                DB      80h dup(?)      ; stack space for transient portion
                                        ;  of program
STACK           ENDS

                END     Install



���������������������������������������������������������������������������
Listing 13-6.  Using C to draw a filled rectangle.
���������������������������������������������������������������������������

/* Listing 13-6 */

FilledRectangle( x1, y1, x2, y2, n )
int     x1,y1;                  /* upper left corner */
int     x2,y2;                  /* lower right corner */
int     n;                      /* pixel value */
{
        int     y;

        for (y = y1; y <= y2; y++)      /* draw rectangle as a set of */
          Line( x1, y, x2, y, n );      /*  adjacent horizontal lines */
}



���������������������������������������������������������������������������
Listing 13-7.  Using DGIS to draw a filled rectangle.
���������������������������������������������������������������������������

                TITLE   'Listing 13-7'
                NAME    dgisrect
;
; Name:         dgisrect
; Function:     draw a filled rectangle using DGIS
;
; Notes:        assemble and link to create DGISRECT.EXE
;
CR              EQU     0Dh
LF              EQU     0Ah

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:_DATA,ss:STACK
EntryPoint      PROC    far
                mov     ax,seg _DATA
                mov     ds,ax                   ; DS -> _DATA
                push    ss
                pop     es                      ; ES -> stack segment

; look for installed DGIS devices
                xor     dx,dx                   ; DX = 0 (buffer length)
                xor     cx,cx                   ; CX = 0
                xor     bx,bx                   ; BX = 0
                mov     ax,6A00h                ; AX = DGIS opcode (Inquire
                                                ;       Available Devices)
                int     10h
                or      cx,cx
                jnz     L01                     ; jump if device(s)
                                                ;  installed
                mov     dx,offset _DATA:Msg0
                jmp     ErrorExit

; find a graphics output device in the list of installed DGIS devices
L01:            inc     cx                      ; CX = (# of bytes in list)
                                                ;  + 1
                and     cx,0FFFEh               ; CX = even number of bytes
                mov     bp,sp
                sub     sp,cx                   ; establish stack frame
                                                ;  (SS:BP -> end of frame)
                mov     di,sp                   ; ES:DI -> start of stack
                                                ;  frame
                push    di                      ; save for later
                mov     dx,cx                   ; DX = size of buffer
                xor     cx,cx
                xor     bx,bx
                mov     ax,6A00h                ; AX = DGIS opcode (Inquire
                                                ;       Available Devices)
                int     10h                     ; get device list at ES:DI
                pop     di                      ; ES:DI -> device list

L02:            cmp     word ptr es:[di+2],0    ; is this a graphics
                                                ;  device?
                je      L04                     ; jump if so
                sub     bx,es:[di]              ; BX = bytes remaining in
                                                ;  list
                jnz     L03                     ; jump if more devices in
                                                ;  list
                mov     dx,offset _DATA:Msg1
                jmp     ErrorExit

L03:            add     di,es:[di]              ; ES:DI -> next device in
                                                ;  list
                jmp     L02

; establish a logical connection to the graphics device
;  using the first available configuration on the device
L04:            les     di,es:[di+6]            ; ES:DI -> device entry
                                                ;  point
                mov     word ptr GrDevEntry,di
                mov     word ptr GrDevEntry+2,es  ; save entry point
                mov     cx,0                    ; CX = first configuration
                                                ;  index
                mov     ax,0027h                ; AX = DGIS opcode
                                                ;  (Connect)
                call    dword ptr GrDevEntry    ; connect to graphics
                                                ;  device
                cmp     bx,-1                   ; test returned handle
                jne     L05                     ; jump if connected
                mov     dx,offset _DATA:Msg2
                jmp     ErrorExit

L05:            mov     ChannelHandle,bx        ; save the handle for later
                mov     ax,001Bh                ; AX = DGIS opcode
                                                ;  (Init DGI)
                call    dword ptr GrDevEntry    ; initialize the device
                                                ;  with default attributes
; draw a filled rectangle using default attributes
                mov     di,100                  ; DI = lower right corner y
                mov     si,100                  ; SI = lower right corner x
                mov     dx,0                    ; DX = upper left corner y
                mov     cx,0                    ; CX = upper left corner x
                mov     bx,ChannelHandle        ; BX = handle
                mov     ax,003Fh                ; AX = DGIS opcode (Output
                call    dword ptr GrDevEntry    ;       Filled Rectangle)

; disconnect and exit
                mov     bx,ChannelHandle        ; BX = handle
                mov     ax,002Bh                ; AX = DGIS opcode
                                                ;  (Disconnect)
                call    dword ptr GrDevEntry

Lexit:          mov     ax,4C00h
                int     21h                     ; return to DOS

ErrorExit:      mov     ah,9
                int     21h                     ; display error message
                mov     ax,4C01h
                int     21h                     ; return to DOS

EntryPoint      ENDP

_TEXT           ENDS
_DATA           SEGMENT para public 'DATA'

GrDevEntry      DD      ?                       ; graphics device entry
                                                ;  point
ChannelHandle   DW      ?                       ; handle to connected
                                                ;  device configuration

Msg0            DB      CR,LF,'No DGIS devices installed',CR,LF,'$'
Msg1            DB      CR,LF,'No graphics devices installed',CR,LF,'$'
Msg2            DB      CR,LF,'Can't connect to graphics device',CR,LF,'$'
_DATA           ENDS

STACK           SEGMENT stack 'STACK'
                DB      400h dup(?)
STACK           ENDS
                END     EntryPoint



���������������������������������������������������������������������������
Listing 13-8.  Using Microsoft Windows GDI (version 1.03) to draw
               a filled rectangle.
���������������������������������������������������������������������������

    HDC          hDC;                 /* device context handle */
    HPEN         hpen;
    HBRUSH       hbrush;
    PAINTSTRUCT  ps;
    .
    .
    .

    /* initialize device context for window */

    hDC = BeginPaint( hWnd, &ps );
    .
    .
    .

    /* associate attributes with device context */

    hpen = CreatePen( PS_SOLID, 0, GetSysColor(COLOR_WINDOWTEXT) );
    SelectObject( hDC, (HANDLE)hpen );

    hbr = CreateSolidBrush( GetNearestColor(hDC,RectFillColor) );
    SelectObject( hDC, (HANDLE)hbr );
    .
    .
    .

    /* draw a filled rectangle */

    Rectangle( hDC, 0, 0, 100, 100 );
    .
    .
    .
    EndPaint( hWnd, &ps );



���������������������������������������������������������������������������
Listing A-1.  Using a Parameter Save Area to keep track of EGA or VGA
              palette registers.
���������������������������������������������������������������������������

                TITLE   'Listing A-1'
                NAME    EstablishPSA
                PAGE    55,132

;
; Name:         EstablishPSA
;
; Function:     Establish a Parameter Save Area for the EGA or VGA video
;                BIOS. This save area will reflect the current values of
;                the Attribute Controller's Palette and Overscan registers.
;
; Caller:       Microsoft C:
;
;                       void EstablishPSA();
;

SAVE_PTR        EQU     0A8h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP,es:DGROUP

                PUBLIC  _EstablishPSA
_EstablishPSA   PROC    near

                push    bp
                mov     bp,sp
                push    si
                push    di

; preserve previous SAVE_PTR

                push    ds
                pop     es              ; ES -> DGROUP
                mov     di,offset DGROUP:Old_SAVE_PTR

                mov     ax,40h
                mov     ds,ax           ; DS -> video BIOS data area
                mov     si,SAVE_PTR     ; DS:SI -> SAVE_PTR

                mov     cx,4
                rep     movsb

; copy SAVE POINTER table to RAM

                lds     si,es:Old_SAVE_PTR ; DS:SI -> SAVE POINTER table
                mov     di,offset DGROUP:SP_TABLE1
                mov     cx,7*4          ; number of bytes to move
                rep     movsb

; update SAVE_PTR with the address of the new SAVE POINTER table

                mov     ds,ax           ; DS -> video BIOS data area
                mov     si,SAVE_PTR
                mov     word ptr [si],offset DGROUP:SP_TABLE1
                mov     [si+2],es

; update SAVE POINTER table with address of Parameter Save Area

                push    es
                pop     ds      ; DS -> DGROUP

                mov     word ptr SP_TABLE1[4],offset DGROUP:PSA
                mov     word ptr SP_TABLE1[6],ds

; restore registers and exit

                pop     di
                pop     si
                mov     sp,bp
                pop     bp
                ret

_EstablishPSA   ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

Old_SAVE_PTR    DD      ?               ; previous value of SAVE_PTR
SP_TABLE1       DD      7 dup(?)        ; RAM copy of SAVE POINTER table
PSA             DB      256 dup(0)      ; Parameter Save Area

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing A-2.  Using a User Palette Profile to override the default VGA
              palette.
���������������������������������������������������������������������������

                TITLE   'Listing A-2'
                NAME    EstablishUPP
                PAGE    55,132

;
; Name:         EstablishUPP
;
; Function:     Establish a User Palette Profile Save Area for the VGA
;                video BIOS. This save area overrides the usual default
;                palette values for a specified list of video modes.
;
; Caller:       Microsoft C:
;
;                       void EstablishUPP();

SAVE_PTR        EQU     0A8h

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP,es:DGROUP

                PUBLIC  _EstablishUPP
_EstablishUPP   PROC    near

                push    bp
                mov     bp,sp
                push    si
                push    di

; preserve previous SAVE_PTR

                push    ds
                pop     es              ; ES -> DGROUP
                mov     di,offset DGROUP:Old_SAVE_PTR

                mov     ax,40h
                mov     ds,ax           ; DS -> video BIOS data area
                mov     si,SAVE_PTR     ; DS:SI -> SAVE_PTR

                mov     cx,4
                rep     movsb

; copy SAVE POINTER table to RAM

                lds     si,es:Old_SAVE_PTR ; DS:SI -> SAVE POINTER table
                mov     di,offset DGROUP:SP_TABLE1
                mov     cx,7*4          ; number of bytes to move
                rep     movsb

; update SAVE_PTR with the address of the new SAVE POINTER table

                mov     ds,ax           ; DS -> video BIOS data area
                mov     si,SAVE_PTR
                mov     word ptr [si],offset DGROUP:SP_TABLE1
                mov     [si+2],es

; copy SECONDARY SAVE POINTER table to RAM

                lds     si,es:SP_TABLE1[16] ; DS:SI -> SEC SAVE POINTER
                                            ;           table
                mov     di,offset DGROUP:SP_TABLE2
                mov     cx,[si]
                rep     movsb

; update new SAVE POINTER table with address of new SECONDARY SAVE POINTER
;  table

                push    es
                pop     ds      ; DS -> DGROUP

                mov     word ptr SP_TABLE1[16],offset DGROUP:SP_TABLE2
                mov     word ptr SP_TABLE1[18],ds

; update SECONDARY SAVE POINTER with address of User Palette Profile

                mov     word ptr SP_TABLE2[10],offset DGROUP:UPP
                mov     word ptr SP_TABLE2[12],ds

; restore registers and exit

                pop     di
                pop     si
                mov     sp,bp
                pop     bp
                ret

_EstablishUPP   ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

Old_SAVE_PTR    DD      ?               ; previous value of SAVE_PTR

SP_TABLE1       DD      7 dup(?)        ; copy of SAVE POINTER table

SP_TABLE2       DW      ?               ; copy of SECONDARY SAVE POINTER
                                        ;  table
                DD      6 dup(?)

UPP             DB      0               ; underlining flag
                DB      0               ; (reserved)
                DW      0               ; (reserved)
                DW      17              ; # of palette & overscan registers
                DW      0               ; first register specified in table
                DW      DGROUP:PalTable ; pointer to palette table
                DW      seg DGROUP
                DW      0               ; number of video DAC color regs
                DW      0               ; first video DAC register
                DD      0               ; pointer to video DAC color table
                DB      3,0FFh          ; list of applicable video modes

PalTable        DB      30h,31h,32h,33h,34h,35h,36h,37h ; a custom palette
                DB      00h,01h,02h,03h,04h,05h,14h,07h
                DB      01h                             ; overscan reg

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing B-1.  A simple screen dump routine for the CGA.
���������������������������������������������������������������������������

                TITLE   'Listing B-1'
                NAME    ScreenDumpCGA
                PAGE    55,132

;
; Name:         ScreenDumpCGA
;
; Function:     Screen Dump for CGA 640x200 2-color and 320x200 4-color
;                modes
;
; Caller:       (undefined)
;
; Notes:        The main procedure of this program, ScreenDumpCGA, may be
;               called from an application program or as part of a TSR
;               (Terminate-but-Stay-Resident) handler for interrupt 5.
;

STDPRN          =       4               ; MS-DOS standard printer handle


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

;
; PrintLine
;
;       Writes one line of characters to the standard printer device.
;       Ignores errors.
;

PrintLine       PROC    near            ; Caller:       DS:DX -> data
                                        ;               CX = # of bytes

                mov     bx,STDPRN
                mov     ah,40h          ; INT 21h function 40h:  write
                int     21h
                ret

PrintLine       ENDP

;
; PrinterGraphics
;
;       Puts the printer in its "graphics mode."  This routine must be
;       customized for different printers.
;

PrinterGraphics PROC    near            ; Configures Epson MX-80 printer
                                        ;  for 480 dots/line

                mov     dx,offset DGROUP:EpsonGraphics
                mov     cx,3
                call    PrintLine
                ret

PrinterGraphics ENDP

;
; PrinterDefault
;
;       Puts the printer in its default (non-graphics) mode.  Again, this
;       routine must be customized for different printers.
;

PrinterDefault  PROC    near            ; Configures Epson MX-80 for
                                        ;  default alphanumeric output

                mov     dx,offset DGROUP:EpsonReset
                mov     cx,2
                call    PrintLine
                ret

PrinterDefault  ENDP

;
; ChopZeros
;
;       Chops trailing zeros from the printer output buffer.
;

ChopZeros       PROC    near            ; Caller:       ES:DI -> buffer
                                        ;               CX = buffer length
                                        ; Returns:      CX = adjusted
                                        ;                     length

                jcxz    L01             ; exit if buffer is empty

                add     di,cx
                dec     di              ; ES:DI -> last byte in buffer

                xor     al,al           ; AL := 0 (byte to scan for)

                std                     ; scan backwards
                repe    scasb
                cld                     ; restore direction flag
                je      L01             ; jump if buffer filled with zeros

                inc     cx              ; adjust length past last nonzero
                                        ;  byte

L01:            ret

ChopZeros       ENDP

;
; PrintPixels
;
;       Prints one row of pixels on an Epson MX-80.
;

PrintPixels     PROC    near            ; Caller:       DI = offset of
                                        ;                     buffer
                                        ;               CX = buffer length

                push    ds
                pop     es              ; ES := DS

                push    di              ; preserve buffer offset
                call    ChopZeros
                push    cx              ; preserve length

                mov     word ptr DataHeader+2,cx  ; store buffer length
                                                  ;  in output data header
                mov     dx,offset DGROUP:DataHeader
                mov     cx,4
                call    PrintLine       ; print data header

                pop     cx              ; CX := buffer length
                pop     dx              ; DX := buffer offset
                call    PrintLine       ; print the pixels

                mov     dx,offset DGROUP:CRLF
                mov     cx,2
                call    PrintLine

                ret

PrintPixels     ENDP

;
; TranslatePixels
;
;       Copies one printable row of pixels from the video buffer to the
;       print buffer.  This routine can be modified at will to change the
;       scaling or orientation of the printed image, to interpolate gray-
;       scale values for color pixels, etc.
;
;       This routine formats the printer buffer for output to an Epson
;       MX-80. The page is printed sideways, with two horizontal printed
;       pixels for each vertical pixel in the video buffer. Since the CGA
;       screen is 200 pixels high, the printed output is 400 pixels wide.
;

TranslatePixels PROC    near            ; Caller:       SI = video buffer
                                        ;                     offset
                                        ;               ES:DI -> print
                                        ;                     buffer

                push    ds              ; preserve DS
                mov     ds,VideoBufSeg  ; DS:SI -> video buffer

                add     di,398          ; ES:DI -> 2 bytes before end of
                                        ;           buffer

                mov     cx,200          ; CX := # of vertical pixels
                mov     bx,2000h+1      ; BX := 1st video buffer increment
                mov     dx,81-2000h     ; DX := 2nd video buffer increment

                std                     ; fill the print buffer backwards

L11:            lodsb                   ; AL := 8 pixels from video buffer
                mov     ah,al           ; AX := 8 doubled pixels
                stosw                   ; write them to print buffer

                add     si,bx           ; increment to next interleave of
                xchg    bx,dx           ;  video buffer

                loop    L11

                cld                     ; clear direction flag
                pop     ds              ; restore DS
                ret

TranslatePixels ENDP

;
; ScreenDumpCGA
;

ScreenDumpCGA   PROC    near            ; Caller:       DS = DGROUP

                call    PrinterGraphics ; configure the printer for
                                        ;  graphics

                push    ds
                pop     es              ; DS,ES := DGROUP
                xor     si,si           ; SI := offset of start of video
                                        ;        buffer

L21:            push    si
                mov     di,offset DGROUP:PrintBuf
                call    TranslatePixels ; copy one printable row of pixels

                mov     cx,400
                mov     di,offset DGROUP:PrintBuf
                call    PrintPixels     ; print them

                pop     si
                inc     si
                cmp     si,80           ; loop across all 80 columns in
                jb      L21             ;  the video buffer

                call    PrinterDefault  ; restore the printer to its
                                        ;  default state
                ret

ScreenDumpCGA   ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

PrintBuf        DB      400 dup(?)      ; print output buffer

VideoBufSeg     DW      0B800h

EpsonGraphics   DB      1Bh,33h,18h
EpsonReset      DB      1Bh,40h
DataHeader      DB      1Bh,4Bh,00h,00h
CRLF            DB      0Dh,0Ah

_DATA           ENDS
                END



���������������������������������������������������������������������������
Listing B-2.  An EGA screen printing routine.
���������������������������������������������������������������������������

                TITLE   'Listing B-2'
                NAME    ScreenDumpEGA
                PAGE    55,132

; Name:         ScreenDumpEGA
;
; Function:     Screen Dump for EGA 640x350 16-color mode
;
; Caller:       (undefined)
;
; Notes:        The main procedure of this program, ScreenDumpEGA, may be
;               called from an application program or as part of a TSR
;               (Terminate-but-Stay-Resident) handler for interrupt 5.

STDPRN          =       4               ; MS-DOS standard printer handle

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

; PrintLine
;
;       Writes one line of characters to the standard printer device.
;       Ignores errors.
;

PrintLine       PROC    near            ; Caller:       DS:DX -> data
                                        ;               CX = # of bytes
                mov     bx,STDPRN
                mov     ah,40h          ; INT 21h function 40h:  Write
                int     21h
                ret

PrintLine       ENDP

; PrinterGraphics
;
;       Puts the printer in its "graphics mode."  This routine must be
;       customized for different printers.
;

PrinterGraphics PROC    near            ; Configures Epson MX-80 printer
                                        ;  for 480 dots/line

                mov     dx,offset DGROUP:EpsonGraphics
                mov     cx,3
                call    PrintLine
                ret
PrinterGraphics ENDP

;
; PrinterDefault
;
;       Puts the printer in its default (non-graphics) mode.  Again, this
;       routine must be customized for different printers.
;

PrinterDefault  PROC    near            ; Configures Epson MX-80 for
                                        ;  default alphanumeric output

                mov     dx,offset DGROUP:EpsonReset
                mov     cx,2
                call    PrintLine
                ret

PrinterDefault  ENDP

;
; ChopZeros
;
;       Chops trailing zeros from the printer output buffer.
;

ChopZeros       PROC    near            ; Caller:       ES:DI -> buffer
                                        ;               CX = buffer length
                                        ; Returns:      CX = adjusted
                                        ;                     length

                jcxz    L01             ; exit if buffer is empty

                add     di,cx
                dec     di              ; ES:DI -> last byte in buffer

                xor     al,al           ; AL := 0 (byte to scan for)

                std                     ; scan backwards
                repe    scasb
                cld                     ; restore direction flag
                je      L01             ; jump if buffer filled with zeros

                inc     cx              ; adjust length past last nonzero
                                        ;  byte

L01:            ret

ChopZeros       ENDP

;
; PrintPixels
;
;       Prints one row of pixels on an Epson MX-80.
;

PrintPixels     PROC    near            ; Caller:       DI = offset of
                                        ;                     buffer
                                        ;               CX = buffer length

                push    ds
                pop     es              ; ES := DS
                push    di              ; preserve buffer offset
                call    ChopZeros
                push    cx              ; preserve length

                mov     word ptr DataHeader+2,cx  ; store buffer length
                                                  ;  in output data header
                mov     dx,offset DGROUP:DataHeader
                mov     cx,4
                call    PrintLine       ; print data header

                pop     cx              ; CX := buffer length
                pop     dx              ; DX := buffer offset
                call    PrintLine       ; print the pixels

                mov     dx,offset DGROUP:CRLF
                mov     cx,2
                call    PrintLine

                ret

PrintPixels     ENDP

;
; TranslatePixels
;
;       Copies one printable row of pixels from the video buffer to the
;       print buffer.  This routine can be modified at will to change the
;       scaling or orientation of the printed image, to interpolate gray-
;       scale values for color pixels, etc.
;
;       This routine formats the printer buffer for output to an Epson
;       MX-80.  The page is printed sideways, so the printed output is
;       350 pixels wide.
;

TranslatePixels PROC    near            ; Caller:       SI = video buffer
                                        ;                     offset
                                        ;               ES:DI -> print
                                        ;                     buffer

                push    ds              ; preserve DS
                mov     ds,VideoBufSeg  ; DS:SI -> video buffer

                add     di,349          ; ES:DI -> last byte in print
                                        ;           buffer

                mov     cx,350          ; CX := # of vertical pixels

; set up the Graphics Controller for read mode 1

                mov     dx,3CEh         ; Graphics Controller I/O port
                mov     ax,805h         ; AH := 00001000b (read mode 1)
                                        ; AL := Mode register number
                out     dx,ax

                mov     ax,002          ; AH := 0 (color compare value)
                out     dx,ax           ; AL := Color Compare register
                                        ;        number

                mov     ax,0F07h        ; AH := 00001111b (color don't care
                                        ;        mask)
                out     dx,ax           ; AL := Color Don't Care register
                                        ;        number

; fill the print buffer; all nonzero pixels in the video buffer are
;  printed

                std                     ; fill the print buffer backwards
L11:            lodsb                   ; AL := 8-pixel color compare value
                                        ;  (bits = 0 if pixel <> 0)
                not     al              ; AL := 8 printable pixels
                stosb                   ; store in print buffer

                add     si,81           ; increment to next row in video
                                        ;  buffer
                loop    L11

                cld                     ; clear direction flag

; restore Graphics Controller default state

                mov     ax,5            ; AH := read mode 0, write mode 0
                out     dx,ax           ; AL := Mode register number

                mov     ax,7            ; AH := 0 (color don't care mask)
                out     dx,ax           ; AL := Color Don't Care register
                                        ;        number

                pop     ds              ; restore DS
                ret

TranslatePixels ENDP

;
; ScreenDumpEGA
;

ScreenDumpEGA   PROC    near            ; Caller:       DS = DGROUP

                call    PrinterGraphics ; configure the printer for
                                        ;  graphics

                push    ds
                pop     es              ; DS,ES := DGROUP

                xor     si,si           ; SI := offset of start of video
                                        ;        buffer

L21:            push    si
                mov     di,offset DGROUP:PrintBuf
                call    TranslatePixels ; copy one printable row of pixels

                mov     cx,350
                mov     di,offset DGROUP:PrintBuf
                call    PrintPixels     ; print them

                pop     si
                inc     si
                cmp     si,80           ; loop across all 80 columns in
                jb      L21             ;  the video buffer

                call    PrinterDefault  ; restore the printer to its
                                        ;  default state
                ret

ScreenDumpEGA   ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

PrintBuf        DB      350 dup(?)      ; print output buffer

VideoBufSeg     DW      0A000h

EpsonGraphics   DB      1Bh,33h,18h
EpsonReset      DB      1Bh,40h
DataHeader      DB      1Bh,4Bh,00h,00h
CRLF            DB      0Dh,0Ah

_DATA           ENDS
                END



���������������������������������������������������������������������������
Listing B-3.  Using RAM-based character definition tables to print
              the character set.
���������������������������������������������������������������������������

                TITLE   'Listing B-3'
                NAME    ScreenDumpAlpha
                PAGE    55,132

;
; Name:         ScreenDumpAlpha
;
; Function:     Screen Dump for EGA alphanumeric modes with 350-line
;                resolution
;
; Caller:       (undefined)
;
; Notes:        The main procedure of this program, ScreenDumpAlpha, may be
;               called from an application program or as part of a TSR
;               (Terminate-but-Stay-Resident) handler for interrupt 5.
;

STDPRN          =       4               ; MS-DOS standard printer handle

DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP,es:DGROUP

;
; PrintLine
;
;       Writes one line of characters to the standard printer device.
;       Ignores errors.
;

PrintLine       PROC    near            ; Caller:       DS:DX -> data
                                        ;               CX = # of bytes
                mov     bx,STDPRN
                mov     ah,40h          ; INT 21h function 40h:  Write
                int     21h
                ret

PrintLine       ENDP

;
; PrinterGraphics
;
;       Puts the printer in its "graphics mode."  This routine must be
;       customized for different printers.
;

PrinterGraphics PROC    near            ; Configures Epson MX-80 printer
                                        ;  for 480 dots/line

                mov     dx,offset DGROUP:EpsonGraphics
                mov     cx,3
                call    PrintLine
                ret

PrinterGraphics ENDP

;
; PrinterDefault
;
;       Puts the printer in its default (non-graphics) mode.  Again, this
;       routine must be customized for different printers.
;

PrinterDefault  PROC    near            ; Configures Epson MX-80 for
                                        ;  default alphanumeric output

                mov     dx,offset DGROUP:EpsonReset
                mov     cx,2
                call    PrintLine
                ret

PrinterDefault  ENDP

;
; ChopZeros
;
;       Chops trailing zeros from the printer output buffer.

ChopZeros       PROC    near            ; Caller:       ES:DI -> buffer
                                        ;               CX = buffer length
                                        ; Returns:      CX = adjusted
                                        ;                     length
                jcxz    L01             ; exit if buffer is empty

                add     di,cx
                dec     di              ; ES:DI -> last byte in buffer

                xor     al,al           ; AL := 0 (byte to scan for)

                std                     ; scan backwards
                repe    scasb
                cld                     ; restore direction flag
                je      L01             ; jump if buffer filled with zeros

                inc     cx              ; adjust length past last nonzero
                                        ;  byte

L01:            ret

ChopZeros       ENDP

;
; PrintPixels
;
;       Prints one row of pixels on an Epson MX-80.
;

PrintPixels     PROC    near            ; Caller:       DI = offset of
                                        ;                     buffer
                                        ;               CX = buffer length

                push    ds
                pop     es              ; ES := DS

                push    di              ; preserve buffer offset
                call    ChopZeros
                push    cx              ; preserve length

                mov     word ptr DataHeader+2,cx  ; store buffer length
                                                  ;  in output data header
                mov     dx,offset DGROUP:DataHeader
                mov     cx,4
                call    PrintLine       ; print data header

                pop     cx              ; CX := buffer length
                pop     dx              ; DX := buffer offset
                call    PrintLine       ; print the pixels

                mov     dx,offset DGROUP:CRLF
                mov     cx,2
                call    PrintLine

                ret

PrintPixels     ENDP

;
; TranslatePixels
;
;       Copies one printable row of pixels from the first character
;       definition table in map 2 to the print buffer.
;
;       This routine formats the printer buffer for output to an Epson
;       MX-80.  The page is printed sideways, so the printed output is
;       350 pixels wide.
;

TranslatePixels PROC    near            ; Caller:       SI = video buffer
                                        ;                     offset
                                        ;               ES:DI -> print
                                        ;                      buffer

                push    ds              ; preserve DS
                mov     ds,VideoBufSeg  ; DS:SI -> video buffer

                add     di,es:PrintBufSize
                dec     di              ; ES:DI -> last byte in print
                                        ;           buffer

                mov     dx,3CEh         ; Graphics Controller I/O port

; fill the print buffer

                mov     cx,es:Rows      ; CX := number of character rows

L11:            push    cx              ; preserve CX and SI
                push    si

                mov     ax,0004h        ; AH := value for Read Map Select
                                        ;        reg
                                        ; AL := Read Map Select reg number
                out     dx,ax           ; select map 0 (character codes)

                lodsb                   ; AX := next char code in video
                                        ;        buffer
                mov     cl,5
                shl     ax,cl           ; AX := AX * 32
                mov     si,ax           ; SI := offset of character
                                        ;  definition in map 2
                mov     ax,0204h
                out     dx,ax           ; select map 2 (bit patterns)

                mov     cx,es:Points    ; CX := size of character
                                        ;        definition

L12:            cld
                lodsb                   ; AL := 8-bit pattern from
                                        ;  character definition table
                                        ; SI := SI + 1
                std
                stosb                   ; store bit pattern in print buffer
                                        ; DI := DI - 1
                loop    L12             ; loop down character definition

                pop     si              ; restore SI and CX
                pop     cx

                add     si,es:Columns   ; DS:SI -> next row of characters
                loop    L11             ; loop down character rows

                cld                     ; clear direction flag
                pop     ds              ; restore DS
                ret

TranslatePixels ENDP

;
; ScreenDumpAlpha
;

ScreenDumpAlpha PROC    near            ; Caller:       DS = DGROUP

                call    PrinterGraphics ; configure the printer for
                                        ;  graphics

                call    CGenModeSet     ; address EGA memory maps in
                                        ;  parallel:
                                        ;  map 0 contains character codes
                                        ;  map 2 contains character
                                        ;  definitions

; copy screen dimensions from Video Display Data Area

                mov     ax,40h
                mov     es,ax           ; ES -> video BIOS data area
                mov     al,es:[84h]     ; AX := ROWS
                inc     ax
                mov     Rows,ax
                mov     ax,es:[4Ah]     ; AX := CRT_COLS
                add     ax,ax           ; * 2 for proper buffer addressing
                mov     Columns,ax
                mov     ax,es:[85h]     ; AX := POINTS
                mov     Points,ax
                mul     Rows            ; AX := ROWS * POINTS
                mov     PrintBufSize,ax

; print the screen

                push    ds
                pop     es              ; DS,ES := DGROUP
                xor     si,si           ; SI := offset of start of video
                                        ;        buffer

L21:            push    si
                mov     di,offset DGROUP:PrintBuf
                call    TranslatePixels ; copy one printable row of pixels

                mov     cx,PrintBufSize
                mov     di,offset DGROUP:PrintBuf
                call    PrintPixels     ; print them
                pop     si
                add     si,2            ; increment to next character
                                        ;  column
                cmp     si,Columns      ; loop across all character columns
                jb      L21

                call    CGenModeClear   ; restore previous alphanumeric
                                        ;  mode
                call    PrinterDefault  ; restore the printer to its
                                        ;  default state
                ret
ScreenDumpAlpha ENDP

;
; CGenModeSet (from Chapter 10)
;

CGenModeSet     PROC    near

                push    si              ; preserve these registers
                push    cx

                cli                     ; disable interrupts
                mov     dx,3C4h         ; Sequencer port address
                mov     si,offset DGROUP:SetSeqParms
                mov     cx,4

L31:            lodsw                   ; AH := value for Sequencer
                                        ;        register
                                        ; AL := register number
                out     dx,ax           ; program the register
                loop    L31
                sti                     ; enable interrupts

                mov     dl,0CEh         ; DX := 3CEH (Graphics Controller
                                        ;        port address)
                mov     si,offset DGROUP:SetGCParms
                mov     cx,3

L32:            lodsw                   ; program the Graphics Controller
                out     dx,ax
                loop    L32

                pop     cx              ; restore registers and return
                pop     si
                ret

CGenModeSet     ENDP

;
; CGenModeClear (from Chapter 10)
;

CGenModeClear   PROC    near

                push    si              ; preserve these registers
                push    cx

                cli                     ; disable interrupts
                mov     dx,3C4h         ; Sequencer port address
                mov     si,offset DGROUP:ClearSeqParms
                mov     cx,4

L41:            lodsw                   ; AH := value for Sequencer
                                        ;        register
                                        ; AL := register number
                out     dx,ax           ; program the register
                loop    L41
                sti                     ; enable interrupts

                mov     dl,0CEh         ; DX := 3CEH (Graphics Controller
                                        ;               port address)

                mov     si,offset DGROUP:ClearGCParms
                mov     cx,3

L42:            lodsw                   ; program the Graphics Controller
                out     dx,ax
                loop    L42

                mov     ah,0Fh          ; AH := INT 10H function number
                int     10h             ; get video mode

                cmp     al,7
                jne     L43             ; jump if not monochrome mode

                mov     ax,0806h        ; program Graphics Controller
                out     dx,ax           ;  to start map at B000:0000

L43:            pop     cx              ; restore registers and return
                pop     si
                ret

CGenModeClear   ENDP

_TEXT           ENDS

_DATA           SEGMENT word public 'DATA'

PrintBuf        DB      400 dup(?)      ; print output buffer

VideoBufSeg     DW      0A000h

EpsonGraphics   DB      1Bh,33h,18h
EpsonReset      DB      1Bh,40h
DataHeader      DB      1Bh,4Bh,00h,00h
CRLF            DB      0Dh,0Ah
Columns         DW      ?               ; number of displayed character
                                        ;  columns
Rows            DW      ?               ; number of displayed character
                                        ;  rows
Points          DW      ?               ; vertical size of character matrix
PrintBufSize    DW      ?               ; Rows * Points

SetSeqParms     DW      0100h           ; parameters for CGenModeSet
                DW      0402h
                DW      0704h
                DW      0300h

SetGCParms      DW      0204h
                DW      0005h
                DW      0006h

ClearSeqParms   DW      0100h           ; parameters for CGenModeClear
                DW      0302h
                DW      0304h
                DW      0300h

ClearGCParms    DW      0004h
                DW      1005h
                DW      0E06h

_DATA           ENDS
                END



���������������������������������������������������������������������������
Listing C-1.  A routine to identify PC and PS/2 video subsystems.
���������������������������������������������������������������������������

                TITLE   'Listing C-1 - VideoID'
                NAME    VideoID
                PAGE    55,132

;
; Name:         VideoID
;
; Function:     Detects the presence of various video subsystems and
;                associated monitors.
;
; Caller:       Microsoft C:
;
;                       void VideoID(VIDstruct);
;
;                       struct
;                       {
;                         char VideoSubsystem;
;                         char Display;
;                       }
;                               *VIDstruct[2];
;
;               Subsystem ID values:
;                                0  = (none)
;                                1  = MDA
;                                2  = CGA
;                                3  = EGA
;                                4  = MCGA
;                                5  = VGA
;                               80h = HGC
;                               81h = HGC+
;                               82h = Hercules InColor
;
;               Display types:   0  = (none)
;                                1  = MDA-compatible monochrome
;                                2  = CGA-compatible color
;                                3  = EGA-compatible color
;                                4  = PS/2-compatible monochrome
;                                5  = PS/2-compatible color
;
;
;       The values returned in VIDstruct[0].VideoSubsystem and
;       VIDstruct[0].Display indicate the currently active subsystem.
;

ARGpVID         EQU     word ptr [bp+4] ; stack frame addressing


VIDstruct       STRUC                   ; corresponds to C data structure

Video0Type      DB      ?               ; first subsystem type
Display0Type    DB      ?               ; display attached to first
                                        ;  subsystem
Video1Type      DB      ?               ; second subsystem type
Display1Type    DB      ?               ; display attached to second
                                        ;  subsystem

VIDstruct       ENDS


Device0         EQU     word ptr Video0Type[di]
Device1         EQU     word ptr Video1Type[di]


MDA             EQU     1               ; subsystem types
CGA             EQU     2
EGA             EQU     3
MCGA            EQU     4
VGA             EQU     5
HGC             EQU     80h
HGCPlus         EQU     81h
InColor         EQU     82h

MDADisplay      EQU     1               ; display types
CGADisplay      EQU     2
EGAColorDisplay EQU     3
PS2MonoDisplay  EQU     4
PS2ColorDisplay EQU     5

TRUE            EQU     1
FALSE           EQU     0


DGROUP          GROUP   _DATA

_TEXT           SEGMENT byte public 'CODE'
                ASSUME  cs:_TEXT,ds:DGROUP

                PUBLIC  _VideoID
_VideoID        PROC    near

                push    bp              ; preserve caller registers
                mov     bp,sp
                push    si
                push    di

; initialize the data structure that will contain the results

                mov     di,ARGpVID      ; DS:DI -> start of data structure

                mov     Device0,0       ; zero these variables
                mov     Device1,0

; look for the various subsystems using the subroutines whose addresses are
;  tabulated in TestSequence; each subroutine sets flags in TestSequence
;  to indicate whether subsequent subroutines need to be called

                mov     byte ptr CGAflag,TRUE
                mov     byte ptr EGAflag,TRUE
                mov     byte ptr Monoflag,TRUE

                mov     cx,NumberOfTests
                mov     si,offset DGROUP:TestSequence

L01:            lodsb                   ; AL := flag
                test    al,al
                lodsw                   ; AX := subroutine address
                jz      L02             ; skip subroutine if flag is false

                push    si
                push    cx
                call    ax              ; call subroutine to detect
                                        ;  subsystem
                pop     cx
                pop     si

L02:            loop    L01

; determine which subsystem is active

                call    FindActive
                pop     di              ; restore caller registers and
                                        ;  return
                pop     si
                mov     sp,bp
                pop     bp
                ret

_VideoID        ENDP

;
; FindPS2
;
;       This subroutine uses INT 10H function 1Ah to determine the video
;        BIOS Display Combination Code (DCC) for each video subsystem
;        present.
;

FindPS2         PROC    near

                mov     ax,1A00h
                int     10h             ; call video BIOS for info

                cmp     al,1Ah
                jne     L13             ; exit if function not supported
                                        ;  (i.e., no MCGA or VGA in system)

; convert BIOS DCCs into specific subsystems & displays

                mov     cx,bx
                xor     bh,bh           ; BX := DCC for active subsystem
                or      ch,ch
                jz      L11             ; jump if only one subsystem
                                        ;  present

                mov     bl,ch           ; BX := inactive DCC
                add     bx,bx
                mov     ax,[bx+offset DGROUP:DCCtable]

                mov     Device1,ax

                mov     bl,cl
                xor     bh,bh           ; BX := active DCC

L11:            add     bx,bx
                mov     ax,[bx+offset DGROUP:DCCtable]

                mov     Device0,ax

; reset flags for subsystems that have been ruled out

                mov     byte ptr CGAflag,FALSE
                mov     byte ptr EGAflag,FALSE
                mov     byte ptr Monoflag,FALSE

                lea     bx,Video0Type[di]  ; if the BIOS reported an
                                           ;  MDA...
                cmp     byte ptr [bx],MDA
                je      L12

                lea     bx,Video1Type[di]
                cmp     byte ptr [bx],MDA
                jne     L13

L12:            mov     word ptr [bx],0    ; ... Hercules can't be ruled
                                           ;      out
                mov     byte ptr Monoflag,TRUE

L13:            ret

FindPS2         ENDP


;
; FindEGA
;
; Look for an EGA.  This is done by making a call to an EGA BIOS function
;  which doesn't exist in the default (MDA, CGA) BIOS.

FindEGA         PROC    near            ; Caller:       AH = flags
                                        ; Returns:      AH = flags
                                        ;               Video0Type and
                                        ;                Display0Type
                                                         updated

                mov     bl,10h          ; BL := 10h (return EGA info)
                mov     ah,12h          ; AH := INT 10H function number
                int     10h             ; call EGA BIOS for info
                                        ; if EGA BIOS is present,
                                        ;  BL <> 10H
                                        ;  CL = switch setting
                cmp     bl,10h
                je      L22             ; jump if EGA BIOS not present

                mov     al,cl
                shr     al,1            ; AL := switches/2
                mov     bx,offset DGROUP:EGADisplays
                xlat                    ; determine display type from
                                        ;  switches
                mov     ah,al           ; AH := display type
                mov     al,EGA          ; AL := subystem type
                call    FoundDevice

                cmp     ah,MDADisplay
                je      L21             ; jump if EGA has a monochrome
                                        ;  display

                mov     CGAflag,FALSE   ; no CGA if EGA has color display
                jmp     short L22

L21:            mov     Monoflag,FALSE  ; EGA has a mono display, so MDA
                                        ;  and Hercules are ruled out
L22:            ret

FindEGA         ENDP


;
; FindCGA
;
;       This is done by looking for the CGA's 6845 CRTC at I/O port 3D4H.
;

FindCGA         PROC    near            ; Returns:      VIDstruct updated

                mov     dx,3D4h         ; DX := CRTC address port
                call    Find6845
                jc      L31             ; jump if not present

                mov     al,CGA
                mov     ah,CGADisplay
                call    FoundDevice

L31:            ret

FindCGA         ENDP


;
; FindMono
;
;       This is done by looking for the MDA's 6845 CRTC at I/O port 3B4H.
;       If a 6845 is found, the subroutine distinguishes between an MDA
;       and a Hercules adapter by monitoring bit 7 of the CRT Status byte.
;       This bit changes on Hercules adapters but does not change on an
;       MDA. The various Hercules adapters are identified by bits 4 through
;       6 of the CRT Status value:
;
;               000b = HGC
;               001b = HGC+
;               101b = InColor card

FindMono        PROC    near            ; Returns:      VIDstruct updated

                mov     dx,3B4h         ; DX := CRTC address port
                call    Find6845
                jc      L44             ; jump if not present

                mov     dl,0BAh         ; DX := 3BAh (status port)
                in      al,dx
                and     al,80h
                mov     ah,al           ; AH := bit 7 (vertical sync on
                                        ;        HGC)

                mov     cx,8000h        ; do this 32768 times
L41:            in      al,dx
                and     al,80h          ; isolate bit 7
                cmp     ah,al
                loope   L41             ; wait for bit 7 to change

                jne     L42             ; if bit 7 changed, it's a Hercules

                mov     al,MDA          ; if bit 7 didn't change, it's an
                                        ;  MDA
                mov     ah,MDADisplay
                call    FoundDevice
                jmp     short L44

L42:            in      al,dx
                mov     dl,al           ; DL := value from status port

                mov     ah,MDADisplay   ; assume it's a monochrome display

                mov     al,HGC          ; look for an HGC
                and     dl,01110000b    ; mask off bits 4 thru 6
                jz      L43

                mov     al,HGCPlus      ; look for an HGC+
                cmp     dl,00010000b
                je      L43             ; jump if it's an HGC+

                mov     al,InColor      ; it's an InColor card
                mov     ah,EGAColorDisplay

L43:            call    FoundDevice

L44:            ret

FindMono        ENDP


;
; Find6845
;
;       This routine detects the presence of the CRTC on an MDA, CGA, or
;       HGC. The technique is to write and read register 0Fh of the chip
;       (Cursor Location Low). If the same value is read as written,
;       assume the chip is present at the specified port address.
;

Find6845        PROC    near            ; Caller:       DX = port addr
                                        ; Returns:      cf set if not
                                        ;                present
                mov     al,0Fh
                out     dx,al           ; select 6845 reg 0Fh (Cursor Low)
                inc     dx

                in      al,dx           ; AL := current Cursor Low value
                mov     ah,al           ; preserve in AH
                mov     al,66h          ; AL := arbitrary value
                out     dx,al           ; try to write to 6845

                mov     cx,100h

L51:            loop    L51             ; wait for 6845 to respond

                in      al,dx
                xchg    ah,al           ; AH := returned value
                                        ; AL := original value
                out     dx,al           ; restore original value

                cmp     ah,66h          ; test whether 6845 responded
                je      L52             ; jump if it did (cf is reset)

                stc                     ; set carry flag if no 6845 present

L52:            ret

Find6845        ENDP


;
; FindActive
;
;       This subroutine stores the currently active device as Device0.  The
;       current video mode determines which subsystem is active.
;

FindActive      PROC    near

                cmp     word ptr Device1,0
                je      L63                     ; exit if only one
                                                ;  subsystem

                cmp     Video0Type[di],4        ; exit if MCGA or VGA
                                                ;  present
                jge     L63                     ;  (INT 10H function 1AH
                cmp     Video1Type[di],4        ;  already did the work)
                jge     L63

                mov     ah,0Fh
                int     10h                     ; AL := current BIOS video
                                                ;        mode

                and     al,7
                cmp     al,7                    ; jump if monochrome
                je      L61                     ;  (mode 7 or 0Fh)

                cmp     Display0Type[di],MDADisplay
                jne     L63                     ; exit if Display0 is color
                jmp     short L62

L61:            cmp     Display0Type[di],MDADisplay
                je      L63                     ; exit if Display0 is
                                                ;  monochrome

L62:            mov     ax,Device0              ; make Device0 currently
                                                ;  active
                xchg    ax,Device1
                mov     Device0,ax

L63:            ret

FindActive      ENDP

;
; FoundDevice
;
;       This routine updates the list of subsystems.
;

FoundDevice     PROC    near                    ; Caller:  AH = display #
                                                ;          AL = subsystem #
                                                ; Destroys: BX
                lea     bx,Video0Type[di]
                cmp     byte ptr [bx],0
                je      L71                     ; jump if 1st subsystem

                lea     bx,Video1Type[di]       ; must be 2nd subsystem

L71:            mov     [bx],ax                 ; update list entry
                ret

FoundDevice     ENDP

_TEXT           ENDS


_DATA           SEGMENT word public 'DATA'

EGADisplays     DB      CGADisplay      ; 0000b, 0001b  (EGA switch values)
                DB      EGAColorDisplay ; 0010b, 0011b
                DB      MDADisplay      ; 0100b, 0101b
                DB      CGADisplay      ; 0110b, 0111b
                DB      EGAColorDisplay ; 1000b, 1001b
                DB      MDADisplay      ; 1010b, 1011b

DCCtable        DB      0,0             ; translate table for INT 10h func
                                        ;  1Ah
                DB      MDA,MDADisplay
                DB      CGA,CGADisplay
                DB      0,0
                DB      EGA,EGAColorDisplay
                DB      EGA,MDADisplay
                DB      0,0
                DB      VGA,PS2MonoDisplay
                DB      VGA,PS2ColorDisplay
                DB      0,0
                DB      MCGA,EGAColorDisplay
                DB      MCGA,PS2MonoDisplay
                DB      MCGA,PS2ColorDisplay

TestSequence    DB      ?               ; this list of flags and addresses
                DW      FindPS2         ;  determines the order in which
                                        ;  this program looks for the
EGAflag         DB      ?               ;  various subsystems
                DW      FindEGA

CGAflag         DB      ?
                DW      FindCGA

Monoflag        DB      ?
                DW      FindMono

NumberOfTests   EQU     ($-TestSequence)/3

_DATA           ENDS

                END



���������������������������������������������������������������������������
Listing C-2.  Calling VideoID from a C program.
���������������������������������������������������������������������������

/* Listing C-2 */

main()
{
        char    *SubsystemName();
        char    *DisplayName();

        static struct
        {
          char  Subsystem;
          char  Display;
        }
        VIDstruct[2];


        /* detect video subsystems */

        VideoID( VIDstruct );


        /* show results */

        printf( "Video subsystems in this computer:" );

        printf( "\n  %s (%s)", SubsystemName(VIDstruct[0].Subsystem),
          DisplayName(VIDstruct[0].Display) );

        if ( VIDstruct[1].Subsystem )
          printf( "\n  %s (%s)", SubsystemName(VIDstruct[1].Subsystem),
            DisplayName(VIDstruct[1].Display) );
}


char *SubsystemName( a )
char    a;
{
        static char *IBMname[] =
        {
          "(none)",
          "MDA",
          "CGA",
          "EGA",
          "MCGA",
          "VGA"
        };

        static char *Hercname[] =
        {
          "HGC",
          "HGC+",
          "InColor"
        };

        if ( a & 0x80 )
          return ( Hercname[a & 0x7F] );
        else
          return( IBMname[a] );
}


char *DisplayName( d )
char    d;
{
        static char *name[] =
        {
          "(none)",
          "MDA-compatible monochrome display",
          "CGA-compatible color display",
          "EGA-compatible color display",
          "PS/2-compatible monochrome display",
          "PS/2-compatible color display"
        };

        return( name[d] );
}




Index


References to source code listings and illustrative figures are in italics.


A
����������������������������������������������������������������������
Adapter. See Video adapter
Algorithm. See Circle; Ellipse; Line; Region fill
All Points Addressable (APA) modes
Alphanumeric mode. See Video modes, alphanumeric
Alternate select. See Interrupt 10H, function 12H
Analog video signals. See also Video DAC
Animation
Application Program Interface (API)
Aspect ratio
Assembly language
Attribute Controller (EGA and VGA)
   programming
   registers
Attributes
   alphanumeric mode
      CGA
      EGA
      HGC
      HGC+
      InColor Card
      MCGA
      MDA
      VGA
   graphics mode
      CGA
      EGA
      HGC
      InColor Card
      MCGA
      VGA


B
����������������������������������������������������������������������
BASIC
BIOS (Basic Input/Output System). See also Interrupt 10H
   about
   anomalies
   data area
      about
      Alphanumeric Character Set Override
      Display Combination Code table
      Graphics Character Set Override
      Parameter Save Area
      SAVE POINTER table
      SECONDARY SAVE POINTER table
      User Palette Profile table
      Video Parameter table
   hardware supported by
   planar
   programming interface
   status
   video modes
      determining
      establishing
Bit block. See also CGA; EGA; HGC; MCGA; VGA
   about
   animation
   tiling
Bit planes
   about
   layering
   programming
      EGA
      InColor Card
      VGA
   write-protecting
      EGA
      InColor Card
Blanking
   horizontal
   vertical
Blinking. See also Interrupt 10H, function 10H
   alphanumeric mode
      CGA
      EGA
      InColor
      MDA
   graphics mode
Border (region fill). See Region fill
Border (video display). See Overscan
Bresenham, J.E.


C
����������������������������������������������������������������������
C language
Cathode ray tube. See CRT Controller
CGA (Color Graphics Adapter)
   about
   alphanumeric mode
      attributes
      character generator
      data representation
      snow
   BIOS
   graphics I/O
      bit block
      circle
      ellipse
      line
      pixel
      text
   graphics mode
      attributes
      character generator
      data representation
Character definition tables
   alphanumeric mode
      address map
      BIOS
      dedicated ROM
      format
   graphics mode
      BIOS
      format
Character generator. See also Character definition tables
   alphanumeric mode
   graphics mode
Character string. See Text
Circle. See also Ellipse
   algorithm
   clipping
Code page
Cohen, D.
Color Select register (VGA)
Compaq
Composite video
Configuration Switch (Hercules)
CRT Controller (CRTC)
   about
   programming
   registers
      CGA
      EGA
      Hercules
      MCGA
      MDA
      VGA
   status (see CRT Status register)
   timing
      computations
      horizontal
      vertical
   write-protecting
CRT Status register
Cursor
   alphanumeric
      emulation by BIOS
      invisible
      location
      size
   graphics


D
����������������������������������������������������������������������
DAC. See Video DAC
Decoder gate array (InColor Card)
DGIS (Direct Graphics Interface Standard)
Digital video signals
Display
   active
   interference (see Snow)
   refresh
   switching
      BIOS support for
      configurations (see  Dual-display configurations)
   video
Display Combination Code
Display Enable
DOS. See MS-DOS
Dot clock
Dual-display configurations


E
����������������������������������������������������������������������
EGA (Enhanced Graphics Adapter)
   about
   alphanumeric mode
      attributes
      character generator
EGA, alphanumeric mode
       data representation
   BIOS
   graphics I/O
      bit block
      circle
      ellipse
      line
      pixel
      region fill
      text
   graphics mode
      attributes
      character generator
      data representation
Ellipse
   algorithm
      derivation
      optimization
      scan-conversion
   clipping
   implementation
Enable Blink bit
Encoder gate array (InColor Card)
EQUIP_FLAG
Exception register (InColor Card)


F
����������������������������������������������������������������������
Fill. See Region fill; Video buffer, fill
Font pages (MCGA)
FORTRAN


G
����������������������������������������������������������������������
GDI (Graphics Device Interface)
GKS (Graphical Kernel  System)
Global data area
GRAFTABL
GRAPHICS
Graphics Controller (EGA  and VGA)
   about
   programming
   read/write modes
      read mode 0
      read mode 1
      write mode 0
Graphics Controller, read/write
      write mode 1
      write mode 2
      write mode 3
   registers
Graphics Memory Expansion Card (EGA)
Graphics mode. See Video mode, graphics
Graphics window (alphanumeric mode)
Gray-scale
   palette
   summing
GWBASIC. See BASIC


H
����������������������������������������������������������������������
Hercules Color Card
Hercules InColor Card. See InColor Card
HGC (Hercules Graphics Card)
   about
   alphanumeric mode
      attributes
      character generator
      data representation
      determining
   graphics I/O
      bit block
      circle
      ellipse
      line
      pixel
      text
   graphics mode
      attributes
      character generator
      data representation
      determining
      establishing
HGC+ (Hercules Graphics  Card Plus)
   about
   alphanumeric mode
      attributes
      character generator
      data representation
      determining
      establishing
   graphics I/O (see also HGC)
      pixel
   graphics mode (see HGC)
Horizontal sync. See Retrace, horizontal


I
����������������������������������������������������������������������
IBM PS/2. See PS/2
InColor Card
   about
   alphanumeric mode
      attributes
      character generator
      data representation
      determining
      establishing
   compared with EGA
   graphics I/O
      bit block
      circle
      ellipse
      line
      pixel
      text
   graphics mode
      attributes
      character generator
      data representation
      determining
      establishing
Inline code
Intel 8259A
Intel 82786
Intensity. See also Attributes
Interface. See Layered interface; Subroutines
Interrupt. See Print screen
Interrupt 0AH. See Vertical interrupt
Interrupt 10H
   about
   functions
      00H (Select Video Mode)
      01H (Set Alphanumeric Cursor Size)
      02H (Set Cursor Location)
      03H (Return Cursor Status)
      04H (Return Light Pen Position)
      05H (Select Video Page)
      06H (Scroll Up)
      07H (Scroll Down)
      08H (Return Character Code and Attribute at Cursor)
      09H (Write Character and Attribute at Cursor)
Interrupt 10H, functions
      0AH (Write Character(s) at Cursor Position)
      0BH (Set Overscan Color, Select 4-Color Palette)
      0CH (Store Pixel Value)
      0DH (Return Pixel  Value)
      0EH (Display Character in Teletype Mode)
      0FH (Return Current Video Status)
      10H (Set Palette Registers, Set Intensity/Blink Attribute)
      11H (Character Generator Interface)
      12H (Video Subsystem Configuration)
      13H (Display Character String)
      1AH (Video Display Combination)
      1BH (Video BIOS Functionality/State Information)
      1CH (Save or Restore Video State)
Interrupt 1DH
Interrupt 1FH
Interrupt 43H
I/O Support Gate Array  (MCGA)


K
����������������������������������������������������������������������
Kappel, M.R.


L
����������������������������������������������������������������������
Language. See Assembly language; BASIC; C language; FORTRAN; Pascal
Latch
   graphics mode
      EGA and VGA
      InColor Card
   vertical interrupt
Latch Protect register (InColor Card)
Layered interface
   about
   BIOS
   DGIS
   GDI
   GKS
   VDI
Light pen
   about
   programming
Line
   algorithm
      derivation
      optimization
      scan-conversion
   clipping
   implementation
      CGA
      EGA
      HGC
      InColor Card
      MCGA
      VGA
Line-adjacency graph (LAG)
Linking


M
����������������������������������������������������������������������
Macintosh
MCGA (Multi-Color  Graphics Array)
   about
   alphanumeric mode
      attributes
      character generator
      data representation
   BIOS
   graphics I/O
      bit block
      circle
      ellipse
      line
      pixel
      text
   graphics mode
      attributes
      character generator
      data representation
MDA (Monochrome Display Adapter)
   about
   attributes
   BIOS
   character generator
   data representation
Memory Controller Gate Array (MCGA)
Memory-resident program
Microsoft Windows
Miscellaneous Output register (EGA)
Mode Control register
   CGA
   Hercules
   MCGA
   MDA
Monitor. See Display, video
Motorola 6845
MS-DOS


N
����������������������������������������������������������������������
Novak, M.


O
����������������������������������������������������������������������
Optimization
   alphanumeric mode
   graphics mode
      ellipse
      line
      region fill
      text
   techniques
Overscan
   color
   EGA
   horizontal
   vertical


P
����������������������������������������������������������������������
Palette. See also Attributes
   colors
   programming
      BIOS
      CGA
      EGA and VGA
      InColor Card
      MCGA
Panning (EGA and VGA)
Pascal
Pavlidis, T.
Personal System/2. See PS/2
Pixel
   attributes
      CGA
      EGA
      HGC
      InColor Card
      MCGA
      VGA
   connectivity
   coordinates
      computation of
      scaling
   logical operations
      about
      AND
      NOT
      OR
      XOR
   representation
      in video buffer
      on screen
   value (see also individual names of adapters)
      reading
      setting
Plane Mask register (InColor Card)
Print screen
   alphanumeric mode
   BIOS
   graphics mode
Programmable interrupt controller (PIC)
Programming language. See Assembly language; BASIC; C language; FORTRAN;
   Pascal
PS/2
   Display Adapter. See VGA; VGA Adapter
   Model 25. See also MCGA
   Model 30. See also MCGA
   Models and 80. See also VGA


R
����������������������������������������������������������������������
Raster
Read mode. See Pixel, value, reading
Read/Write Color register (InColor Card)
Read/Write Control register (InColor Card)
Refresh. See Display, refresh
Regeneration buffer. See Video buffer
Region fill
   about
   algorithms
      border trace
      line adjacency
      recursive
      using horizontal lines
   optimization
   scan-conversion
Retrace
   horizontal
   vertical
Reverse video. See also Attributes
RGB (Red, Green, Blue)
ROM BIOS. See BIOS
Rubberbanding


S
����������������������������������������������������������������������
Save area
   BIOS (see BIOS, data area)
   display switch
   video state
SAVE POINTER table. See BIOS, data area
Scaling factors
Scan-conversion
   about
   ellipse
   line
   region
Screen dump. See Print screen
Scrolling
SECONDARY SAVE POINTER table. See BIOS, data area
Sequencer (EGA and VGA)
   programming
   registers
Shani, U.
Shift-PrtSc
Snapshot. See Print screen
Snow
Split screen (EGA and VGA)
Sproull, R.F.
Status
   BIOS (see BIOS, status)
   CRT Controller
   register (see CRT Status register)
   video subsystem
Subroutines
   linking
   memory models
   parameter passing
   structure
      BASIC
      C
      FORTRAN
      Pascal
Sutherland, I.E.


T
����������������������������������������������������������������������
Text. See also Attributes
   alphanumeric mode
   graphics mode (see names of individual adapters)
Tiling. See Bit block, tiling
TMS34010


U
����������������������������������������������������������������������
Underline attribute.
   See also Attributes, alphanumeric mode


V
����������������������������������������������������������������������
Van Aken, J.R.
Van Dam, A.
VDI (Virtual Device Interface)
Vertical interrupt
   EGA
   MCGA
   VGA
Vertical sync. See Retrace, vertical
VGA (Video Graphics Array)
   about
   alphanumeric mode
      attributes
      character generator
      data representation
   BIOS
   graphics I/O
      bit block
      circle
      ellipse
      line
      pixel
      text
   graphics mode
      attributes
      character generator
      data representation
VGA Adapter. See  also VGA
Video adapter. See also names of individual adapters
Video bandwidth. See Dot clock
Video BIOS. See BIOS
Video buffer
   about
   address map
   CPU access
   data representation
      alphanumeric mode
      graphics mode
   fill
      about
      CGA
      EGA
      HGC
      InColor Card
      MCGA
      VGA
   paging
   panning
   resizing
   scrolling
   split screen
Video Control Data Area. See Video Display Data Area
Video DAC (Digital-to-Analog Converter)
   about
   programming
Video display
   about
   resolution
Video Display Data Area (VDDA)
   about
   format
   variables
      ACTIVE_PAGE
      ADDR_6845
      CRT_COLS
      CRT_LEN
      CRT_MODE
      CRT_MODE_SET
      CRT_PALETTE
      CRT_START
      CURSOR_MODE
      CURSOR_POSN
      INFO
      INFO_3
      POINTS
      ROWS
      SAVE_PTR
Video Formatter (MCGA)
Video modes
   about
   alphanumeric
      about
      attributes
      data representation
   BIOS interface
   determining
      BIOS
      Hercules
   establishing
   graphics
      about
      attributes
      data representation
   hardware control
Video monitor. See Video display
Video page. See Video buffer, paging


W
����������������������������������������������������������������������
Window
   alphanumeric mode
   Microsoft Windows
Write mode. See Pixel, value, setting


X
����������������������������������������������������������������������
xMode register