所有格式页面 ↓
图形与媒体

PCX——ZSoft Paintbrush 位图

来自 coreprime/kbot @ 0ab97bf · 在 GitHub 上编辑

PCX 是 90 年代初的一种索引色位图格式。横扫千军用它来存放单位头像(unitpics/*.pcx)、界面面板,以及少量地图预览图。在《横扫千军:王国》中,1×1 的 PCX 还是按阵营区分的调色板的载体——引擎从 PCX 中读出内嵌的调色板,把它应用到同目录的 .taf 动画文件上。

由 PCX 转换而来的 ARMCOM 单位头像
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 字节(存在时) —

另见

  • PAL——调色板存放在单独文件中的情况。
  • GAF——横扫千军:王国的 .taf 文件与一个 .pcx 调色板载体配对使用。
  • 术语表——带调色板的图像(paletted image)、内嵌调色板(embedded palette)。