PCX: bitmap do ZSoft Paintbrush
Fonte: coreprime/kbot @ 0ab97bf · Editar no GitHub
O PCX é um formato de bitmap em cores indexadas do início dos anos 90. O Total Annihilation o usa nos retratos das unidades (
unitpics/*.pcx), nos painéis da interface e em algumas imagens de pré-visualização de mapas. No TA: Kingdoms, um PCX de 1×1 também serve de portador das paletas de cada lado: o motor lê a paleta embutida no PCX e a aplica a um arquivo de animação.tafirmão.
unitpics/armcom.pcx → PNG (paleta do TA)
Tip
Experimente você mesmo.
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
Veja a referência de kbot pcx da CLI.
Em Go. Use 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
O PCX é um formato da comunidade, não da Cavedog
A Cavedog não inventou o PCX, que é anterior ao Windows. Por isso, ao contrário de HPI, GAF ou TNT, a estrutura no disco já era uma especificação aberta quando o TA foi lançado. Esta página trata só do que importa quando você encontra um PCX numa instalação do TA; para a especificação completa, veja a referência técnica original da ZSoft.
O subconjunto que interessa ao TA:
- 8 bits por pixel, 1 plano: só imagens com paleta. As variantes de 24 bits, de 4 bits e de vários planos existem, mas a Cavedog não lançou nenhuma.
- Codificação RLE (
Encoding = 1). PCXs sem compressão são tecnicamente válidos, mas raros. - Paleta VGA de 256 cores anexada depois dos dados dos pixels, precedida
por um único byte marcador
0x0C. O TA sempre usa esta estrutura; a paleta de 16 cores do cabeçalho é ignorada.
Cabeçalho (128 bytes)
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;
| Campo | Valor no TA | Observações |
|---|---|---|
Manufacturer |
0x0A |
Rejeite qualquer outro valor: não é um PCX. |
Version |
5 |
O TA 3.1c só carrega a versão 5. |
BitsPerPixel × NumPlanes |
8 × 1 = 8 |
A única configuração que o TA traz e a única que o jogo decodifica. |
Encoding |
1 |
RLE, veja abaixo. O jogo decodifica RLE seja qual for o valor do byte. |
BytesPerLine |
XMax − XMin + 1 |
Para o TA, precisa ser igual à largura. O jogo o ignora e lê exatamente a largura em bytes por linha; editores de imagem que preenchem as linhas até um número par geram arquivos cujas linhas se deslocam no jogo. |
Tamanho calculado da imagem
Width = XMax − XMin + 1
Height = YMax − YMin + 1
O jogo calcula esses valores com inteiros completos, então uma largura de
65.536 é possível, e recusa um arquivo cujo máximo seja menor que o mínimo.
Um leitor padrão trata os bytes que sobram em cada linha de varredura
(BytesPerLine − Width) como preenchimento; o jogo não (veja abaixo).
Dados dos pixels em RLE
Para cada uma das Height linhas de varredura, repita:
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
}
}
Um leitor padrão então descarta o preenchimento final (BytesPerLine − Width bytes). O TA 3.1c, em vez disso, decodifica exatamente Width pixels
por linha, em sequência a partir do cabeçalho, então qualquer preenchimento
vira os primeiros pixels da linha seguinte; uma sequência que atravessa o fim
de uma linha é cortada ali.
Important
Os bytes literais com os dois bits mais altos ligados precisam ser
escapados em RLE. Um byte solto de valor 0xC0–0xFF não é válido como
literal; é preciso gravá-lo como uma sequência de contagem 1 (0xC1, 0xFF
para um único 0xFF). Os gravadores que esquecem disso geram arquivos que
decodificam uma linha e depois perdem a sincronia.
Paleta embutida (256 entradas × 3 bytes)
Depois do último byte RLE, o arquivo termina com 769 bytes:
1 byte : marker, always 0x0C
256 × 3 : R, G, B per entry (8-bit each)
Para achar a paleta sem analisar o RLE, vá até fileSize - 769 e confira o
marcador. Um leitor padrão, sem o marcador, volta à paleta de 16 cores do
cabeçalho (as pré-visualizações do kbot usam a paleta do TA). O TA 3.1c
sempre toma os últimos 768 bytes como paleta, com ou sem marcador, e recusa
um arquivo com menos de 896 bytes (cabeçalho mais paleta).
Note
Numa instalação do TA, a paleta embutida costuma ser a paleta padrão do
TA, mas nem sempre. Retratos específicos de missões às vezes trazem
paletas próprias, e palettes/guipal.pcx é de propósito um portador de
paleta de 1×1 pixel, com uma variante ajustada para a interface.
O que o TA 3.1c faz com um PCX
O jogo só lê os campos de fabricante, versão e extensão do cabeçalho:
- Ele só carrega a versão 5 e recusa um arquivo cujo
XMax/YMaxseja menor que o mínimo ou que tenha menos de 896 bytes. - Ele ignora
Encoding,BitsPerPixel,NumPlaneseBytesPerLine: todo arquivo é decodificado em RLE como dados de 8 bits e um único plano, comWidthbytes por linha. Por isso um arquivo de 24 bits aparece como lixo e as linhas com preenchimento se deslocam. - A paleta são os últimos 768 bytes, seja o que for que venha antes.
- Os fundos são desenhados opacos: o índice 0 da paleta é um preto comum.
- Dados de pixels que terminam antes da hora não o fazem parar: ele continua desenhando a partir do último byte que leu.
Os editores de imagem leem esses arquivos do jeito padrão, então um fundo de
mod pode parecer certo no editor (e na pré-visualização do kbot) e errado no
jogo. O kbot pcx describe termina com uma seção TA 3.1c que lista cada
diferença (o pcx.Reader.Compat do kbot-io), a ferramenta MCP
pcx_describe a devolve como game_loads / game_issues, e o explorador de
recursos a mostra como um selo ao lado da pré-visualização:
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
Exemplo comentado: 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
Isso diz tudo o que é preciso para renderizar o arquivo à mão: imagem de
96×96, 41 índices de paleta diferentes em uso, paleta anexada no fim. O
kbot pcx convert -f png o exporta como um PNG com paleta, para que os
índices de cor sobrevivam à volta para PCX.
TA: Kingdoms: o truque do "PCX de paleta"
Quando você converte um .gaf do TA para o TA: Kingdoms, o motor precisa
de uma paleta, mas o .taf (um .gaf renomeado) não tem nenhuma. O truque
é colocar ao lado do .taf um PCX de 1×1 com o mesmo nome de arquivo base:
ui/mywidget.taf ← animation data
ui/mywidget.pcx ← 1×1 paletted PCX carrying the 256-colour palette
O motor lê a paleta embutida em mywidget.pcx e ignora o seu pixel. Nas
animações de unidades e elementos, a paleta é implícita para cada lado
(Aramon, Taros, Veruna, Zhon); nas animações da interface, coloque um PCX
para cada sequência.
A armadilha mais comum aqui é usar uma paleta exportada do PSP, que tem
valores RGB um pouco diferentes dos da paleta canônica do TA e pode derrubar
o TA:K em algumas sequências. O kbot pal swatch exporta qualquer paleta
para um PNG, para você comparar com a referência.
Armadilhas
Warning
Para o TA, grave BytesPerLine igual à largura. Um leitor padrão
descarta o preenchimento quando BytesPerLine > Width, mas o TA 3.1c
ignora BytesPerLine e decodifica Width bytes por linha, então o
preenchimento entorta todas as linhas seguintes. O kbot pcx describe
avisa disso.
- O jogo usa os 768 bytes finais como paleta, haja ou não o marcador
0x0Cantes deles. Os leitores padrão, sem o marcador, voltam à paleta de 16 cores do cabeçalho. - No TA, só a versão 5 carrega.
- O campo
PaletteInfodo cabeçalho não é confiável. Trate-o só como indicação; presuma uma paleta colorida, a menos que todas as entradas da paleta embutida tenham R==G==B. - Os arquivos
unitpics/*.pcxdo TA têm 96×96 por convenção, e não por exigência do formato. Alguns mods trazem retratos maiores ou não quadrados.
Tamanhos típicos
| Recurso | Dimensões | Tamanho do arquivo |
|---|---|---|
unitpics/*.pcx (retrato de unidade) |
96 × 96 | 3–12 KB |
Painel da interface (bitmaps/*.pcx) |
variadas, muitas vezes de 64 a 256 px de largura | 5–80 KB |
| Pré-visualização de mapa | 252 × 252 | 30–80 KB |
| Portador de paleta do TA:K | 1 × 1 | ~800 bytes (cabeçalho + paleta) |
| Custo do cabeçalho | sempre 128 bytes | — |
| Paleta embutida | sempre 769 bytes (quando existe) | — |