PCX — ZSoft Paintbrushのビットマップ
PCXは、90年代初めのインデックスカラーのビットマップ形式です。Total Annihilationはこれを、ユニットの肖像画(
unitpics/*.pcx)、GUIのパネル、そしていくつかのマッププレビュー画像に使っています。TA: Kingdomsでは、1×1のPCXが陣営ごとのパレットを運ぶ役目も担っています。エンジンはPCXに埋め込まれたパレットを読み出し、隣にある同名の.tafアニメーションファイルに適用します。
unitpics/armcom.pcx → PNG(TAパレット)
Tip
自分で試す
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
CLIのkbot pcxのリファレンスを参照してください。
Goから使う場合は、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はCavedogの形式ではなく、広く使われてきた形式
PCXを考案したのはCavedogではありません。PCXはWindowsよりも古い形式です。そのため、HPI、GAF、TNTとは違い、TAが発売された時点でディスク上の配置はすでに公開された仕様でした。このページでは、TAのインストールの中でPCXを見つけたときに関係することだけを扱います。仕様の全体については、ZSoftのオリジナルの技術リファレンスを参照してください。
TAに関係する部分:
- 1ピクセル8ビット、1プレーン:パレット付きの画像だけです。24ビット、4ビット、複数プレーンの派生形も存在しますが、Cavedogはどれも同梱していません。
- RLE符号化(
Encoding = 1):非圧縮のPCXも仕様上は正当ですが、あまり見かけません。 - ピクセルデータの後ろに付く256色のVGAパレット:直前に
0x0Cのマーカーバイトが1つ付きます。TAは常にこの配置を使い、ヘッダー内の16色パレットは無視されます。
ヘッダー(128バイト)
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;
| フィールド | TAでの値 | 備考 |
|---|---|---|
Manufacturer |
0x0A |
それ以外はPCXではないので拒否します。 |
Version |
5 |
TA 3.1cはバージョン5しか読み込みません。 |
BitsPerPixel × NumPlanes |
8 × 1 = 8 |
TAが同梱している唯一の構成で、ゲームがデコードする唯一の構成でもあります。 |
Encoding |
1 |
RLE(後述)。ゲームはこのバイトの値にかかわらずRLEとしてデコードします。 |
BytesPerLine |
XMax − XMin + 1 |
TAでは幅と等しくなければなりません。ゲームはこれを無視し、1行につきちょうど幅の分のバイトを読みます。行を偶数バイトに揃える画像エディターが作ったファイルは、ゲーム内で行がずれます。 |
画像サイズの計算
Width = XMax − XMin + 1
Height = YMax − YMin + 1
ゲームはこれを完全な整数で計算するので、幅を65,536にすることもでき、最大値が最小値より小さいファイルは拒否します。一般的なリーダーは、走査線ごとの末尾のバイト(BytesPerLine − Width)を余白として扱いますが、ゲームはそうしません(後述)。
RLEのピクセルデータ
Height本の走査線のそれぞれについて、次を繰り返します。
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
}
}
一般的なリーダーは、その後で末尾の余白(BytesPerLine − Widthバイト)を捨てます。一方、TA 3.1cはヘッダーの直後から1行につきちょうどWidth個のピクセルをデコードするので、余白はすべて次の行の最初のピクセルになってしまいます。行の終わりをまたぐランは、そこで切り詰められます。
Important
上位2ビットが立っているリテラルのバイトは、RLEでエスケープしなければなりません。値が0xC0–0xFFのバイトは、そのままではリテラルとして不正です。長さ1のランとして出力する必要があります(0xFFが1つなら0xC1, 0xFF)。これを忘れたライターが作るファイルは、1行目はデコードできても、その後ずれていきます。
埋め込みパレット(256エントリ × 3バイト)
最後のRLEのバイトの後、ファイルは769バイトで終わります。
1 byte : marker, always 0x0C
256 × 3 : R, G, B per entry (8-bit each)
RLEを解析せずにパレットを見つけるには、fileSize - 769へシークしてマーカーを確かめます。一般的なリーダーは、マーカーがないとヘッダーの16色パレットにフォールバックします(kbotのプレビューはTAパレットを使います)。TA 3.1cは、マーカーの有無にかかわらず常に末尾の768バイトをパレットとして扱い、896バイト(ヘッダーとパレット)より短いファイルは拒否します。
Note
TAのインストールでは、埋め込まれたパレットはたいてい標準のTAパレットです。ただし、いつもそうとは限りません。ミッション専用の肖像画には独自のパレットが付いていることがあり、palettes/guipal.pcxは、GUI向けに調整した独自のパレットを運ぶために意図して作られた、1×1ピクセルのファイルです。
TA 3.1cがPCXをどう扱うか
ゲームがヘッダーから読むのは、製造者、バージョン、範囲のフィールドだけです。
- 読み込むのはバージョン5だけで、
XMax/YMaxが最小値より小さいファイルや、896バイトより短いファイルは拒否します。 Encoding、BitsPerPixel、NumPlanes、BytesPerLineは無視します。どのファイルも、8ビットの単一プレーンのデータとして、1行につきWidthバイトでランレングス復号されます。そのため、24ビットのファイルはでたらめに表示され、余白のある行はずれます。- パレットは、その前に何があろうと末尾の768バイトです。
- 背景は不透明で描かれます。パレットインデックス0はごく普通の黒です。
- ピクセルデータが途中で終わっても、描画は止まりません。最後に読んだバイトから描き続けます。
画像エディターはこうしたファイルを一般的な方法で読むので、MODの背景がエディター(やkbotのプレビュー)では正しく見えても、ゲームでは崩れることがあります。kbot pcx describeは最後にTA 3.1cの節を出力し、違いをすべて列挙します(kbot-ioのpcx.Reader.Compat)。MCPのpcx_describeツールはこれをgame_loads / game_issuesとして返し、アセットエクスプローラーはプレビューの横にバッジとして表示します。
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
実例 — 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
これで、ファイルを自分で描画するのに必要なことはすべてわかります。96×96の画像で、使われているパレットインデックスは41種類、パレットは末尾に付いています。kbot pcx convert -f pngはこれをパレット付きのPNGとして書き出すので、PCXに戻すラウンドトリップでも色のインデックスが保たれます。
TA: Kingdoms — 「パレットPCX」の手法
TAの.gafをTA: Kingdoms用に変換するとき、エンジンはパレットを必要としますが、.taf(名前を変えた.gaf)はパレットを持っていません。そこで、ファイル名の基の部分が同じ1×1のPCXを.tafの隣に置くという手法を使います。
ui/mywidget.taf ← animation data
ui/mywidget.pcx ← 1×1 paletted PCX carrying the 256-colour palette
エンジンはmywidget.pcxに埋め込まれたパレットを読み、そのピクセルは無視します。ユニットや地形物のアニメーションでは、パレットは陣営(Aramon、Taros、Veruna、Zhon)ごとに暗黙に決まっています。UIのアニメーションでは、シーケンスごとにPCXを置きます。
ここで最もよくある落とし穴は、PSPで書き出したパレットを使うことです。そのパレットは正規のTAパレットとRGB値がわずかに異なり、シーケンスによってはTA:Kがクラッシュすることがあります。kbot pal swatchはどんなパレットでもPNGに書き出せるので、基準のパレットと比べることができます。
注意点
Warning
TA向けには、BytesPerLineを幅と等しくして書き出してください。一般的なリーダーはBytesPerLine > Widthのとき余白を捨てますが、TA 3.1cはBytesPerLineを無視して1行につきWidthバイトをデコードするので、余白によってそれ以降のすべての行がずれます。kbot pcx describeはこれを警告します。
- ゲームは、直前に
0x0Cのマーカーがあるかどうかにかかわらず、末尾の768バイトをパレットとして使います。一般的なリーダーは、マーカーがないとヘッダーの16色パレットにフォールバックします。 - TAで読み込めるのはバージョン5だけです。
- ヘッダーの
PaletteInfoフィールドは当てになりません。参考程度に扱ってください。埋め込みパレットのすべてのエントリでR==G==Bになっていない限り、カラーのパレットとみなします。 - TAの
unitpics/*.pcxのファイルは、慣習として96×96です。形式が強制しているわけではありません。MODによっては、より大きな肖像画や正方形でない肖像画を同梱していることがあります。
典型的なサイズ
| アセット | 寸法 | ファイルサイズ |
|---|---|---|
unitpics/*.pcx(ユニットの肖像画) |
96 × 96 | 3–12 KB |
GUIのパネル(bitmaps/*.pcx) |
さまざま。幅64–256 pxが多い | 5–80 KB |
| マッププレビュー | 252 × 252 | 30–80 KB |
| TA:Kのパレットを運ぶPCX | 1 × 1 | 約800バイト(ヘッダー+パレット) |
| ヘッダーの分 | 常に128バイト | — |
| 埋め込みパレット | 常に769バイト(ある場合) | — |