PCX — растровые изображения ZSoft Paintbrush
Источник: coreprime/kbot @ 0ab97bf · Редактировать на GitHub
PCX — растровый формат с индексированными цветами из начала 90-х. Total Annihilation использует его для портретов юнитов (
unitpics/*.pcx), панелей интерфейса и нескольких превью карт. В TA: Kingdoms PCX размером 1×1 служит ещё и носителем палитр сторон: движок читает встроенную в 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
См. справку по 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. Эта страница описывает только то, что важно, когда вы находите PCX в установке TA; полную спецификацию ищите в оригинальном техническом справочнике 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 (0xC1, 0xFF
для одного 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
Из заголовка игра читает только поля производителя, версии и границ:
- Она загружает только версию 5 и отвергает файл, у которого
XMax/YMaxменьше минимума или который короче 896 байт. - Она игнорирует
Encoding,BitsPerPixel,NumPlanesиBytesPerLine: каждый файл декодируется из RLE как 8-битные одноплоскостные данные,Widthбайт на строку. Поэтому 24-битный файл выглядит как мусор, а строки с заполнением съезжают. - Палитра — последние 768 байт, что бы ни стояло перед ними.
- Фоны рисуются непрозрачными: индекс палитры 0 — обычный чёрный.
- Пиксельные данные, которые кончились раньше времени, её не останавливают: она продолжает рисовать с последнего прочитанного байта.
Графические редакторы читают такие файлы стандартным способом, поэтому
фон из мода может правильно выглядеть в редакторе (и в превью
kbot) и неправильно — в игре. kbot pcx describe в конце выводит раздел
TA 3.1c со всеми отличиями (pcx.Reader.Compat из kbot-io),
инструмент 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-палитрой»
Когда вы переносите .gaf из TA в TA: Kingdoms, движку нужна палитра,
а в .taf (переименованном .gaf) её нет. Приём в том, чтобы положить
рядом с .taf PCX размером 1×1 с тем же базовым именем файла:
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 об этом предупреждает.
- Игра использует последние 768 байт как палитру независимо от того,
стоит ли перед ними маркер
0x0C. Стандартные читатели при отсутствии маркера откатываются на 16-цветную палитру заголовка. - В TA загружается только версия 5.
- Поле заголовка
PaletteInfoненадёжно. Считайте его лишь подсказкой; исходите из того, что палитра цветная, если только во встроенной палитре не везде R==G==B. - Файлы
unitpics/*.pcxв TA имеют размер 96×96 по соглашению — сам формат этого не требует. Моды иногда поставляют портреты крупнее или неквадратные.
Типичные размеры
| Ресурс | Размеры | Размер файла |
|---|---|---|
unitpics/*.pcx (портрет юнита) |
96 × 96 | 3–12 КБ |
Панель интерфейса (bitmaps/*.pcx) |
разные, часто 64–256 пикс. в ширину | 5–80 КБ |
| Превью карты | 252 × 252 | 30–80 КБ |
| Носитель палитры TA:K | 1 × 1 | ~800 байт (заголовок + палитра) |
| Накладные расходы заголовка | всегда 128 байт | — |
| Встроенная палитра | всегда 769 байт (если есть) | — |