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 is exported by Pix8, our own 8-bit pixel art editor, and by Aseprite — so modern pixel art workflows map onto it directly.
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
Pix8 (Recommended)
Pix8 is our own 256-color indexed pixel art editor — it runs right in the browser, with nothing to install (or as a Windows desktop app), and it exports SPX sprite sheets as well as PCX.
-
Create image (File → New...)
- Size: 320×200 (full screen), 32×32 (sprites), 16×16 (tiles)
- Already indexed (256 colors) — nothing else to set
-
Draw your pixel art
- Use up to 256 colors
- Color 0 = transparent (engine convention)
- The GrafX2-style palette editor has a 6-bit VGA mode, matching the VGA DAC exactly
-
Export (File → Export as..., Ctrl+Shift+E)
- Format: PCX
-
Save to
D:\ENGINE\DATA\YOURIMAGE.PCX
Pix8 also exports SPX (sprite XML + packed PCX sheets), so animated sprites can go straight from the editor into the Resource Manager.
GrafX2
The classic DOS-style pixel art editor. Roughly the same feature set as Pix8, and it still covers a couple of things Pix8 does not (dithering, gradient fill).
- Draw with 256 colors
- Save as PCX (RLE-compressed)
Aseprite
A polished modern pixel art editor — layers, animation, and a proper indexed color mode — and it exports PCX directly.
-
Create sprite (File → New)
- Mode: Indexed (256 colors)
- Size: 320×200 (full screen), 32×32 (sprites), 16×16 (tiles)
-
Draw your pixel art
- Use up to 256 colors
- Color 0 = transparent (engine convention)
-
Export (File → Export)
- Format: .pcx
- Color Mode: Indexed (8-bit)
- ✅ Ensure "Apply pixel ratio" is OFF
-
Save to
D:\ENGINE\DATA\YOURIMAGE.PCX
GIMP
- Image → Mode → Indexed (256 colors)
- File → Export As → .pcx
- Options: "8-bit indexed color"
Photoshop
- Image → Mode → Indexed Color (256 colors)
- File → Save As → PCX
- 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 extractionLoadPCXToFrameBuffer- 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
TImageallocation - No
PutImagecall 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
- ZSoft PCX Specification: Available at FileFormat.Info
- Pix8 (recommended, PCX + SPX export): https://pix8.app
- GrafX2 (for PCX): http://grafx2.chez.com/
- Aseprite: https://www.aseprite.org/
See Also
- VGA.md - Graphics API reference
- Palette Workflow - Splitting the palette between UI and artwork
- Graphics & Input - All graphics and input modules