The VGA and 256 Colors

Copyright Dr. William T. Verts April 28, 1996

This document contains some notes for those of you who use the VGA on a PC compatible. Most of the time, in Turbo Pascal or Turbo C, the Borland .BGI is sufficient for doing graphics, but when you need 256 colors there isn't really a "standard" .BGI file for 320x200 mode. There are a few versions out on the net, but it turns out that if you are doing simple graphics that doesn't have to be compatible across all graphics adapters, then it is really pretty easy to write your own code for this one graphics mode.

In 320x200 256-color mode, the screen is an array of 64000 contiguous bytes, arranged as 200 rows of 320 bytes, with one byte per pixel, located in memory at absolute address $A000:$0000. So, if you can convince the adapter to go into this mode, plotting stuff on the screen is as easy as poking bytes into memory at the appropriate offset from $A000:$0000.

If we can get into graphics mode, we must also be able to get out of graphics mode and go back to text mode.

The other issue to deal with is that of the palette, or color look-up table. This is a list of 256 byte-triples, stored in the VGA card. We must be able to get the current palette from the VGA card, and also tell the VGA card to use a palette that we have created.

These five problems outlined above require some detailed knowledge of the VGA adapter in this mode. These cases require that we either use some assembly language, or some non-standard procedure calls, or both. In summary, these problems are:

Getting into Graphics Mode

There is no specific call to get into this graphics mode, but we can call the assembly language code to switch the adapter. There are two main methods. In direct 8088 assembly language (or using the ASM directive in Turbo Pascal), you use the following instructions:

MOV AX, 13H INT 10H The other way is to use the support routines for assembly language provided in Turbo Pascal, which effectively does the same thing. The "Registers" type is declared in the DOS unit. "FillChar" can fill a block of memory with the same value quickly, as long as you know the address of the block, the number of bytes to change, and the value to change them to. The code is as follows:

Procedure InitGraph ; Var R : Registers ; Begin FillChar (R, SizeOf(R), 0) ; R.AX := $0013 ; Intr ($10, R) ; End ;

Getting Out of Graphics Mode

Getting back to text mode is as easy as getting into graphics mode. The first way is to use the assembly language:

MOV AX, 03H INT 10H You can also use the "Registers" type, and do the same thing from Pascal, as in the following code:

Procedure CloseGraph ; Var R : Registers ; Begin FillChar (R, SizeOf(R), 0) ; R.AX := $0003 ; Intr ($10, R) ; End ;

The VGA Palette Type

Working with the palettes is a little bit trickier. First off, you need to have a type declared that is assignment compatible with the palette registers in the VGA card. This can be done with the Color_Table type as follows (the record type Color_Type is the same as in earlier discussions):

Color_Type = Record Red : Byte ; Green : Byte ; Blue : Byte ; End ; Color_Table = Array [Byte] Of Color_Type ; Note that this is not exactly the same as the Palette type discussed in Dr. Bill's Notes on Palettes, but is structurally compatible with the array component of the Palette type. If you are clever, you can use the Color_Table definition in the Palette type.

There is another "gotcha" as well when dealing with the VGA. Even though the Color_Type record contains three bytes, the VGA only uses the lower six bits of each byte for the color value. This gives 64 variations per color instead of 256 (64x64x64=262144 possible colors), and means that the values should be shifted left by 2 bits (multiplied by 4) after they are extracted from the VGA to get approximately correct byte values for true RGB color, and shifted right by 2 bits (divided by 4) before they are placed into the VGA.

Fetching the Current VGA Palette

This is a little bit more complicated than simply going into or out of graphics mode, as there are a lot more registers to establish. Certainly this can be done very simply in assembly language, but it is as easy in Pascal, as follows: Procedure Get_Palette (Var P:Color_Table) ; Var R : Registers ; I : Byte ; Begin FillChar (R, SizeOf(R), Zero) ; (* Initialize Record *) With R Do Begin AX := $1017 ; (* Command to get Palette *) BX := 0 ; (* Starting Palette Entry *) CX := 256 ; (* Number of Entries *) ES := Seg(P) ; (* Address of Palette *) DX := Ofs(P) ; (* Address of Palette *) End ; Intr ($10, R) ; (* Do the command *) (*--------------------------------------------------------*) (* Turn 6 bit VGA palette entries into 8 bit RGB bytes *) For I := 0 To 255 Do With P[I] Do Begin Red := Red SHL 2 ; Green := Green SHL 2 ; Blue := Blue SHL 2 ; End ; End ;

Creating a New VGA Palette

Going the other direction and placing a new palette into the VGA is almost identical to extracting the current palette from the VGA. The assembly language command is very similar, and color byte values must be shifted right by 2 bits to convert them into the 6 bit VGA values before storing them in the VGA. Procedure Put_Palette (P:Color_Table) ; Var R : Registers ; I : Byte ; Begin (*--------------------------------------------------------*) (* Turn 8 bit RGB values into 6 bit VGA palette entries *) For I := 0 To 255 Do With P[I] Do Begin Red := Red SHR 2 ; Green := Green SHR 2 ; Blue := Blue SHR 2 ; End ; (*--------------------------------------------------------*) FillChar (R, SizeOf(R), Zero) ; (* Initialize Record *) With R Do Begin AX := $1012 ; (* Command to put Palette *) BX := 0 ; (* Starting Palette Entry *) CX := 256 ; (* Number of Entries *) ES := Seg(P) ; (* Address of Palette *) DX := Ofs(P) ; (* Address of Palette *) End ; Intr ($10, R) ; (* Do the command *) End ;

The Memory Layout

Luckily for us, the screen 320x200 mode is under the 65536 byte limit of a single data structure on an Intel machine. This means that we can easily treat the 64000 bytes of the screen as a single array, without having to do any fancy paging techniques. Since we know the size, layout, and base address in memory of the screen, our additional constant, type, and variable declarations become fairly simple:

Const Zero = 0 ; GetMaxX = 319 ; GetMaxY = 199 ; Type VGA_Linear = Array [0..63999] Of Byte ; VGA_Array = Array [Zero..GetMaxY, Zero..GetMaxX] Of Byte ; Var Screen_Linear : VGA_Linear Absolute $A000:$0000 ; Screen_Array : VGA_Array Absolute $A000:$0000 ; Note that we have two different arrangements of the arrays at location $A000:$0000. The first one is a simple single dimension array, and the second is a two dimensional array. The first can be used to flood the entire screen with a single value (as in clearing the screen), and the second allows us to address individual pixels.

Saving and Restoring the Screen

With the types above, it becomes a trivial matter to capture the entire screen, then restore it later. All we need is a local array of the correct type (or a pointer to that type, since it is easier to allocate the storage off of the heap than to use all 64K of the free variable area for a single data structure), then saving and restoring the screen are each a variable-to-variable copy, as follows:

Type VGA_Pointer = ^VGA_Array ; Var Save_Screen : VGA_Pointer ; (*-----*) New (Save_Screen) ; (* Allocate the storage *) Save_Screen^ := Screen_Array ; (* Capture the screen *) Screen_Array := Save_Screen^ ; (* Restore the screen *) Dispose (Save_Screen) ; (* Deallocate the storage *) Then, of course, a file of screens means that a simple form of animation can be achieved by reading frames one at a time from the file and blasting them straight into the video buffer: Type Screen_File = File Of VGA_Array ; Var Infile : Screen_File ; Temp : VGA_Pointer ; (*-----*) New (Temp) ; Assign (Infile, 'INFILE.DAT') ; (* Use appropriate DOS filename *) Reset (Infile) ; While Not EOF(Infile) Do Begin Read (Infile, Temp^) ; Screen_Array := Temp^ ; End ; Close (Infile) ; Dispose (Temp) ;

PutPixel and GetPixel Routines

These are the two most critical routines for graphics, and they are both effectively "clipping + array indexing". I think they are pretty much self-explanatory (note that indexing the array with locations X and Y requires that the Y value be the first index):

Procedure PutPixel (X,Y:LongInt ; Color:Byte) ; Begin If (X >= Zero ) And (X <= GetMaxX) And (Y >= Zero ) And (Y <= GetMaxY) Then Screen_Array [Y, X] := Color ; End ; Function GetPixel (X,Y:LongInt) : Byte ; Begin If (X < Zero) Or (X > GetMaxX) Or (Y < Zero) Or (Y > GetMaxY) Then GetPixel := Zero Else GetPixel := Screen_Array[Y, X] ; End ;

Clearing the Screen

Now, we can use the VGA_Linear type and the Screen_Linear definition to set the entire screen to a single color. Certainly we can use a FOR loop, as in the code:

FOR I := 0 To 63999 Do Screen_Linear[I] := Color ; but we can use the extremely fast FillChar procedure of Turbo Pascal to accomplish the same feat: Procedure Fill_Screen (Color:Byte) ; Begin FillChar (Screen_Linear, SizeOf(Screen_Linear), Color) ; End ; Procedure ClearDevice ; Begin Fill_Screen (Zero) ; End ;

Horizontal and Vertical Lines

Because we know the layout of the screen in memory, these specialized line types can be made to run very fast (again leveraging the FillChar procedure in the horizontal line routine). The slow way of performing these routines is to use a FOR loop, as in the case of drawing a horizontal line from (X1,Y) to (X2,Y):

For X := X1 To X2 Do PutPixel (X, Y, Color) ; Once the values are known to be within the array bounds, then the routine can avoid the penalty of clipping inside every call to PutPixel, as in:

FOR X := X1 To X2 Do Screen_Array[Y, X] := Color ; Of course, the fastest method is to perform all of the clipping in the horizontal and vertical line routines, then use FillChar if possible (Lswap is a utility routine, necessary for clipping, and the Exit command immediately returns from the procedure in which it occurs):

Procedure Lswap (Var N1,N2:LongInt) ; Var Temp : LongInt ; Begin Temp := N1 ; N1 := N2 ; N2 := Temp ; End ; Procedure Horizontal_Line (X1,X2,Y:LongInt ; Color:Byte) ; Begin If Y < Zero Then Exit ; If Y > GetMaxY Then Exit ; If X1 > X2 Then Lswap (X1, X2) ; If X1 < Zero Then X1 := Zero ; If X2 > GetMaxX Then X2 := GetMaxX ; If X1 <= X2 Then FillChar (Screen_Array[Y, X1], X2 - X1 + 1, Color) ; End ; Procedure Vertical_Line (X,Y1,Y2:LongInt ; Color:Byte) ; Var Y : Integer ; Begin If X < Zero Then Exit ; If X > GetMaxX Then Exit ; If Y1 > Y2 Then Lswap (Y1, Y2) ; If Y1 < Zero Then Y1 := Zero ; If Y2 > GetMaxY Then Y2 := GetMaxY ; For Y := Y1 To Y2 Do Screen_Array[Y, X] := Color ; End ;
At this point, everything else of importance can be implemented in terms of the routines given above. A general purpose Bresenham line routine calls PutPixel (or Horizontal_Line or Vertical_Line if those special conditions are met), normal circles and ellipses call PutPixel, filled circle and filled rectangle routines call Horizontal_Line, and so on. Certainly, it is possible to tune these routines to the VGA hardware, and if speed is an issue (it always is) the tuning should be done. If, on the other hand, correctness is more important than tuning (it always is), then the rectangle, circle, and other specialized routines can and should be written in terms of the primitives listed here. If necessary, they can be tuned for speed later.

VGA_320.ZIP (15K)
This is a link to a file containing: In the source, tabs are assumed to be every 8 spaces.

Back to the CS 32 Page

Back to Dr. Bill's Page