PAL / ALP / LHT / SHD——调色板与颜色查找表
横扫千军用 256 种颜色呈现了它的整个世界。
.pal文件存放这个调色板;.alp、.shd和.lht文件则是据此构建的查找表,引擎用它们在不离开索引色空间的前提下对像素做混合、阴影和光照处理。这四种文件都没有文件头、版本字段或压缩。
.pal是 1,024 字节(256 × RGBx);.alp是 65,536 字节(256 行,每行 256 个调色板索引),.shd/.lht各为 8,192 字节(32 行,每行 256 个调色板索引)。
palettes/palette.pal——由 kbot pal swatch 渲染
Tip
自己试试。
kbot pal info palettes/palette.pal # one-liner
kbot pal describe palettes/palette.pal # full RGB dump
kbot pal swatch palettes/palette.pal -o pal.png --cell 24
# Render a lookup table, coloured by the palette:
# .alp as 256×256 cells, .shd / .lht as 256×32 cells
kbot pal lookup palettes/palette.alp --palette palettes/palette.pal --target alp.png
kbot pal lookup palettes/palette.shd --target shd.png
# TA: Kingdoms: the palette is usually a PCX next to its tables
kbot pal lookup palettes/aramon.alp --palette palettes/aramon.pcx --target aramon.png
# Round-trip to editor-friendly formats
kbot pal convert palettes/palette.pal -o palette.gpl # GIMP
kbot pal convert palettes/palette.pal -o palette.txt --format jasc # JASC
见 CLI 的 kbot pal 参考。
在 Go 中调用。 使用 formats/pal:
import "github.com/coreprime/kbot/formats/pal"
p, _ := pal.LoadFromFile("palettes/palette.pal")
fmt.Printf("RGB[0] = %v (black; sprites key it per frame)\n", p.Colors[0])
for i, c := range p.Colors {
fmt.Printf("%3d #%02x%02x%02x\n", i, c.R, c.G, c.B)
}
磁盘布局(.pal)
typedef struct {
uint8 R;
uint8 G;
uint8 B;
uint8 A; // Unused. Cavedog files have this as 0 throughout.
} PALEntry;
PALEntry entries[256]; // 256 × 4 = 1024 bytes
没有签名,也没有版本号。游戏按如下方式加载 palettes/<name>.pal(kbot-io 的 pal.LoadNamed 做法相同,studio 和资源浏览器在每一次 TA 渲染中都通过它加载 palette.pal:图块池、小地图、沙盒地形、地图物件、纹理和模型颜色):
- 1,024 字节或以上: 前 1,024 字节是调色板;其余部分被忽略。
- 为空或缺失: 调色板改从
palettes/<name>.pcx获取——即该文件的最后 768 字节,无论前面有没有0x0C标记。随后 TA 3.1c 会针对这些颜色重建.alp、.shd和.lht表。 - 1 到 1,023 字节: 不可用;游戏会越过文件末尾读满 1,024 字节。kbot 会报告错误(studio 则退回到内置的 TA 调色板)。
.pal——标准的 TA 调色板
每个条目的字节 R、G、B、A 就字面意思而言分别是红、绿、蓝、未使用。索引 0 是普通的黑色。 地形图块、小地图和背景都把它画成不透明(275 张零售版地图中有 62 张在图块中用到了它)。透明与否由每张图像各自决定,而不是由调色板决定:GAF 帧的透明色键字节(通常是 9,有时是 0)及其跳过的像素是透明的,模型纹理采样时则不使用透明色键。
| 索引 | TA 中的惯例 | 说明 |
|---|---|---|
0 |
黑色 | 在地形、小地图和背景中不透明;只在透明色键为 0 的精灵图帧中透明。 |
9 |
“魔术粉”(Magic pink) | 编辑工具在编码前用它填充 PCX/GAF 中的透明区域;运行时并不透明。 |
16–95 |
玩家颜色循环 | 引擎按阵营替换这些颜色,为单位重新着色。 |
223–254 |
保留 | 主要用于队伍颜色循环和特效。 |
255 |
纯白 | 常用于激光的核心。 |
Note
这 256 组 RGB 三元组并不是随意的。 Cavedog 手工调校了这个调色板,让每一大类材质(金属、有机物、水、天空、熔岩……)都有平滑的色阶。替换条目以塞进一种“新”颜色,通常会破坏所有取色于该色阶的单位。色阶的分组见 pal describe 的输出。
.alp / .shd / .lht——颜色查找表
这三种文件不是 RGB 数据:每个字节都是一个调色板索引。每张表为每个混合对象或每个级别各有一行 256 个条目,各列则是 256 种源颜色:
| 文件 | 大小 | 行数 | 条目 [r][c] |
|---|---|---|---|
.alp(alpha) |
65,536 | 256 | 最接近颜色 r 与 c 平均值的索引:一半对一半的混合,用于半透明绘制。表是对称的,且 [c][c] 就是 c。 |
.shd(阴影) |
8,192 | 32 | 最接近颜色 c 乘以 r × 0.06875 后结果的索引:第 0 行是黑色,第 14–15 行让颜色几乎不变,更高的行则使颜色变亮。 |
.lht(光照) |
8,192 | 32 | 最接近颜色 c 乘以 1 + r/30 后结果的索引,从不变(第 0 行)到大约两倍亮度。 |
uint8 alp[256][256]; // blend = alp[a][b]
uint8 shd[32][256]; // shaded = shd[level][c]
uint8 lht[32][256]; // lit = lht[level][c]
实际上,引擎从不以算术方式混合颜色——它查表,所以得到的像素永远是一个真实的调色板索引,可以经由现有的 8bpp 管线绘制。这就是为什么 TA 中所有的阴影看起来都是同一色阶中更暗的颜色,而不是“叠上一层 50% 的黑色”:每个阴影结果都是最接近那个数学混合值的调色板索引。
只有当表文件的大小完全正确时,游戏才会使用它;缺失的表或大小不对的表都会根据调色板重建。因此 kbot pal lookup 会拒绝任何其他大小的文件。横扫千军:王国为其 palettes/ 文件夹中几乎每个调色板都配了同样大小的表;那些调色板大多是 PCX 文件,而 kbot pal lookup --palette 既接受 .pal,也接受 .pcx。
Important
查找表不是调色板。 用 kbot pal describe 渲染 .alp 只会显示一堆无意义的 RGB 值,因为这些字节并不是 RGB。要看它们实际做什么,请使用 kbot pal lookup——传入配套的 .pal,这样每个查找格都能用它所解析到的调色板条目着色。
实例解析——palette.pal
原版 TA 调色板的前 16 个条目:
| 索引 | 十六进制 | R G B | 作用 |
|---|---|---|---|
| 0 | #000000 |
0 0 0 | 黑色(在地形中不透明;某些精灵图帧的透明色键) |
| 1 | #800000 |
128 0 0 | 暗红 |
| 2 | #008000 |
0 128 0 | 暗绿 |
| 3 | #808000 |
128 128 0 | 橄榄色 |
| 4 | #000080 |
0 0 128 | 暗蓝(水面阴影) |
| 5 | #800080 |
128 0 128 | 暗品红 |
| 6 | #008080 |
0 128 128 | 暗青 |
| 7 | #808080 |
128 128 128 | 中灰 |
| 8 | #C0DCC0 |
192 220 192 | 淡绿 |
| 9 | #5454FC |
84 84 252 | “魔术粉”(编辑器中的透明色) |
| 10–15 | #000000 × 6 |
0 0 0 | 保留的填充 |
kbot pal describe palettes/palette.pal 会打印完整的表。
实例解析——把 palette.alp 和 palette.shd 作为查找表
以调色板为颜色来源渲染这些表(kbot pal lookup … --cell 2):
palette.alp:256 × 256 格。第 a 行第 b 列显示颜色 a 与 b 的混合;
对角线重现每种颜色本身。
palette.shd:256 × 32 格。每一列是一种源颜色;
各行从黑色(第 0 行)经过颜色本身,再到更亮的色调。
解读阴影表:第 80 列(中灰)在第 0 行是黑色,沿着它所在色阶中较暗的灰色逐渐上升,在第 14、15 行左右到达自身,然后变亮。引擎从不需要对 RGB 值做乘法——它只是读取 shd[level][c]。
为编辑器转换调色板
kbot pal convert 支持几种便于编辑器使用的输出格式:
| 格式 | 扩展名 | 工具 |
|---|---|---|
二进制 TA .PAL |
.pal |
格式本身;编辑后重新输出时有用。 |
| GIMP Palette | .gpl |
GIMP、Krita、Inkscape。 |
| JASC-PAL(文本) | .pal / .txt(--format jasc) |
Paint Shop Pro、Aseprite。 |
| PNG 色板 | .png(通过 kbot pal swatch) |
用于直观参考;不能作为调色板加载。 |
反方向——把 .gpl 或 .pal(JASC)导回二进制 TA .pal——只要源文件有 256 个条目就可行。
Note
TA 调色板中有 13 组重复的 RGB 三元组。 如果你的编辑器在导入时对颜色去重(有些会这样做),你就会丢失条目,调色板会悄无声息地变成一个 243 条目的调色板。kbot pal info 会报告重复的数量,便于你发现差异;用 kbot pal convert -o roundtrip.pal 做一次往返即可验证。
坑点
Warning
每个条目的第 4 个字节不是 alpha。 它是一个未使用的填充字节,Cavedog 总是把它设为 0x00。有几个开源调色板工具把它当作 alpha 读取,结果拒绝渲染这个调色板(alpha 处处为 0 ⇒ 全部透明)。kbot 读取时忽略它,写入时写出 0x00。
- 调色板索引 0 本身并不透明。 地形、小地图和背景把它画成黑色;精灵图帧只有在它自己的透明色键(或跳过游程)指明的地方才透明。kbot 的地形和小地图渲染使用不透明的调色板。
- 没有魔数意味着校验能力很弱。 检查一个
.pal是否合理的唯一办法,是要求它至少有 1024 字节,并且(可选地)要求每个条目的 alpha 字节都为零。 - 大小不对的查找表会被游戏忽略,并根据调色板重建。
.alp/.lht/.shd不能互换。 它们针对不同的效果调校;互换会产生明显错误的阴影或光照。- 横扫千军:王国使用按阵营区分、嵌在 PCX 中的调色板,而不是全局
.pal的工作流程。载体 PCX 的技巧见 PCX。
典型大小
| 文件 | 大小 | 说明 |
|---|---|---|
palette.pal |
1,024 字节 | 文件更长时,游戏只读取前 1,024 字节。 |
palette.alp |
65,536 字节 | 256 × 256 个调色板索引。 |
palette.shd / palette.lht |
8,192 字节 | 32 × 256 个调色板索引。 |
导出的 GIMP .gpl |
约 5 KB | 文本格式,比二进制大得多。 |
导出的 JASC .pal(文本) |
约 3 KB | 同上。 |
kbot pal swatch 生成的 PNG |
1–10 KB | 取决于 --cell 大小。 |
| 原版 TA 中的调色板文件总数 | 约 15 个 | 一套标准的 palette.*,外加几个备用的界面调色板。 |