Todas as páginas de formatos ↓
Gráficos e mídia

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 .taf irmão.

Retrato da unidade ARMCOM convertido de PCX
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/YMax seja menor que o mínimo ou que tenha menos de 896 bytes.
  • Ele ignora Encoding, BitsPerPixel, NumPlanes e BytesPerLine: todo arquivo é decodificado em RLE como dados de 8 bits e um único plano, com Width bytes 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 0x0C antes 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 PaletteInfo do 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/*.pcx do 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) —

Veja também

  • PAL: quando a paleta fica num arquivo separado.
  • GAF: os arquivos .taf do TA: Kingdoms vêm em par com um portador de paleta .pcx.
  • Glossário: imagem com paleta, paleta embutida.