PCX Format

From ModdingWiki
Jump to: navigation, search
PCX Format
There is no example of an image in this format — upload one!
Format typeImage
HardwareEGA, VGA
Colour depth24-bit (true colour)
Minimum size (pixels)0×0
Maximum size (pixels)65536×65536
PaletteInternal (optional)
Plane count1-255
Transparent pixels?No
Hitmap pixels?No

The PCX Format is an image format used by many games, usually to store full screen (320x200) 16-colour EGA, and later 256-colour VGA (mode 13h), graphics. It was, for a time, also a general picture format like .bmp or .png, and was the primary format used by PC Paintbrush. It declined in popularity after support for 24-bit true colour images was added too late, by which time many people had switched to competing formats like .png and JPEG (the latter offering far better compression for photos.) It also lacks support for transparency, which resulted in it losing some ground to the GIF format which otherwise provided a similar feature set.


The PCX file is composed of two parts, the header and the image data, which is usually compressed. The header is as follows:

Data type Description
UINT8 manufacturer Always 0x0A
UINT8 version PCX Paintbrush version
0 = 2.5
2 = 2.8 w/ palette
3 = 2.8 w/out palette
5 = 3.0 or better
UINT8 encoding Should be 0x01
0 = uncompressed image (allowed, but not much software supports it)
1 = .PCX run length encoding
UINT8 bitsPerPlane Number of bits per pixel in each colour plane (1, 2, 4, 8, 24)
UINT16LE Xmin Window (image dimensions):
Image width = Xmax - Xmin + 1
Image height = Ymax - Ymin + 1
Normally Xmin and Ymin should be set to zero. Note these field values are valid rows and columns, which is why you have to add one to get the actual dimension (so a 200 pixel high image would have Ymin=0 and Ymax=199, or Ymin=100 and Ymax=299, etc.)
UINT16LE VertDPI Vertical resolution, in DPI (dots per inch). Can also be image width.
UINT16LE HorzDPI Horizontal resolution, in DPI (dots per inch). Can also be image height. May be absent.
UINT8 palette[48] For 16 colors or less, entries of RGB for the palette, similar to bitmap palette, but each entry 3 bytes long only. Padded with 0x00 to 48 bytes in total length. See below.
UINT8 reserved Should be set to 0
UINT8 colorPlanes Number of colour planes, e.g. 4 = 16 colors (if bitsPerPlane is 1), 3 = 24-bit true color (if bitsPerPlane is 8)
UINT16LE bytesPerPlaneLine Number of bytes to read for a single plane's scanline, i.e. at least image_width ÷ 8 bits per byte × bitsPerPlane. MUST be an EVEN number. Do NOT calculate from Xmax-Xmin. Normally a multiple of the machine's native word length (2 or 4)
UINT16LE paltype How to interpret palette:
1 = Color/BW
2 = Grayscale (ignored in PC Paintbrush IV/ IV +)
UINT16LE hScrSize Only supported by PC Paintbrush IV or higher; deal with scrolling.
UINT16LE vScrSize
BYTE pad[54] Filler to bring header up to 128 bytes total (56 if no HorzDPI)

Image Data

Image data comes after the header (starting at offset 0x80 in a PCX file), and will be RLE compressed if the header indicated so. The way the data is stored depends on how many colour planes are specified. Each row has its color planes stored sequentially, similar to raw EGA data.

For one plane of eight bits (256-colour), each byte will represent one pixel. For one plane of four bits (16-colour), each byte will represent two pixels. The bits within the byte are in big-endian order, so the most significant bit belongs to the left-most pixel. In other words, a byte of value 0xE4 (binary 11 10 01 00) will have left-to-right pixel values of 3, 2, 1, 0, assuming two bits per pixel.

EGA 16-colour images are often stored with four colour planes instead of one, with each plane being one-bit-per-pixel (think of four black and white images, one each for red, green, blue and intensity.) The planes are stored sequentially for each line (see Row-planar EGA data for the exact details), thus a 320x200 EGA image will store at least 40 bytes for each scanline's colour plane (320 pixels ÷ 8 bits per byte × 1 bit per pixel), with each scanline being at least 160 bytes long (320 pixels ÷ 8 bits per byte × 1 bit per pixel × 4 planes). Note that the scanline length can be larger than expected (40 bytes in this example), especially for images whose width is not a multiple of four. This is because each scanline in a plane is padded to a multiple of two or four bytes, depending on the architecture of the machine used to create the file. The actual size is stored in the bytesPerPlaneLine field in the header, which should always be used instead of calculating the value from the other image attributes.

True colour PCX files are not common, and could be either three planes (R, G and B) of eight bits each (24-bit RGB) or one plane of 24-bits.

The split into planes is generally governed by what is most convenient for the game at the time, which in turn depends on which video mode is being used to display the image. Since EGA video memory is split into planes, 16-colour PCX files are frequently split into matching planes so that no processing is required when loading an image directly into video memory.

RLE Compression

The PCX format uses a form of RLE Compression that is rather unique. It compresses on the byte level and uses a flag system, where the flag is the two highest bits of a byte. If this flag is set (i.e. the two upper bits are set, or in other words the value is >= 192) then the lower six bits are the number of times to repeat the following byte.

Thus the byte pair $C7 $28 means '7 bytes of $28' (the flag value is 192 (128 + 64) so $C7 is 199-192 or a length of 7 bytes, or alternatively using faster logic operations, byte & 0x3F will yield the length value).

This means that the six-bit length values have a maximum of 63. It also means that any value larger than 191 MUST be stored as a length/value pair, which can actually INCREASE the size of the file in some cases. For instance, if you have a single byte of color 192, then it must be represented by two bytes - one of 193 ($C1, length byte of 1) followed by one of 192 ($C0, color byte 192).

It is also worth noting that the byte value $C0 does not have a clearly defined effect. Based on the implementation in the decoding program, this could do any of the following:

  • treat $C0 as a literal byte.
  • ignore $C0 and continue with the following byte.
  • 'repeat the following byte zero times', effectively ignoring any byte following $C0. This could conceivably be used to embed non-image data in the PCX file which would be ignored by any program displaying the image.
  • 'repeat the following byte 65536 times', which is basically a bugged implementation using a "while (--count != 0)" style loop with a 16 bit variable/CPU register.

At any rate, the best way to handle a $C0 when encoding (compressing) is to write the sequence $C1 $C0. Upon decoding (decompressing), a value of $C0 almost always indicates an error in the file.

Note that each scanline is compressed independently - an RLE sequence may span multiple planes, but it will never span more than one row of pixels. Thus when decompressing an image, the RLE algorithm will produce at most bytesPerPlaneLine bytes at a time. Even where the RLE coding could have continued over to the next scanline, it will stop and start again fresh for each line.[1] For example, if the input image is 8×4 pixels EGA 16-colour, and the first two lines of pixels are black (color 0) and the last two are white (color 15), they must be compressed as $C4 $00 | $C4 $00 | $C4 $FF | $C4 $FF and not as $C8 $00 | $C8 $FF.


For images with 16 colours or less, the palette is stored in the header. For images with more colours (i.e. 256-colour images) the header palette is ignored, and the 768-byte VGA Palette (in 8-bit RGB format) is stored after the image data. A single signature byte of 0x0C is included before the palette data begins.

For 16-colour (EGA) images, the palette is comprised of 16 entries of three bytes each. Each byte is the red, green then blue value of the first palette entry, then the red, green and blue values of the second entry, and so on. Each value is between 0 and 255 inclusive.

For 4-colour (CGA) images, the data is in the following structure:

Data type Name Description
UINT8 bg Background colour in the upper four bits. Shift-right by 4 to get a value from 0..15, which matches standard CGA/EGA text-mode colours.
BYTE[2] padding Unused, set to 0.
UINT8 flag CGA palette flag, see below.
BYTE[44] padding Unused, set to 0.

The flag byte is split as:

D7 D6 D5 D4 D3 D2 D1 D0
0 = colour
1 = mono
0 = dark
1 = bright
- - - - -

Source Code


EXTERN  kbdin, dosxit           ; LIB291 functions
SEGMENT ScratchSeg
ScratchPad      resb 65535
        resb    64*8
        resb    0
PCX1    db      'my_pcx1.pcx', 0        ; Filenames
PCX2    db      'my_pcx2.pcx', 0        ; (Must end with 0 byte)
        mov     ax, cs          ; Set up data and stack segments
        mov     ds, ax
        mov     ax, stkseg
        mov     ss, ax
        mov     sp, stacktop
        ; 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.
        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
        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
        movsb                   ; Copy color value from [SI] to [ES:DI]
        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
        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
        mov     ah, 3Eh
        int     21h             ; Close file
        pop     es              ; Restore registers
        pop     di
        pop     si
        pop     cx
        pop     bx
        pop     ax


.PCX files can be read, and occasionally converted by several programs, notably XP's Microsoft Photo editor can do so.

  • XnView is a program that will convert PCX files into any number of formats. Freeware for private non-commercial or educational use.
  • ImageMagick is a cross-platform command-line utility that can convert PCX files, however it is unable to correctly write 16-colour PCX files.

Useful links


  1. http://www.drdobbs.com/pcx-graphics/184402396#01ED_0105