PCX – ZSoft-Paintbrush-Bitmap
PCX ist ein Bitmap-Format mit indizierten Farben aus den frühen 90ern. Total Annihilation verwendet es für Einheitenporträts (
unitpics/*.pcx), Oberflächen-Panels und eine Handvoll Kartenvorschaubilder. In TA: Kingdoms ist ein 1×1-PCX außerdem der Träger für Paletten pro Seite – die Engine liest die eingebettete Palette aus dem PCX und wendet sie auf eine danebenliegende.taf-Animationsdatei an.
unitpics/armcom.pcx → PNG (Palette von TA)
Tip
Probier es selbst.
kbot pcx describe unitpics/armcom.pcx # full header + what TA will do with it
kbot pcx info unitpics/armcom.pcx # one-line summary
kbot pcx convert unitpics/armcom.pcx -f png -o armcom.png
Siehe die kbot pcx-Referenz der CLI.
Aus Go heraus. Verwende formats/pcx:
import "github.com/coreprime/kbot/formats/pcx"
f, _ := os.Open("unitpics/armcom.pcx")
defer f.Close()
r, _ := pcx.LoadFromReader(f)
fmt.Println(r.Width(), "×", r.Height(), "@", r.BitsPerPixel(), "bpp")
img, _ := r.Decode() // returns image.Image
PCX ist ein allgemein verbreitetes Format, keines von Cavedog
Cavedog hat PCX nicht erfunden – das Format ist älter als Windows. Anders als bei HPI, GAF oder TNT war der Aufbau auf der Platte also bereits eine offene Spezifikation, als TA erschien. Diese Seite behandelt nur, was wichtig ist, wenn du in einer TA-Installation auf ein PCX stößt; die vollständige Spezifikation steht in der ursprünglichen technischen Referenz von ZSoft.
Die für TA relevante Teilmenge:
- 8 Bit pro Pixel, 1 Ebene – nur Bilder mit Palette. Die Varianten mit 24 Bit, 4 Bit und mehreren Ebenen gibt es, aber Cavedog liefert keine davon aus.
- RLE-Kodierung (
Encoding = 1). Unkomprimierte PCX-Dateien sind formal zulässig, aber selten. - VGA-Palette mit 256 Farben, hinter den Pixeldaten angehängt, mit
einem einzelnen Markerbyte
0x0Cdavor. TA verwendet immer diesen Aufbau; die Palette mit 16 Farben im Header wird ignoriert.
Header (128 Byte)
typedef struct {
uint8 Manufacturer; // Always 0x0A (ZSoft signature)
uint8 Version; // 0–5 (TA images are typically version 5)
uint8 Encoding; // 1 = RLE
uint8 BitsPerPixel; // Bits per plane per pixel (8 in TA)
uint16 XMin, YMin; // Image bounds (inclusive)
uint16 XMax, YMax;
uint16 HorzDPI, VertDPI;
uint8 Palette16[48]; // Legacy 16-colour palette (unused in TA)
uint8 Reserved;
uint8 NumPlanes; // 1 for 256-colour images
uint16 BytesPerLine; // Encoded bytes per scan line per plane
uint16 PaletteInfo; // 1 = colour, 2 = grayscale
uint16 HorzScreen, VertScreen;
uint8 Filler[54]; // Pad to 128 bytes (Cavedog leaves it zeroed)
} PCXHeader;
| Feld | Wert in TA | Anmerkungen |
|---|---|---|
Manufacturer |
0x0A |
Alles andere abweisen – kein PCX. |
Version |
5 |
TA 3.1c lädt nur Version 5. |
BitsPerPixel × NumPlanes |
8 × 1 = 8 |
Die einzige Konfiguration, die TA ausliefert, und die einzige, die das Spiel dekodiert. |
Encoding |
1 |
RLE – siehe unten. Das Spiel dekodiert RLE, egal, was das Byte sagt. |
BytesPerLine |
XMax − XMin + 1 |
Muss bei TA gleich der Breite sein. Das Spiel ignoriert den Wert und liest genau so viele Byte pro Zeile, wie das Bild breit ist; Bildeditoren, die Zeilen auf eine gerade Anzahl auffüllen, erzeugen Dateien, deren Zeilen sich im Spiel verschieben. |
Berechnete Bildgröße
Width = XMax − XMin + 1
Height = YMax − YMin + 1
Das Spiel berechnet diese Werte in vollen Ganzzahlen, eine Breite von
65.536 ist also möglich, und es weist eine Datei ab, deren Maximum unter
ihrem Minimum liegt. Ein üblicher Leser behandelt die überzähligen Bytes
pro Scanzeile (BytesPerLine − Width) als Auffüllung; das Spiel tut das
nicht (siehe unten).
RLE-Pixeldaten
Für jede der Height Scanzeilen wiederhole:
while (decoded < BytesPerLine) {
uint8 b = read();
if ((b & 0xC0) == 0xC0) {
int count = b & 0x3F; // 1..63 reps
uint8 value = read();
emit(value, count);
} else {
emit(b, 1); // literal
}
}
Ein üblicher Leser verwirft danach die überzählige Auffüllung
(BytesPerLine − Width Byte). TA 3.1c dekodiert stattdessen genau
Width Pixel pro Zeile, direkt im Anschluss an den Header, sodass jede
Auffüllung zu den ersten Pixeln der nächsten Zeile wird; ein Lauf, der über
das Ende einer Zeile hinausgeht, wird dort abgeschnitten.
Important
Literal-Bytes, bei denen die oberen zwei Bits gesetzt sind, müssen per
RLE maskiert werden. Ein nacktes Byte mit dem Wert 0xC0–0xFF ist als
Literal unzulässig; du musst es als Lauf der Länge 1 ausgeben
(0xC1, 0xFF für ein einzelnes 0xFF). Writer, die das vergessen,
erzeugen Dateien, die sich eine Zeile lang dekodieren lassen und dann
aus dem Tritt geraten.
Eingebettete Palette (256 Einträge × 3 Byte)
Nach dem letzten RLE-Byte endet die Datei mit 769 Byte:
1 byte : marker, always 0x0C
256 × 3 : R, G, B per entry (8-bit each)
Um die Palette zu finden, ohne das RLE zu parsen, springe zu
fileSize - 769 und prüfe den Marker. Ein üblicher Leser greift ohne den
Marker auf die Palette mit 16 Farben im Header zurück (die Vorschauen von
kbot verwenden die Palette von TA). TA 3.1c nimmt immer die letzten 768
Byte als Palette, mit oder ohne Marker, und weist eine Datei ab, die
kürzer als 896 Byte ist (Header plus Palette).
Note
In einer TA-Installation ist die eingebettete Palette meist die
Standardpalette von TA – aber nicht immer. Missionsspezifische
Porträts bringen manchmal eigene Paletten mit, und
palettes/guipal.pcx ist ein bewusst angelegter Palettenträger von
1×1 Pixel mit einer eigenen, auf die Oberfläche abgestimmten Variante.
Was TA 3.1c mit einem PCX macht
Das Spiel liest aus dem Header nur die Felder für Hersteller, Version und Ausdehnung:
- Es lädt nur Version 5 und weist eine Datei ab, deren
XMax/YMaxunter dem jeweiligen Minimum liegt oder die kürzer als 896 Byte ist. - Es ignoriert
Encoding,BitsPerPixel,NumPlanesundBytesPerLine: Jede Datei wird lauflängendekodiert als 8-Bit-Daten mit einer einzigen Ebene,WidthByte pro Zeile. Eine 24-Bit-Datei erscheint deshalb als Müll, und aufgefüllte Zeilen verschieben sich. - Die Palette sind die letzten 768 Byte, egal, was davor steht.
- Hintergründe werden deckend gezeichnet: Palettenindex 0 ist gewöhnliches Schwarz.
- Pixeldaten, die zu früh enden, halten es nicht auf: Es zeichnet mit dem zuletzt gelesenen Byte weiter.
Bildeditoren lesen solche Dateien auf die übliche Weise, ein Hintergrund
in einem Mod kann also im Editor (und in der Vorschau von kbot) richtig
und im Spiel falsch aussehen. kbot pcx describe endet mit einem
Abschnitt TA 3.1c, der jeden Unterschied auflistet
(pcx.Reader.Compat aus kbot-io), das MCP-Werkzeug pcx_describe liefert
ihn als game_loads / game_issues, und der Asset-Explorer zeigt ihn als
Abzeichen neben der Vorschau:
TA 3.1c:
⚠ The game will load this file but draw it differently:
⚠ BytesPerLine is 4 but the width is 3: the game ignores BytesPerLine and decodes 3 bytes per row, so the rows shift
Beispiel im Detail – unitpics/armcom.pcx
$ kbot pcx describe unitpics/armcom.pcx
Manufacturer: 0x0A
Version: 5
Encoding: 1 (RLE)
BitsPerPixel: 8
Planes: 1
Bounds: (0,0) - (95,95) → 96 × 96 px
BytesPerLine: 96
Palette: embedded (768 bytes after 0x0C marker)
Unique colors: 41
Das verrät dir alles, was du brauchst, um die Datei von Hand zu rendern:
ein Bild von 96×96, 41 verschiedene Palettenindizes in Gebrauch, die
Palette am Ende angehängt. kbot pcx convert -f png exportiert es als PNG
mit Palette, damit die Farbindizes einen Round-Trip zurück nach PCX
überstehen.
TA: Kingdoms – der Trick mit dem „Paletten-PCX“
Wenn du ein .gaf von TA nach TA: Kingdoms überträgst, braucht die Engine
eine Palette, aber das .taf (ein umbenanntes .gaf) hat keine. Der
Trick besteht darin, ein PCX von 1×1 mit demselben Basisnamen neben das
.taf zu legen:
ui/mywidget.taf ← animation data
ui/mywidget.pcx ← 1×1 paletted PCX carrying the 256-colour palette
Die Engine liest die eingebettete Palette von mywidget.pcx und
ignoriert sein Pixel. Bei Animationen von Einheiten und Kartenobjekten
ergibt sich die Palette implizit aus der Seite (Aramon, Taros, Veruna,
Zhon); für Oberflächenanimationen legst du ein PCX pro Sequenz dazu.
Die häufigste Stolperfalle ist hier eine aus PSP exportierte Palette, die
leicht andere RGB-Werte als die kanonische Palette von TA hat und TA:K bei
manchen Sequenzen abstürzen lassen kann. kbot pal swatch schreibt jede
Palette als PNG heraus, damit du sie mit der Referenz vergleichen kannst.
Stolperfallen
Warning
Schreib BytesPerLine für TA gleich der Breite. Ein üblicher Leser
verwirft die Auffüllung, wenn BytesPerLine > Width, aber TA 3.1c
ignoriert BytesPerLine und dekodiert Width Byte pro Zeile, die
Auffüllung verschiebt also jede weitere Zeile. kbot pcx describe
warnt davor.
- Das Spiel verwendet die letzten 768 Byte als Palette, ob ihnen der
Marker
0x0Cvorausgeht oder nicht. Übliche Leser greifen ohne den Marker auf die Palette mit 16 Farben im Header zurück. - In TA lädt nur Version 5.
- Das Header-Feld
PaletteInfoist unzuverlässig. Behandle es nur als Hinweis; geh von Farbpaletten aus, es sei denn, in der eingebetteten Palette gilt überall R==G==B. - Die Dateien
unitpics/*.pcxvon TA sind nach Konvention 96×96 – das Format erzwingt das nicht. Mods liefern gelegentlich größere oder nicht quadratische Porträts aus.
Typische Größen
| Asset | Maße | Dateigröße |
|---|---|---|
unitpics/*.pcx (Einheitenporträt) |
96 × 96 | 3–12 KB |
Oberflächen-Panel (bitmaps/*.pcx) |
unterschiedlich, oft 64–256 px breit | 5–80 KB |
| Kartenvorschau | 252 × 252 | 30–80 KB |
| Palettenträger in TA:K | 1 × 1 | ~800 Byte (Header + Palette) |
| Header-Overhead | immer 128 Byte | – |
| Eingebettete Palette | immer 769 Byte (wenn vorhanden) | – |