Programming With VESA BIOS Extensions



August 01, 1996
URL:http://www.drdobbs.com/programming-with-vesa-bios-extensions/184403213

August 1996/Programming With VESA BIOS Extensions

VESA standardizes the software interface to many "super" VGA cards, but nobody said it made programming effortless.


When I first started coding in BASIC on the Apple IIe many years ago, I made a fantasy adventure game called "Fubar's Jubilant Sword," or something. I sat down with a sheet of engineering paper and a pen, marked off the screen dimensions on the page, sketched the graphic for my title screen, and then diligently mapped every little square of the grid that had ink on it to a pixel array in my program -- my very first bitmap.

Things change. Today, a standard VGA-equipped IBM PC can display 256 colors simultaneously, selected from a palette of 16.8 million. It can also achieve a 640x480 pixel screen resolution -- but not while displaying 256 colors. Thus, the standard VGA is a tantalizing beast: if it could only give both color and resolution at once, it could display lifelike images on a 15-inch monitor. But it can't.

Fortunately, a host of "Super" VGA cards appeared on the scene, with resolutions of 640x480x256, 800x600x256, and beyond. Photorealism on a PC

was no longer a fantasy. But these new cards, unlike the IBM-standard VGA, varied from one manufacturer to the next. Writing a program with Super VGA graphics meant writing system-level code for every make of video card your software was likely to encounter.

Someone needed to set a standard for SVGA cards, so the Video Electronics Standards Association (VESA) was formed in 1989 to write one. They created the VESA BIOS Extension (VBE), of which the two major releases are VBE 1.2 (1991) and VBE 2.0 (1994). The VBE specifies that manufacturers bundle their cards with driver software that extends the existing BIOS of an IBM PC. This driver provides a set of VESA-defined extended video BIOS system calls to isolate the programmer from the manufacturer-dependent quirks of the hardware. With the VBE, you have a single interface that works no matter who made the video card.

As the VBE catches on, more and more commercial programs using 640x480x256 display resolutions and higher are appearing on the shelves, awing the public with their photorealistic images and surrealistic renderings. Forget those boring 16-color user interfaces and ugly 320x200 games; I'm going to give you a peek under the hood and see how the VBE works, finishing up with a program that will display a photorealistic image on the screen.

Talking To The VBE

As the term BIOS extension implies, programs access the VBE functions through the IBM PC's BIOS video interface. They make a PC/MS-DOS system call to interrupt 0x10 with the CPU's AH register set to 0x4f, the AL register set to the VBE function number, and any other registers loaded according to the function's requirements. Most of the VBE.C functions ( Listing 1) are little more than shells over these system calls. Figure 1 shows the core function list.

The VBE will return the value 0x004f in the AX register if all went well. Any value other than 0x4f in the AL register means the driver doesn't support the function. AH = 0 represents success, AH = 1 is failure, AH = 2 means the hardware doesn't support the function (VBE 2.0+ only), and AH = 3 means the function is invalid in the current video mode (also VBE 2.0+ only).

Detecting The VESA VBE

Function 0 (Return VBE Controller Information) is the first function any VESA-aware application should call. The application calls this function to see if the VESA driver is present, which version it is, what modes are available, and so on. The C code to call this function looks like this:

union REGS r;
struct SREGS s;

r.x.ax = 0x4f00;
r.x.di = FP_OFF(VbeInfo);
s.es = FP_SEG(VbeInfo);
int86x(0x10,&r,&r,&s);

(The int86x function, or its equivalent, is supplied with PC C compilers to enable programs to call the MS-DOS interrupt functions from C. The REGS and SREGS structures are defined in DOS.H to hold copies of the 80x86 register contents.)

VbeInfo must point to a free block of memory at least 256 bytes long; it must be 512 bytes long if you want the 2.0 extended information as well. The function will treat the block of memory like a VbeInfo_t structure as shown in VESA.H (Listing 2) .To get the function to return the extended fields, initialize the VbeSignature field to the characters "VBE2" before calling the VBE function

When the call returns, the AX register should equal 0x004f, indicating success, and the VbeSignature field should contain the characters "VESA." There are a few other important fields to know in this structure:

The VBE.C function VbeGetVersion forms a wrapper over this system call, returning a Boolean on whether or not the VBE was detected.

Detecting SVGA Mode Support

The previous function indicated which modes the VGA board would support by setting the VideoModePtr field to point to an array of supported mode numbers. But after obtaining this mode list from the previous function, the application still must check to see if a particular mode from the list really can be used. Function 1 (Return VBE Mode Information) answers this question. The call looks like this:


r.x.ax=0x4f01;
r.x.cx=Mode;
r.x.di=FP_OFF(ModeInfo);
s.es=FP_SEG(ModeInfo);
int86x(0x10,&r,&r,&s);

The Mode passed into the CX register is one of the mode numbers from the array of supported modes. Figure 2 lists the VESA-defined modes. As with function 0, the memory block pointed to by ModeInfo must be at least 256 bytes long. The VBE function will treat this block as a ModeInfo_t structure. (This structure is not shown here, but is available electronically. See p.3 for details.) There's a lot of information in this structure, but here are some of the most important fields:

Here's a typical example of how a VESA mode can turn up on the mode list but not actually be available: your VBE may support a mode like 1,280x1,024x256 (most do), but this mode needs one byte/pixel * 1,280 pixels * 1,024 pixels = 1,310,720 bytes, or 1.25 Mb of display memory (1Mb = 1,048,576 bytes). If your card has only has 1Mb video memory installed, the ModeAttributes bit 0 will be clear because you don't have enough memory to run the mode.

The code disk and online services contain a program CHECK.C that uses the VBE.C functions VbeGetVersion and VbeCheckMode to return information about the VBE on the current machine, and then to report on each of the VESA modes supported by the driver.

Initializing the VESA Modes

Now that we've determined that the mode we want to use is available, we can use function 2 (Set VBE Mode) to initialize the mode. Calling function 2 is accomplished as follows:

r.x.ax=0x4f02;
r.x.bx=Mode;
int86(0x10,&r,&r);

This is what the VESA.C function VbeSetMode does. This function can be used to set any of the standard VGA modes as well. If Mode == 3, for example, the code above would restore the computer to text mode.

You get a couple of extras with the VBE function: if you pass Mode|0x8000 into the BX register, you can set the mode without clearing display memory (and the screen); and (for VBE 2.0+ only) if you pass Mode|0x4000 you can initialize the mode to use the linear frame buffer memory model.

Of Memory Models and Windows

Okay, we can detect the driver, check the video modes, and initialize them; time to plot some high-res SVGA pixels. How, exactly, does a program plot hi-res SVGA pixels? The answer, unfortunately, is "it depends." The modes do have one thing in common: each of them map the display to a "window" in main memory; but there are numerous ways of organizing that map. In other words, different video modes use different memory models.

In this article I'll deal with the most common SVGA mode, 0x101 (640x480x256). This mode uses an 8-bit packed pixel memory model, a fairly easy layout to deal with. Display memory begins at the start of the memory window (typically located at A000:0000), with each consecutive byte mapping to each consecutive pixel, going from left to right, top to bottom. The value contained in the byte is the color number for the pixel. These color numbers reference a palette that can contain 256 entries (the number of possible values in a byte). This is where the term "eight-bit color" comes from -- eight bits is one byte.

Other common memory models are as follows: planar is a counterintuitive layout used by VGA mode 0x12; non-chain 4 256-color (or planar 256) is an even more counterintuitive scheme used in VGA mode X; direct color bypasses the palette and writes the color data itself to the memory window, and is used by 15-, 16-, 24-bit color modes, and up.

The ModeInfo block's MemoryModel field tells which memory model the specified mode uses. Most

SVGA graphics modes return either 4 (packed pixel) or 6 (direct color) in this field.

Packed pixel is almost as easy as it gets, but that's a relative assessment: there's still a lot to think about. Remember the ModeInfo fields, WinASegment and WinBSegment? These fields indicate what segment or segments of main memory have been mapped to the display. Typically, WinASegment == 0xa000, the standard video segment for color graphics modes on the IBM PC. In the packed pixel memory model (for mode 0x101), each byte in the video segment corresponds to one pixel on the screen, beginning with pixel (0,0) in the upper-left corner. In general, pixel (x,y) and its corresponding memory offset are related as follows:

Offset(x,y) = Xresolution*y + x
x(Offset) = Offset mod Xresolution
y(Offset) = Offset div Xresolution

Where Xresolution == 640 in mode 0x101. On an IBM PC, there are 65,536 bytes in any segment, the last one at offset 65,535. But:


x(0xffff) = 65,535 mod 640 = 255
y(0xffff) = 65,535 div 640 = 102

So you can only plot pixels from (0,0) to (255,102) before overrunning the video segment, even though the mode 0x101 screen goes all the way to (639,479). To plot pixels beyond (255,102) the memory window must be remapped to a different part of the video display area.

The function that remaps the display is function 5 (VBE Display Window Control), subfunction BH = 0, Set Memory Window. (Subfunction BH = 1 fetches the current window position in the DX register.) Here's how you call function 5:


r.x.ax=0x4f05;
r.h.bh=0;
r.h.bl=Window;
r.x.dx=Position;
int86(0x10,&r,&r);

The BL register tells the VBE which window to remap: BL = 0 means window A, and BL = 1 means window B. The window position passed into the DX register must be in WinGranularity units. WinGranularity is the smallest increment (in Kb) by which a memory window can be shifted. If WinGranularity == 64, then the memory window can start at the first pixel or the 65,536th, but not between them.

The ugly part of all this is that when we move the window from position 0 to position 1, offset 0 in the video segment now corresponds to pixel (256,102), which isn't even the first pixel in the scan line. We need to rethink the relationship between pixel coordinates, window positions, and memory offsets. It comes down to this:


Offset(x,y) = (x+y*Xresolution) mod WinGranularity
Position(x,y) = (x+y*Xresolution) div WinGranularity
x(Offset,Position) = (Position*WinGranularity+Offset) mod Xresolution
y(Offset,Position) =  (Position*WinGranularity+Offset) div Xresolution

Assuming Xresolution == 640, WinASize == 64Kb, and WinGranularity == 64Kb, then it takes about four and a half windows to cover the whole screen:


Position 0: (0,0) to (255,102)
Position 1: (256,102) to (511,204)
Position 2: (512,204) to (127,307)
Position 3: (128,307) to (383,409)
Position 4: (384,409) to (639,479)

One more thing: the ModeInfo block contains a field called WinFuncPtr. WinFuncPtr is a far pointer to the window control function (function 5) just discussed; it's there because calling it this way is faster than calling the function through the VBE interface. Because its arguments are passed in the CPU registers and not pushed on the stack, it must be called from the inline assembler:


_asm
{
    sub bh,bh       ;subfunction bh=0: set memory window
    mov bl,Window   ;bl=window a or b
    mov dx,Position ;dx=position in WinGranularity units
    call  WinFuncPtr  ;call window control function directly
}

Note that this code didn't have to load the AX register the VBE function number. We don't need it here because WinFuncPtr already references function 5 directly. The VESA.C function VbeSetWindow uses the interrupt version for clarity, but when speed counts, use the direct call instead.

Setting the DAC Palette Registers

I mentioned earlier that in an eight-bit, packed pixel mode like VESA mode 0x101, the value of the byte written to memory dictates the color of the pixel on the screen. What happens, roughly, is this: this byte gets fed to the video card's DAC (digital-to-analog converter) chip, which uses it to index a table of 256 RGB triplets (the red, green and blue components that actually make up the color). The chip converts this data to an analog signal that the video monitor can use.

This table, called the DAC color register set or the 256-color palette, can be and frequently is preloaded with a custom color map before anything is drawn to the screen. A photographic-quality 256-color image achieves the smooth gradations in hue and shadow by loading all 256 palette entries with the colors it needs most. A program can load the palette through the VBE or through the BIOS. Since VBE function 9 (Load/Unload Palette Data) only exists in VESA version 2.0+, I'll mention both methods. There isn't much difference between them anyway.

The code to load Count palette registers beginning at register offset First looks like this, using function 9:


r.x.ax=0x4f09;
r.h.bl=0;
r.x.cx=Count;
r.x.dx=First;
r.x.di=FP_OFF(PaletteData);
s.es=FP_SEG(PaletteData);
int86x(0x10,&r,&r,&s);

The BL register specifies subfunction 0, Load Palette Data. The PaletteData array needs to be at least 3*Count bytes long, since the data for each entry is a triplet of red, green, and blue components, in that order. If you set all 256 palette entries, Count == 256, First == 0, and sizeof(PaletteData) == 768.

The standard BIOS version of loading the palette uses video function 0x1012:


r.x.ax=0x1012;
r.x.bx=First;
r.x.cx=Count;
r.x.dx=FP_OFF(PaletteData);
s.es=FP_SEG(PaletteData);
int86x(0x10,&r,&r,&s);

So the VBE and standard BIOS techniques are pretty close. The VESA.C function VbeSetPalette will use the VBE call under 2.0, and the BIOS call under prior versions.

The Moment of Truth

Check out PCX.C (Listing 3) . This (very) scaled-down program uses the VESA.C functions to display a 640x480x256 (mode 0x101) PCX image file. I've extracted this code out of a more comprehensive PCX file viewer I wrote that handled CGA, EGA, MCGA, VGA, as well as eight-, 16-, and 24-bit SVGA pictures. I've tried to make this example as small and concise as I could, so this program will only handle PCX files that can be displayed in mode 0x101.

A quick word about the PCX file format: it was developed by Zsoft Corporation for their PC Paintbrush software, and was a de facto standard for a long time. Today it's being displaced by the GIF (which offers better 256-color compression) and JPEG (which offers excellent true color compression) standards. Though PCX is a poor choice of formats for SVGA images, it is the simplest.

A PCX file begins with a header record, which I've typedef'd as pcx_t. I've abridged the structure of the block a bit to make it as self-explanatory as possible; essentially, the PCX file will have Manufacturer == 10 (i.e., a PCX file), Encoding == 1 (run-length encoded), Planes == 1 (packed pixel), BitsPerPixel == 8 (256 colors), Xmax-Xmin<640, and Ymax-Ymin<480 (no larger than 640x480 pixels). The PCX.C function ReadHeader checks for these values in the header to ensure the viewer can display the requested file.

If the header's Version field is 5, a 256-color custom palette resides at the end of the file. (Just about every 256-color PCX file will have its own palette). The palette record consists of the signature byte 0x0c followed by a 768-byte block of palette RGB data. This block is similar to what gets passed into VBE function 9 (LoadPaletteData), with one exception: the DAC uses only 6-bit RGB components, and the PCX palette contains 8-bit components. To convert from a PCX palette component to a DAC palette component, the viewer just drops the two least significant bits with a right shift. The PCX.C function ReadPalette shows how to read and translate the palette data.

The compressed image lies between the header and the palette data. Decoding the data is simple: fetch a byte and check the two most significant bits. If they're not both set, then the byte represents a single pixel value. If both bits are set, then the low six bits represent the number of following consecutive pixels that take the value of the next byte fetched. This is a simple form of run length encoding. The PCX.C function ReadLine demonstrates the decoding algorithm.

The viewer's logic is pretty straightforward. The program opens the requested file, verifies the header, and translates the palette data. Then it sets the video mode and loads the palette into the DAC. Finally, the viewer enters the read/display loop, which decompresses one line of the image into a buffer and then copies the buffer to the display. When the viewer is done, it uses the VBE to set the video mode back to text mode (VGA mode 3). The code is not meant to be terribly efficient, just illustrative. (When I yanked the code from my file viewer I did a lot of de-optimizing in favor of simplicity.)

Is That All?

Heck, no. I've ignored a bunch of stuff in order to nail down the basics without writing a book. There's plenty of territory left to explore: linear frame buffers and DPMI; other memory models and color models; multiple window segments, scrolling displays, and page flipping...you get the idea.

Graphics technology is a fleet-footed beast. Somewhere in my garage, buried at the bottom of a disintegrating cardboard box, on a crumbling sheet of engineering paper, is my very first bitmap. Big pixels. No color. But back then it added a touch of class to "Fubar's Jubilant Sword."

Or whatever the game was called. It's been about 10,000 years since then.

Standards Information

Programmer's Toolkit: VBE/Core 2.0. Video Electronics Standards Association. This is the most current version of the official VBE standard, can be purchased from VESA (Voice: 408-435-0333).

Zsoft PCX File Format Technical Reference Manual, Revision 5. Zsoft Corporation. This is available from Zsoft's BBS at 404-427-1045 (9600/2400,N81). Also available from Zsoft is the file SHOW_PCX.PAS, a Pascal program that displays CGA, EGA, MCGA, and VGA files.

Chris Krehbiel is a systems programmer with Logicon Syscon Corporation. He has a degree in computer science from Thomas Edison State College, and lives in Williamburg, Virginia. He tinkers with games and graphics and can be reached at [email protected].

August 1996/Programming With VESA BIOS Extensions/Figure 1

Figure 1: VESA VBE Function List (INT 10H)

AX=0x4f00    Return VBE Controller Information
AX=0x4f01    Return VBE Mode Information
AX=0x4f02    Set VBE Mode
AX=0x4f03    Return Current VBE Mode
AX=0x4f04    Save/Restore State
               
    DL=0: Return save/restore buffer size
    DL=1: Save state
    DL=2: Restore state

AX=0x4f05    VBE Display Window Control
               
    BH=0: Set memory window
    BH=1:Get memory window

AX=0x4f06    VBE Set/Get Logical Scan
             Line Length
               
    BL=0: Set Scan Line Length  In Pixels
    BL=1: Get Scan Line Length
    BL=2: Set Scan Line Length In Bytes
    BL=3: Get Maximum Scan Line Length

AX=0x4f07    VBE Set/Get Display Start Control

    BL=0: Set Display Start
    BL=1: Get Display Start
    BL=0x80: Set Display Start 
             During Vert.Retrace

AX=0x4f08    VBE Set/Get DAC Palette Format

    BL=0: Set DAC Palette Format
    BL=1: Get DAC Palette Format

AX=0x4f09    Load/Unload Palette Data
             (2.0+ only)

    BL=0: Set Palette Data
    BL=1: Get Palette Data
    BL=2: Get Secondary Palette Data
    BL=3: Set Secondary Palette Data
    BL=0x80: Set Palette Data During Vertical Retrace

AX=0x4f0a    VBE 2.0 Protected Mode Interface
             (2.0+ only)
               
    BL=0: Return Protected Mode Table

VESA VBE Function Status Word (AX)

AH    Result:
      0: success
      1: failure
      2: unsupported in hardware (2.0+ only)
      3: invalid in current video mode
         (2.0+ only)

AL    Support:
      0x4f: function supported in VBE
      Other: function unsupported

August 1996/Programming With VESA BIOS Extensions/Figure 2

Figure 2: VESA-defined Super VGA Modes


Number    Resolution
0x100    640x480x256
0x101    640x480x256
0x102    800x600x16
0x103    800x600x256
0x104    1024x768x16
0x105    1024x768x256
0x106    1280x1024x16
0x107    1280x1024x256
0x108    80x60x16
0x109    132x25x16
0x10A    132x43x16
0x10B    132x50x16
0x10C    132x60x16
0x10D    320x200x32K
0x10E    320x200x64K
0x10F    320x200x16M
0x110    640x480x32K
0x111    640x480x64K
0x112    640x480x16M
0x113    800x600x32K
0x114    800x600x64K
0x115    800x600x16M
0x116    1024x768x32K
0x117    1024x768x64K
0x118    1024x768x16M
0x119    1280x1024x32K
0x11A    1280x1024x64K
0x11B    1280x1024x16M

August 1996/Programming With VESA BIOS Extensions/Listing 1

Listing 1: VBE wrapper functions


#include <string.h>
#include <dos.h>
#include "vbe.h"    /* exports and data types */

/*------------------ local data -------------------*/

static VbeInfo_t _VbeInfo;  
static ModeInfo_t _ModeInfo;

static int _Width;   /* scan line width in bytes */
static long _Window; /* memory window size in bytes */
static int _Segment; /* mem window segment */

/*--------------- exported functions ---------------*/

int VbeGetVbeInfo(VbeInfo_t far *p)

/* fetches the vbe info block; returns 0 if no vbe. */
{
    union REGS r={0x4f00,0,0,0,0,FP_OFF(p)};
    struct SREGS s={FP_SEG(p)};

    if(!p) return 0;
        
    _fmemset(p,0,sizeof(VbeInfo_t));
    _fmemcpy(p->VbeSignature,"VBE2",4);
    int86x(0x10,&r,&r,&s);

    if(_fmemcmp(p->VbeSignature,"VESA",4)) return 0;
    return r.x.ax==0x4f;
}
int VbeGetModeInfo(int mode,ModeInfo_t far *p)

/* fetches the mode info block for the specified mode 
   number; returns 0 if mode unsupported by vbe. */
{
    union REGS r={0x4f01,0,mode,0,0,FP_OFF(p)};
    struct SREGS s={FP_SEG(p)};

    if(!p) return 0;
    
    _fmemset(p,0,sizeof(ModeInfo_t));
    int86x(0x10,&r,&r,&s);

    return r.x.ax==0x4f;
}
int VbeSetMode(int mode)

/* initializes the requested video mode; returns 0 if 
   there's no vbe or the mode is unavailable. */
{
    union REGS r={0x4f02,mode};

    if(!VbeGetVbeInfo(&_VbeInfo)) return 0;
    
    if(mode>=0x100)    /* svga mode */
    {
        if(!VbeGetModeInfo(mode,&_ModeInfo) || 
            !(_ModeInfo.ModeAttributes&1)) return 0;
    
        _Width=_ModeInfo.BytesPerScanLine;
        _Window=1024L*_ModeInfo.WinSize;    /* convert to bytes */
        _Segment=_ModeInfo.WinASegment;        
    }
    int86(0x10,&r,&r);
    
    return r.x.ax==0x4f;
}
void VbeSetPalette(const char far *p,int start,int n)

/* loads the dac palette registers; uses bios on vbe
   versions before 2.0 */
{
    if(_VbeInfo.VbeVersion<0x200)    /* use bios */
    {
        union REGS r={0x1012,start,n,FP_OFF(p)};
        struct SREGS s={FP_SEG(p)};
        int86x(0x10,&r,&r,&s);
    }
    else    /* use vbe */
    {
        union REGS r={0x4f09,0,n,start,0,FP_OFF(p)};
        struct SREGS s={FP_SEG(p)};
        int86x(0x10,&r,&r,&s);
    }
}
void VbeSetWindow(int window,int position)

/* repositions the indicates memory window to the new
   position (in WinGranularity units). */
{
    union REGS r={0x4f05,window,0,position};
    int86(0x10,&r,&r);
}
void VbeWrite(int x,int y,int bytes,const char far *buffer)

    /* copies the contents of the buffer (<64k) to display, 
    starting at pixel (x,y). */

{
    long absolute=x+(long)y*_Width;      /* absolute offset */
    long position=absolute/_Window;      /* of window */
    long offset=absolute%_Window;        /* of window */
    char far *vram=MK_FP(_Segment,0);    /* to window */

    VbeSetWindow(0,(int)position);
    
    if(offset+bytes>_Window)    /* data overruns window */
    {
        int    n=(int)(_Window-offset);  /* bytes left */
        
        _fmemcpy(vram+offset,buffer,n);  /* display 1st part */
        VbeSetWindow(0,(int)++position); /* move window */
        _fmemcpy(vram,buffer+n,bytes-n); /* display rest */
    }    
    else _fmemcpy(vram+offset,buffer,bytes);/* no overrun */
}
/* End of File */

August 1996/Programming With VESA BIOS Extensions/Listing 2

Listing 2: Structure to hold VBE hardware info


typedef struct
{
    char VbeSignature[4];    /* "VESA" */
    int  VbeVersion;         /* bcd */
    char far *OemStringPtr;  /* asciiz */
    long Capabilities;       /* of video,
                                bitmapped */
    int  far *VideoModePtr;  /* array of
mode
numbers */ int TotalMemory; /* video memory/64kb */ /* vbe 2.0+ */ int OemSoftwareRev; /* bcd */ char far *OemVendorNamePtr; /* asciiz */ char far *OemProductNamePtr; /* asciiz */ char far *OemProductRevPtr; /* asciiz */ char Reserved[222]; /* used by vbe */ char OemData[256]; /* used by vbe */ } VbeInfo_t; /* Not shown: ModeInfo_t structure, VBE function prototypes. These are available on the code disk and via ftp see p. 3 for details -mb */ /* End of File */

August 1996/Programming With VESA BIOS Extensions/Listing 3

Listing 3: PXC file functions


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <conio.h>
#include <dos.h>

#include "vbe.h"    /* vesa vbe interface */

/* --------------- Local Data -------------*/

/* (Abridged) header record of a PCX file */
typedef struct    
{
    char Manufacturer; /* pcx file sig: 10 */
    char Version;      /* pc paintbrsh ver */
    char Encoding;     /* 1: rle */
    char BitsPerPixel; /* color resolution */
    int  Xmin,Ymin;    /* image origin */
    int  Xmax,Ymax;    /* image extent */
    char NotUsed1[53]; /* not used by this
                          program */
    char Planes;       /* number of color
                          planes */
    int  BytesPerLine; /* (per plane) */
    char NotUsed2[58]; /* by this program */
    
} pcx_t;

/* Error codes; these also index ErrorText
   array below */
enum    
{
    PCX_OK,PCX_EOPEN,PCX_EVIDEO,
    PCX_EREAD,PCX_EFORMAT
};

static const char *ErrorText[]=
{
/* PCX_OK */     "Ok",
/* PCX_EOPEN */  "File not found",
/* PCX_EVIDEO */ "Unsupported video mode",
/* PCX_EREAD */  "File read error",
/* PCX_EFORMAT */
    "Invalid or unsupported format"
};

/* PCX image line buffer */
static char Line[640]; 
/* DAC palette data */
static char Palette256[768];
/* PCX file header record */
static pcx_t Header; 
           
/*------ local pcx file functions ---------*/

static int ReadHeader(FILE *f)

/* reads the header of the open file into the
   global Header structure; returns 0 on a
   file read error, or if the file is not a
   valid pcx file, or if the pcx file does
   not contain a 640x480x256-compatible
   image. */
{
    pcx_t *h=&Header;
    
    fread(h,sizeof(pcx_t),1,f);
    return !(ferror(f) ||   
        h->Manufacturer!=10 ||
        h->Encoding!=1 ||
        h->Planes!=1 ||
        h->BitsPerPixel!=8 ||
        h->Ymax-h->Ymin>479 ||
        h->Xmax-h->Xmin>639);
}
static int ReadPalette(FILE *f)
    
/* reads the 256-color palette of the open
   file into the global buffer Palette256,
   and converts it to 6-bit DAC palette
   register data; returns 0 on a file read
   error or an invalid palette block. */
{
    int i;
    fseek(f,-769L,SEEK_END); /* to palette */
    if(fgetc(f)!=12) /* check sig byte */
        return 0; 

    /* read the 256 triplets */

    fread(Palette256,768,1,f); 
    if(ferror(f)) return 0;
    fseek(f,128L,SEEK_SET); /* bk to image */
    for(i=0;i<768;i++)
        Palette256[i]>>=2; /* to 6-bit rgb */
    return 1;
}
static void ReadLine(FILE *f)


/* reads and decompresses the next scanline
   from the current pcx file into the global
   Line buffer. */
{
    int    c,i=0;
    
    while(i<Header.BytesPerLine)
    {
        c=fgetc(f);
        if((c&0xc0)==0xc0) /* is rle hdr */
        {
            c&=~0xc0;   /* =repeat count */
            /* copy repeated pixel data */
            memset(Line+i,fgetc(f),c);
            i+=c;
        }
        else Line[i++]=c; /* is pixel data */
    }
     return;
}

/*----------- general functions -----------*/

int PcxShowFile(const char *file)

/* reads and displays a pcx file; returns 0
   on success, PCX_ error code on failure. */
{
    int    e=PCX_OK;
    FILE *f=fopen(file,"rb");
    if(!f) e=PCX_EOPEN;
    else if(!ReadHeader(f)) e=PCX_EFORMAT;
    else if(Header.Version==5 &&
            !ReadPalette(f)) e=PCX_EFORMAT;
    else if(!VbeSetMode(0x101)) e=PCX_EVIDEO;
    else
    {
        int y,ymax=min(Header.Ymax,479);
        int    n=min(Header.Xmax,640)-
               Header.Xmin+1;
        if(Header.Version==5)
            VbeSetPalette(Palette256,0,256);
        for(y=Header.Ymin;y<ymax;y++)
        {
            ReadLine(f);
            VbeWrite(Header.Xmin,y,n,Line);
        }
    }
    if(f) fclose(f);
    return e;
} 
void PcxDeinit()

/* clears the image and resets the video to
   standard 80-column text mode */
{
    VbeSetMode(3);
}
void main(int argc,char **argv)

/* displays the pcx file specified by the
   first argument to the program
   (usage: PCX <filename>) */
{
    int e=PcxShowFile(argv[1]);
    
    if(!e)
    {
        getch();
        PcxDeinit();
    }
    else puts(ErrorText[e]);
} 
/* End of File */

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.