PCX——ZSoft Paintbrush 位图
PCX 是 90 年代初的一种索引色位图格式。横扫千军用它来存放单位头像(
unitpics/*.pcx)、界面面板,以及少量地图预览图。在《横扫千军:王国》中,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 发明的——它比 Windows 还早。因此与 HPI、GAF 或 TNT 不同,在 TA 发售时,它的磁盘布局早已是公开的规范。本页只讲你在 TA 安装目录中碰到 PCX 时用得上的内容;完整的规范请参阅 ZSoft 最初的技术参考文档。
与 TA 相关的子集:
- 每像素 8 位、1 个平面——只有带调色板的图像。24 位、4 位和多平面的变体虽然存在,但 Cavedog 一个也没有发布。
- RLE 编码(
Encoding = 1)。未压缩的 PCX 在技术上是合法的,但不常见。 - 256 色 VGA 调色板附加在像素数据之后,前面有一个
0x0C标记字节。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 而言必须等于宽度。 游戏忽略它,每行恰好读取宽度个字节;把行填充到偶数长度的图像编辑器生成的文件,在游戏中各行会错位。 |
计算图像尺寸
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 则紧接着文件头、每行恰好解码 Width 个像素,因此任何填充都会变成下一行开头的像素;跨越行尾的游程会在行尾被截断。
Important
最高两位被置位的字面字节必须经过 RLE 转义。 值为 0xC0–0xFF 的裸字节不能作为字面量出现;必须把它写成长度为 1 的游程(单个 0xFF 写作 0xC1, 0xFF)。忘记这一点的写入器生成的文件,只能正确解码一行,之后就失去同步。
内嵌调色板(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 是一个有意为之的 1×1 像素调色板载体,承载一个专为界面调校的变体。
TA 3.1c 如何处理 PCX
游戏只读取文件头中的 manufacturer、version 和范围字段:
- 它只加载版本 5,并拒绝
XMax/YMax小于最小值的文件或短于 896 字节的文件。 - 它忽略
Encoding、BitsPerPixel、NumPlanes和BytesPerLine:每个文件都按 8 位单平面数据做游程解码,每行Width个字节。因此 24 位文件会显示为乱码,带填充的行会错位。 - 调色板是最后 768 字节,不管它前面是什么。
- 背景以不透明方式绘制:调色板索引 0 就是普通的黑色。
- 像素数据提前结束也不会让它停下:它会用读到的最后一个字节继续绘制。
图像编辑器按标准方式读取这类文件,所以一张模组背景图可能在编辑器中(以及在 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 时得以保留。
横扫千军:王国——“调色板 PCX”技巧
把 TA 的 .gaf 转换到横扫千军:王国时,引擎需要一个调色板,但 .taf(改了扩展名的 .gaf)里没有。技巧是在 .taf 旁边放一个基本文件名相同的 1×1 PCX:
ui/mywidget.taf ← animation data
ui/mywidget.pcx ← 1×1 paletted PCX carrying the 256-colour palette
引擎读取 mywidget.pcx 的内嵌调色板,忽略其中的像素。对于单位/地图物件动画,调色板按阵营隐式确定(Aramon、Taros、Veruna、Zhon);对于界面动画,则要为每个序列放一个 PCX。
这里最常见的坑是使用 PSP 导出的调色板:它的 RGB 值与标准 TA 调色板略有不同,可能导致 TA:K 在某些序列上崩溃。kbot pal swatch 能把任何调色板导出为 PNG,方便你与参考调色板对比。
坑点
Warning
为 TA 写入的 BytesPerLine 要等于宽度。 当 BytesPerLine > Width 时,标准读取器会丢掉填充,但 TA 3.1c 忽略 BytesPerLine,每行解码 Width 个字节,所以填充会让之后的每一行都发生偏移。kbot pcx describe 会对此发出警告。
- 无论前面有没有
0x0C标记,游戏都把末尾的 768 字节当作调色板。 没有标记时,标准读取器会退回到文件头中的 16 色调色板。 - TA 只加载版本 5。
- 文件头中的
PaletteInfo字段不可靠。 只把它当作参考;除非内嵌调色板中每个条目都满足 R==G==B,否则就假定是彩色调色板。 - TA 的
unitpics/*.pcx文件按惯例是 96×96——格式本身并不强制。模组偶尔会发布更大或非正方形的单位头像。
典型大小
| 资源 | 尺寸 | 文件大小 |
|---|---|---|
unitpics/*.pcx(单位头像) |
96 × 96 | 3–12 KB |
界面面板(bitmaps/*.pcx) |
各不相同,通常宽 64–256 像素 | 5–80 KB |
| 地图预览图 | 252 × 252 | 30–80 KB |
| TA:K 调色板载体 | 1 × 1 | 约 800 字节(文件头 + 调色板) |
| 文件头开销 | 总是 128 字节 | — |
| 内嵌调色板 | 总是 769 字节(存在时) | — |