Alle Formatseiten ↓
Grafik und Medien

PCX – ZSoft-Paintbrush-Bitmap

Aus coreprime/kbot @ 0ab97bf · Auf GitHub bearbeiten

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.

Einheitenporträt ARMCOM, aus PCX konvertiert
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 0x0C davor. 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/YMax unter dem jeweiligen Minimum liegt oder die kürzer als 896 Byte ist.
  • Es ignoriert Encoding, BitsPerPixel, NumPlanes und BytesPerLine: Jede Datei wird lauflängendekodiert als 8-Bit-Daten mit einer einzigen Ebene, Width Byte 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 0x0C vorausgeht 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 PaletteInfo ist 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/*.pcx von 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) –

Siehe auch

  • PAL – wenn die Palette stattdessen in einer eigenen Datei steht.
  • GAF – .taf-Dateien von TA: Kingdoms gehören zu einem .pcx-Palettenträger.
  • Glossar – paletted image, embedded palette.