Все страницы форматов ↓
Графика и медиа

PCX — растровые изображения ZSoft Paintbrush

Источник: coreprime/kbot @ 0ab97bf · Редактировать на GitHub

PCX — растровый формат с индексированными цветами из начала 90-х. Total Annihilation использует его для портретов юнитов (unitpics/*.pcx), панелей интерфейса и нескольких превью карт. В TA: Kingdoms PCX размером 1×1 служит ещё и носителем палитр сторон: движок читает встроенную в PCX палитру и применяет её к соседнему файлу анимации .taf.

Портрет юнита ARMCOM, преобразованный из PCX
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 байт (если есть) —

См. также

  • PAL — когда палитра хранится в отдельном файле.
  • GAF — файлы .taf из TA: Kingdoms работают в паре с PCX-носителем палитры.
  • Глоссарий — paletted image (изображение с палитрой), embedded palette (встроенная палитра).