Dr. Roger T. Stevens is a member of the technical staff of the MITRE Corporation, Bedford, MA. He holds a B. A. degree in English from Union College, an M. A. in Mathematics from Boston University, an M. Eng. in Systems Engineering from Virginia Tech and a PhD. in Electrical Engineering from California Western University. Dr. Steven's books Graphics Programming in C and Fractal Programming in C were published by M & T Publishing.
The MS-DOS screen dump program works well with many dot matrix printers; but fails to fully exploit the capabilites of more sophisticated printers such as ink jet or color thermal printers. The software described here dumps an EGA color screen written in text mode to a CalComp Plotmaster color printer, using techniques that can easily be adapted to many other types of printers.
Planning The Display Size
Most printers handle graphics data a line at a time, first receiving characters that define the number of bytes of graphics data to follow, and then the graphics data for a line. Each transmitted bit represents a dot on the paper. The resolution and transmission codes tend to be different for each printer.Most printers have a much higher resolution than the EGA display. Thus, the first step in planning a printer graphics display is to determine how many dots are to be printed for each display pixel. You must compare the printer resolution (obtained from the printer documentation) with the color card resolution. The EGA display has 640 pixels horizontally by 350 pixels vertically, giving an aspect ratio of 4:3. However, the pixels on the EGA do not have the same size in both directions; a pixel's vertical size is 1.37 times its horizontal size. The CalComp Plotmaster printer, for example, has a graphics area of 2000 dots horizontally by 1600 dots vertically in the landscape mode. The dots on the printer are square with the same horizontal and vertical sizes.
With this hardware, three printed horizontal dots can be used to represent each horizontal pixel, resulting in a graphics area of 1920 horizontal dots (640 x 3) and ideally, 1440 vertical dots to keep the proper proportions. Printing four vertical dots for every one in the original display gives 1400 vertical dots, resulting in a display that is close to the proper proportions.
Alternatively, one might use wider margins and print two horizontal dots for each horizontal pixel, giving 1280 printed dots horizontally. To maintain the proper proportions, there should be 960 vertical dots. Unfortunately, the best manageable approximation is three vertical dots for each horizontal pixel, producing 1050 printed dots. Though this will give a larger vertical dimension than desired, it is acceptable in representing a text display: when trying to copy a graphics display, the printer may reproduce circles as ovals.
Alphanumerics Text To Graphics
In the EGA text mode screen, contents are stored beginning at address B8000H. The screen consists of 25 rows of 80 characters. The characters are stored in sequence, using two bytes for each. The first byte is the ASCII value of the character; the second byte is the attribute, which contains color information for both the character and the background.You can't replicate the shape (and possibly the color) of the screen display by just sending characters to the printer. Instead, the printer, operating in graphics mode, must draw a reproduction of the screen display. Thus the screendump program must convert each ASCII character in display memory to the eight by 14 pixel grid which is actually written on the screen. The BIOS contains 14 bytes of shape information for each character. The eight bits in each byte represent the eight character columns, and each represents the one of the 14 character lines.
Each active (light) pixel that is represented by a 1; each inactive pixel by a 0. The beginning address of this character array for the EGA and VGA display is found by issuing interrupt 10H (the video services interrupt), with register AH containing 11H, register AL containing 30H, and register BH containing 02H. The address of the beginning of the character array will be returned in registers ES and BP.
Writing To The Printer
If a printer is assigned to parallel port LPT1, the same fundamental function will work to send a character to the printer regardless of the kind of printer. The function put_out in Listing 1 outputs a character to the printer parallel port data register, then loops repeatedly, reading the status register until the status bit shows that the printer is ready. An 0DH is then sent to the control port to start a strobe and an 0CH to end the strobe. The strobe clocks the character data into the printer's receive buffer.The function print_handler in Listing 1 sends a screenful of data to the printer. To write to the CalComp Plotmaster, for example, four full sets of data are transmitted, one each for red, yellow, blue, and black. The print-handler function initializes the printer for graphics, then performs a loop for each of the four data sets. For each set, information on the beginning location of the display is transmitted. A loop then sends data for each of the 25 display lines. After each color panel (data set) is transmitted, the printer advances to the next one. Finally, the end-of-transmission command ejects the completed picture and returns the printer to the normal mode. The exact codes in these commands differ from printer to printer, but are usually well-documented and easy to implement.
Sending A Row Of Characters In Graphics Mode
The function print_row sends a row of characters to the printer. A loop repeats for each of the 80 characters in a display row. First print_row formulates the address in memory that contains the character to be sent. The actual address of this memory's beginning is B8000H. The 8086 family of microprocessors stores this address in two registers: a segment register containing B000H and a pointer register 8000H. These are combined by multiplying the segment register contents by 16 and adding it to the pointer register contents. Turbo C and other implementations of C represent the address as a long integer consisting of the segment register contents followed by the pointer register contents. The attribute and ASCII value of the character are then read.The information for the characters comprising one display row is stored in a buffer consisting of two bytes for each of the 80 characters. The first byte contains the ASCII value of the character, and the second determines whether the foreground and background colors should be printed. The values in the attribute byte and the color panel determine the value of the second byte. It is zero if neither foreground or background is to be printed, one if the foreground is to be printed but not the background, two if the background is to be printed but not the foreground, and three if both are to be printed.
Once the information for a row of characters has been deciphered and stored, print_row sends the information for the row to the printer. This section starts with a loop that repeats for each of the 14 lines of pixels that make up the character. Since the resolution of the printer is set to print three identical lines of dots for each line of pixels, a loop duplicates the process three times. For each printer line, print_row outputs the characters that specify the raster mode and the number of data bytes that follow. Two bytes of data are then sent for each character.
One of four possible situations are selected by a switch statement. If neither foreground or background is to be printed, two bytes of zeroes are sent to the printer. If only the foreground is to be printed, two bytes in which each pair of bits corresponds to a single bit in the character representation are sent. If only the background color is to be printed, two bytes are sent; a one represents a background pixel and a zero a foreground pixel. If both foreground and background are to be printed, two bytes of 0FFH are sent to the printer, resulting in all pixels being printed.
Replacing The Original Print Screen Function
PrtSc generates interrupt 5. To replace the standard print screen function, we must replace the standard int 5 vector with the address of print_handler. The most significant byte of the int 5 vector is at address 17H. If the original print screen function's address is still in the table, this byte should be 0F0H, (ROM BIOS section of memory) and should be changed to use the new print routine. If the byte contains a different value, the installation program displays the message "Alternate Print Screen Routine Has Already Been Installed" and then terminates. This message will appear if the print routine has already been installed, or if a different specialized print routine has been used.If the new function to be installed, Turbo C's setvect command puts the address of print_handler in the vector table for interrupt 5. The function print_handler is of type interrupt, a special Turbo C type which creates a function that saves all registers at entry and restores them on exit.
The registers are loaded and a geninterrupt command obtains the address of the 8x14 EGA character table from BIOS. This address is placed in the variable new_chars for future use when the interrupt is activated. The message "Text Screen Printing Routine for Plot-Master Installed" is then displayed on the screen.
The Turbo C instruction keep is used to terminate but keep the new print screen interrupt handler resident in memory. The second parameter passed to the keep function is the amount of memory, in paragraphs, reserved for the function being saved. The amount of memory specified should be as little as possible, but not so small as to cut off part of the function. To determine the minimum amount of memory, temporarily comment out the main program and then compile the remainder while generating a map file. (In Turbo C type an ALT-0 (Options), selecting Linker and then Map File Detailed. When compilation is completed, you can examine the map file to get an idea of how much memory the program requires. You should add some memory as a safety factor, convert the result to paragraphs and enter it in the keep statement. With the comment delimiters removed, the program can be recompiled using the tiny memory model. The compiled program should be converted from an .EXE program to a .COM program using the EXE2BIN utility.
MS-DOS is not reentrant, so you must take care in designing of a function to act as an interrupt handler. Portions of the program that are to remain resident and replace the original print screen function must not include any calls to MS-DOS or BIOS routines. If the functions makes calls to MS-DOS or BIOS routines, it may behave unexpectedly when the interrupt is called while another MS-DOS function is active since one stack overlays another. There are ways of including checks and tests to assure that everything is compatible, but the safest method is to write the function without MS-DOS calls.
Making It Work With Turbo C 2.0
The program in Listing 1 will work with Turbo C v1.0 or 1.5. However, it will not work with Turbo C v2.0. In the newer version, Borland has chosen to restore the original values of interrupts 4, 5, and 6 before leaving a Turbo C program. Thus the setvect command puts the address of the new print function into the interrupt table, but as soon as the program terminates and stays resident, the address is changed back to its original value. When the PrtSc key is hit, the original print screen function will run.Borland's documentation doesn't call attention to the change in setvect but the distribution disks do include some (undocumented) files which address the problem.
The assembly file CO.ASM contains the source code used to begin and end a Turbo C program, including the function _restorezero, which restores the original values of the interrupts. Remove all of this function except for the return statement. You cannot remove the entire function because it is called by several other functions. With TASM in the same directory as Turbo C rebuild the startup code by running the batch file BUILD_CO. This batch program will run TASM with all the proper switches set. It's best to use the TINY memory model when compiling. As a result of running this program, a new version of COT.OBJ is created, which replaces the version supplied with Turbo C. After compiling the print program with Turbo C using the new COT.OBJ, the compiled and linked program will work.
This work was supported by the Department of Defense under contract AF F19628-86-C0001.