Format reference · .pcx

Pictures, runs & scanlines

Unpack an indexed image and see why a perfectly ordinary PCX scanline can decode differently in Total Annihilation.

Stock profile
Version 5 · 8-bit · one plane
Header
128 bytes
Encoding
Literal bytes + RLE runs
Palette trailer
256 RGB entries · 768 bytes

An old image format, a specific reader

Total Annihilation uses ZSoft PCX for unit portraits, menu backgrounds, logos, mission victory images, and even a palette source. Stock assets use 8-bit, single-plane, RLE-encoded version 5 PCX, with a 256-color RGB palette at the end.

That authored profile and the executable’s reader are different contracts. The most visible distinction is scanline padding: standard PCX consumes the stored stride; retail consumes exactly the visible width.

Decoder comparison

One extra byte per row changes the picture

Standard: honor stride 16

A straight rectangular calibration frame decoded by consuming 16 stored bytes and displaying 15 pixels per row.

Retail: consume width 15

The same frame appears diagonally sheared, with coral padding bytes drifting across the rows.
Both panels decode the original padded.pcx: width 15, stored stride 16. A coral padding byte ends every stored row. The standard decoder skips it; retail begins the next row with it, shearing the image. These diagrams are decoded from authored fixtures, not captured retail art.
Logical locationRoleObserved convention
unitpics/<UNITNAME>.PCXF1 unit portrait96 × 96 pixels; basename matches the unit short name.
bitmaps/Backgrounds, logos, glamour imagesBackground/glamour art commonly 640 × 480; logos vary.
palettes/GUIPAL.PCXPalette sourcePalette data carried in an image container.

Lookup is case-insensitive. The dimensions above describe asset conventions, not fixed PCX dimensions.

From header to trailer

0x0000        128-byte header
0x0080        RLE image stream
file size−769 0x0C palette marker (standard profile)
file size−768 256 × { red, green, blue }
EOF

The palette channels are full 8-bit values. The image stream produces indexes into a color table, not RGB triples. Width and height come from inclusive extents: xmax − xmin + 1 and ymax − ymin + 1.

Byte layouts

Image header

All offsets are file-relative; multibyte values are little-endian. The table records the researched fields and groups the remaining bytes without assigning unverified runtime meanings.

Scroll table horizontally to see all fields →

OffsetBytesTypeFieldStock profile / retail use
0x001u8ManufacturerMust be 0x0A; retail validates it.
0x011u8VersionMust be 5; retail validates it.
0x021u8EncodingStock 1 (RLE); not a retail validation gate.
0x031u8Bits per pixel per planeStock 8; not a retail validation gate.
0x048u16 × 4xmin, ymin, xmax, ymaxInclusive bounds used to derive dimensions.
0x0C4u16 × 2DPIIgnored by retail.
0x1048Bytes16-color EGA paletteIgnored by retail.
0x401ByteRemaining header byteNot part of the traced checks.
0x411u8PlanesStock 1; not a retail validation gate.
0x422u16Bytes per scanlineAuthored stride; never read by the executable.
0x442u16Palette informationIgnored by retail.
0x4658BytesRemaining headerNot used by the traced reader.

The header must read completely. Only manufacturer and version are validation gates; encoding, depth, planes, stride, and palette marker are not checked [02 R-MALF-01 §9].

Palette trailer

Scroll table horizontally to see all fields →

OffsetBytesTypeFieldMeaning
size − 7691u8MarkerStandard value 0x0C; retail does not check it.
size − 768768u8 RGB[256]Palette256 RGB triplets, index order, channel range 0–255.

Retail simply reads the last 768 bytes as RGB. A marker-free file is not rejected for lacking the marker. Whether those colors are installed is the consumer’s decision.

RLE, byte by byte

A byte below 0xC0 is a literal pixel index. A byte with both high bits set introduces a run: its low six bits are the count, and the following byte is the repeated index. This makes the largest positive run 63 pixels.

Encoded bytesInterpretationDecoded indexes
03Literal index 303
C3 03Repeat index 3 three times03 03 03
C2 C5Repeat index 197 twiceC5 C5
C1 C5Escape one high-valued literalC5
07Literal index 707

The authored six-pixel sequence 03 03 03 C5 C5 07 becomes C3 03 C2 C5 07. A value of 0xC5 cannot appear as a lone literal command: it would be interpreted as a run count. 0xC0 has a zero count and needs separate malformed-input handling.

Retail decodes one visible-width row at a time. A run that reaches beyond the right edge is clamped to the remaining row, with no overflow pixels carried to the next row. The standard scanline path instead targets bytes_per_line for the authored single-plane profile.

A compact picture to inspect

An original blue-green calibration frame with an amber center, decoded from a 16-by-12 PCX fixture.
The original example.pcx has a 16 × 12 visible image and stride 16, so both decoding approaches agree. Its amber center uses index 197 (0xC5), exercising the escaped/RLE high-index path. The SVG uses the file’s embedded palette for this explanatory preview.
Fixture fieldValueBytes
Manufacturer/version/encoding/depth0x0A, 5, 1, 80A 05 01 08
Extents(0, 0) … (15, 11)00 00 00 00 0F 00 0B 00
DPI72 × 7248 00 48 00
Planes101
Bytes per line1610 00
TrailerMarker + 256 colors0C followed by 768 bytes

The separate padded.pcx reduces visible width to 15 and retains stride 16. Its image is deliberately encoded as literal low-valued pixels, making each padding byte’s movement easy to follow. These are small teaching fixtures; they do not follow the 96 × 96 unit-portrait convention.

Failure behavior and host policy

Retail returns 0 with no message if the header is short or the manufacturer/version check fails. It allocates dimensions derived from the extents as read. Truncation in the image stream is not detected: failed one-byte reads leave the previous byte in use. A stale 0xC0 makes no progress and can hang the loader. For a file shorter than 768 bytes, the negative palette seek fails and reading continues from the current position [02 §7; R-MALF-01 §9].

The parser tests lock the visible-width rule, marker-free palette read, run clamping, and checked truncated-value failure. The F1 client’s indexed PCX blit also follows the established active-display-palette behavior.

Retail’s screenshot writer uses 63-byte RLE runs and literal bytes below 0xC0 [01 R-PLAT-02 §6]. Reader permissiveness does not imply that the writer authors all accepted variants.

Sources and example files

Adapted from the owning PCX research at the pinned sidebar revision. The complete source snapshot includes the standard/TA bibliography and the publication omission of the retail portrait example. No retail picture is included in this page.

Both downloadable PCX files and all three byte-derived SVGs are original, reproducible assets from scripts/pcx-example.py. Its --check mode verifies the artifacts, confirms matching decodes for the unpadded fixture, and confirms the intentional standard/retail divergence for the padded fixture.