11.4 PCX Graphic File Format

The PCX format is a relatively simple format that provides a minimum of compression using Run Length Encoding (RLE). RLE means that the file can be read from start to finish in one pass and encoded or decoded without any holistic information (i.e., in order to figure out what the next encoded byte is, you only have to know what preceded it, not anything after it.) The PCX format is especially useful for 320x200x256 VGA mode 13h (where each pixel is stored as a byte). The PCX format was originally used by PC Paintbrush.

11.4.1 RLE Encoding

The following discussion assumes 320x200x256 VGA mode 13h, as described in Section 11.2

Two types of bytes are stored in the data image portion of a PCX file. One type is a length, and the other is color. A length byte is specified by the two upper bits being set. This limits the length specified by a length byte to 64. The other type is a color byte, and specifies a value for the byte from the palette table (the palette holds the actual RGB values of the color, and the color byte is an index into this table). This is the same method used in mode 13h. The first byte from the data is read. If the two upper bits are set, then it is a length byte, and the next byte is the color which will be replicated as many times as stated by the length byte, from left to right on the screen, ending at the end of a line (see BYTES_PER_LINE below). If the two bits are not set, then it is a color byte, and it goes onto the screen in the next location (left to right) as is.

Note: Any color greater than or equal to 192 cannot be stored as a single color byte, and must be a given a length first. For instance, if you have a single byte of color 192, then it must be represented by two bytes of 193 (length byte of 1) and 192 (color byte 192).

11.4.2 PCX File Format

The PCX file itself contains two parts--the first part is called the header, which contains information about the image; the second part is the image data, which contains actual image data and color information. Rather than explain each field of the header in detail, a structure is shown below which gives a brief glance at the purpose of each field.

STRUC PCX_Header
.Manufacturer   resb    1       ; should always be 0Ah
.Version        resb    1       ; (1)
.Encoding       resb    1       ; should always be 01h
.BitsPerPixel   resb    1       ; (2)
.XMin           resw    1       ; image width = XMax-XMin
.YMin           resw    1       ; image height = YMax-YMin
.XMax           resw    1
.YMax           resw    1
.VertDPI        resw    1       ; (3)
.Palette        resb    48      ; (4)
.Reserved       resb    1
.ColorPlanes    resb    1       ; (5)
.BytesPerLine   resw    1       ; (6)
.PaletteType    resw    1
.HScrSize       resw    1       ; only supported by
.VScrSize       resw    1       ; PC Paintbrush IV or higher
.Filler         resb    56
ENDSTRUC
(1)
PCX version number. It corresponds to the following PC Paintbrush versions and/or features:
0 -- Version 2.5
2 -- Version 2.8, palette included
3 -- Version 2.8, use default palette
5 -- Version 3.0 or better
(2)
Number of bits of color used for each pixel.
1 -- Monochrome
4 -- 16 colors
8 -- 256 colors
24 -- 16.7 million colors
(3)
Vertical resolution, in DPI (dots per inch).
(4)
If 16 colors or less, contains the color palette.
(5)
Number of color planes:
4 -- 16 colors
3 -- 24 bit color (16.7 million colors)
(6)
Number of bytes per line (the width of the image in bytes). For 320x200, 256-color images, this is 320 bytes per line.

In a PCX file containing 16 colors of less, the palette is contained in the .Palette section of the header. In a PCX file containing 256 colors, the palette is at the end of the file, and takes up the last 768 bytes (256 * 3 bytes per color RGB). If the last 768 bytes is a palette, there is a padding byte preceding it in the file (whose value is 12).

Example 11-1. Displaying a PCX File

EXTERN  kbdin, dosxit           ; LIB291 functions

SEGMENT ScratchSeg
ScratchPad      resb 65535

SEGMENT stkseg STACK
        resb    64*8
stacktop:
        resb    0

SEGMENT code

PCX1    db      'my_pcx1.pcx', 0        ; Filenames
PCX2    db      'my_pcx2.pcx', 0        ; (Must end with 0 byte)

..start:
        mov     ax, cs          ; Set up data and stack segments
        mov     ds, ax
        mov     ax, stkseg
        mov     ss, ax
        mov     sp, stacktop

MAIN:
        ; Sets up mode 13h and clears screen
        mov     ax, 0013h
        int     10h

        mov     dx, pcx1        ; Filename to display
        call    ShowPCX         ; Display PCX file to screen

        ; Wait for keypress
        call    kbdin

        ; Go back to text mode
        mov     ax, 0003h
        int     10h

        ; Return to DOS
        call    dosxit

;-----------------------------------------------------------------------------
; ShowPCX procedure by Brandon Long,
;   modified by Eric Meidel and Nathan Jachimiec,
;   converted to NASM, cleaned up, and better commented by Peter Johnson
; Inputs: DX has the offset of PCX filename to show.
; Output: PCX file displayed (all registers unchanged)
; Notes:  Assumes PCX file is 320x200x256.
;         Uses ScratchSeg for temporary storage.
;         The PCX file must be in the same directory as this executable.
;-----------------------------------------------------------------------------
ShowPCX
        push    ax              ; Save registers
        push    bx
        push    cx
        push    si
        push    di
        push    es

        mov     ax, 3D00h
        int     21h             ; Open file
        jc      .error          ; Exit if open failed

        mov     bx, ax          ; File handle
        mov     cx, 65535       ; Number of bytes to read
        mov     ax, ScratchSeg  ; DS:DX -> buffer for data
        mov     ds, ax
        mov     dx, ScratchPad
        mov     si, dx
        mov     ah, 3Fh
        int     21h             ; Read from file

        mov     ax, 0A000h      ; Start writing to upper-left corner
        mov     es, ax          ; of graphics display
        xor     di, di

        add     si, 128         ; Skip header information

        xor     ch, ch          ; Clear high part of CX for string copies

.nextbyte:
        mov     cl, [si]        ; Get next byte
        cmp     cl, 0C0h        ; Is it a length byte?
        jb      .normal         ;  No, just copy it
        and     cl, 3Fh         ; Strip upper two bits from length byte
        inc     si              ; Advance to next byte - color byte
        lodsb                   ; Get color byte into AL from [SI]
        rep stosb               ; Store to [ES:DI] and inc DI, CX times
        jmp     short .tst

.normal:
        movsb                   ; Copy color value from [SI] to [ES:DI]

.tst:
        cmp     di, 320*200     ; End of file? (written 320x200 bytes)
        jb      .nextbyte

        mov     cl, [si]
        cmp     cl, 0Ch         ; Palette available?
        jne     .close

        ; Set palette using port I/O
        mov     dx, 3C8h
        mov     al, 0
        out     dx, al
        inc     dx              ; Port 3C9h
        mov     cx, 256*3       ; Copy 256 entries, 3 bytes (RGB) apiece
        inc     si              ; Skip past padding byte

.palette:
        lodsb
        shr     al, 1           ; PCX stores color values as 0-255
        shr     al, 1           ;  but VGA DAC is only 0-63
        out     dx, al
        dec     cx
        jnz     .palette

.close:
        mov     ah, 3Eh
        int     21h             ; Close file

.error:
        pop     es              ; Restore registers
        pop     di
        pop     si
        pop     cx
        pop     bx
        pop     ax
        ret