PCX Image Format

Unit: PCX

PCX (ZSoft Paintbrush) is a classic DOS image format (1985) widely used in retro games like DOOM, Duke Nukem, and Commander Keen. It’s the native export format for Aseprite, making it perfect for modern pixel art workflows.

Overview

  • Format: ZSoft PCX v5 (256-color)

  • Color Mode: 8-bit indexed (256 colors, VGA Mode 13h compatible)

  • Compression: Simple RLE (Run-Length Encoding)

  • Palette: 768-byte RGB triplets at end of file

  • DOS Era: 1985-1995 (extremely period-correct)

Specifications

File Structure

[128-byte header]
[RLE-compressed pixel data]
[0x0C marker byte]          ← Palette marker
[768-byte palette]          ← 256 RGB triplets (0-255 range)

Header (128 bytes)

Offset

Size

Field

Value

0

1

Manufacturer

10 (always)

1

1

Version

5 = 256-color

2

1

Encoding

1 = RLE

3

1

BitsPerPixel

8

4

2

XMin

Left edge (usually 0)

6

2

YMin

Top edge (usually 0)

8

2

XMax

Right edge (Width-1)

10

2

YMax

Bottom edge (Height-1)

12

2

HDpi

Horizontal DPI

14

2

VDpi

Vertical DPI

16

48

ColorMap

EGA palette (unused)

64

1

Reserved

0

65

1

NPlanes

1 (256-color)

66

2

BytesPerLine

Scanline width (may be padded)

68

2

PaletteInfo

1 = color

70

2

HScreenSize

Horizontal screen size

72

2

VScreenSize

Vertical screen size

74

54

Filler

Reserved (zeros)

Important: Width = XMax - XMin + 1, Height = YMax - YMin + 1

RLE Compression

PCX uses extremely simple run-length encoding:

Byte Value

Meaning

0x00-0xBF

Literal pixel value (no run)

0xC0-0xFF

Run: (byte & 0x3F) = count, next byte = color

Example:

0xC5 0x12  →  5 pixels of color 0x12
0x12       →  1 pixel of color 0x12 (literal)
0xCA 0xFF  →  10 pixels of color 0xFF

Decoding Algorithm:

Read(byte);
if (byte and $C0) = $C0 then
  count := byte and $3F;
  Read(color);
  Output color 'count' times
else
  Output byte once (literal)

Palette

  • Located at EOF - 768 bytes

  • Preceded by marker byte 0x0C (at EOF-769)

  • Format: 256 RGB triplets, each 0-255

  • Conversion for VGA: Divide by 4 (0-255 → 0-63)

for i := 0 to 255 do
  Read(R, G, B);           { 0-255 range }
  Palette[i].R := R shr 2; { Convert to VGA 0-63 }
  Palette[i].G := G shr 2;
  Palette[i].B := B shr 2;

Creating PCX Files

GIMP

  1. Image → Mode → Indexed (256 colors)

  2. File → Export As → .pcx

  3. Options: “8-bit indexed color”

Photoshop

  1. Image → Mode → Indexed Color (256 colors)

  2. File → Save As → PCX

  3. Format: “8 bits/pixel”

Using PCX.PAS

Interface

uses PCX, VGA;

function LoadPCX(const FileName: string; var Image: TImage): Boolean;
function LoadPCXWithPalette(const FileName: string; var Image: TImage;
                            var Palette: TPalette): Boolean;
function LoadPCXToFrameBuffer(const FileName: string; FrameBuffer: PFrameBuffer): Boolean;
function GetLoadPCXError: string;

Functions:

  • LoadPCX - Load image without palette (uses existing VGA palette)

  • LoadPCXWithPalette - Load image with palette extraction

  • LoadPCXToFrameBuffer - Load PCX directly to framebuffer (no intermediate TImage, optimized)

  • GetLoadPCXError - Get last error message

Basic Example

uses VGA, PCX;

var
  Img: TImage;
  Pal: TPalette;
  FB: PFrameBuffer;

begin
  { Load PCX with palette }
  if not LoadPCXWithPalette('IMAGE.PCX', Img, Pal) then
  begin
    WriteLn('Error: ', GetLoadPCXError);
    Halt(1);
  end;

  { Display }
  InitVGA;
  SetPalette(Pal);
  FB := CreateFrameBuffer;
  PutImage(Img, 0, 0, False, FB);
  RenderFrameBuffer(FB);

  ReadLn;

  { Cleanup }
  FreeFrameBuffer(FB);
  FreeImage(Img);
  DoneVGA;
end.

Load Without Palette

{ Use when palette already set (e.g., from another image) }
if LoadPCX('SPRITE.PCX', Img) then
  PutImage(Img, 100, 50, True, FrameBuffer);

Load Directly to FrameBuffer (Optimized)

uses VGA, PCX;

var
  FB: PFrameBuffer;
  Pal: TPalette;

begin
  InitVGA;
  FB := CreateFrameBuffer;

  { Load PCX directly to framebuffer - no intermediate TImage needed }
  if not LoadPCXToFrameBuffer('BACKGROUND.PCX', FB) then
  begin
    WriteLn('Error: ', GetLoadPCXError);
    Halt(1);
  end;

  { Display }
  RenderFrameBuffer(FB);
  ReadLn;

  { Cleanup }
  FreeFrameBuffer(FB);
  DoneVGA;
end.

Benefits:

  • No intermediate TImage allocation

  • No PutImage call needed

  • Saves memory and CPU time

  • Perfect for loading backgrounds directly to BackgroundBuffer

Testing

# Compile PCX loader and test
cd TESTS
CPCXTEST.BAT
PCXTEST.EXE

Test program loads DATA\TEST.PCX and displays it with palette.

Size Limits

  • Max image size: 65,520 bytes (DOS GetMem limit)

  • Max dimensions: 65,535×65,535 (Word type)

  • Practical limits:

    • Full screen: 320×200 = 64,000 bytes ✅

    • Large sprite: 320×204 = 65,280 bytes ✅

    • Oversized: 320×205 = 65,600 bytes ❌

Technical Notes

Scanline Padding

PCX may pad scanlines to even byte boundaries:

  • BytesPerLine ≥ Width

  • Extra bytes at end of each scanline are discarded

  • PCX.PAS handles this automatically

Palette Marker

Some PCX files omit the 256-color palette (rare). PCX.PAS:

  • Checks for marker byte 0x0C at EOF-769

  • If missing: uses grayscale fallback palette

  • Continues loading (non-fatal warning)

VGA Color Conversion

PCX palette uses 0-255 range, VGA DAC uses 0-63:

VGA_R := PCX_R shr 2;  { Divide by 4 }
VGA_G := PCX_G shr 2;
VGA_B := PCX_B shr 2;

Turbo Pascal Compatibility

  • Uses BlockRead (untyped files)

  • No external dependencies (except VGA.PAS)

  • Real mode safe (no extended memory)

  • 16-bit pointer arithmetic

References

See Also