Todas as páginas de formatos ↓
Gráficos e multimédia

PCX — mapa de bits do ZSoft Paintbrush

Fonte: coreprime/kbot @ 0ab97bf · Editar no GitHub

O PCX é um formato de mapa de bits em cor indexada do início dos anos 90. O Total Annihilation usa-o para os retratos das unidades (unitpics/*.pcx), os painéis da interface e algumas imagens de pré-visualização de mapas. No TA: Kingdoms, um PCX de 1×1 serve também para transportar as paletas de cada lado: o motor lê a paleta incorporada no PCX e aplica-a ao ficheiro de animação .taf com o mesmo nome.

retrato da unidade ARMCOM convertido de PCX
unitpics/armcom.pcx → PNG (paleta do TA)

Tip

Experimente por si.

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 linha de comandos.

A partir de 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: o formato é anterior ao Windows. Por isso, ao contrário do HPI, do GAF ou do TNT, a disposição no disco já era uma especificação aberta quando o TA saiu. Esta página cobre apenas o que interessa quando se encontra um PCX numa instalação do TA; para a especificação completa, consulte a referência técnica original da ZSoft.

O subconjunto que interessa ao TA:

  • 8 bits por píxel, 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 inclui nenhuma.
  • Codificação RLE (Encoding = 1). Os PCX sem compressão são tecnicamente válidos, mas pouco comuns.
  • Paleta VGA de 256 cores acrescentada a seguir aos dados dos píxeis, precedida por um único byte marcador 0x0C. O TA usa sempre esta disposição; 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 Notas
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 inclui, e a única que o jogo descodifica.
Encoding 1 RLE (ver abaixo). O jogo descodifica RLE seja qual for o valor deste byte.
BytesPerLine XMax − XMin + 1 No TA, tem de ser igual à largura. O jogo ignora-o e lê exatamente tantos bytes por linha quanto a largura; os editores de imagem que completam as linhas até um número par produzem ficheiros cujas linhas aparecem desalinhadas no jogo.

Tamanho calculado da imagem

Width  = XMax − XMin + 1
Height = YMax − YMin + 1

O jogo faz estas contas com inteiros de tamanho completo, por isso é possível uma largura de 65 536, e recusa um ficheiro cujo máximo seja inferior ao mínimo. Um leitor normal trata os bytes a mais em cada linha de varrimento (BytesPerLine − Width) como preenchimento; o jogo não (ver abaixo).


Dados de píxeis RLE

Para cada uma das Height linhas de varrimento, 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
    }
}

Depois, um leitor normal descarta o preenchimento final (BytesPerLine − Width bytes). O TA 3.1c, pelo contrário, descodifica exatamente Width píxeis por linha, de seguida a partir do cabeçalho, por isso qualquer preenchimento passa a ser os primeiros píxeis da linha seguinte; uma série que atravesse o fim de uma linha é cortada aí.

Important

Os bytes literais com os dois bits superiores definidos têm de ser escapados com RLE. Um byte isolado com valor 0xC0–0xFF não é válido como literal; tem de o emitir como uma série de contagem 1 (0xC1, 0xFF para um único 0xFF). Os escritores que se esquecem disto produzem ficheiros que se descodificam bem durante uma linha e depois se dessincronizam.


Paleta incorporada (256 entradas × 3 bytes)

Depois do último byte RLE, o ficheiro termina com 769 bytes:

   1 byte  : marker, always 0x0C
 256 × 3   : R, G, B per entry (8-bit each)

Para encontrar a paleta sem analisar o RLE, posicione-se em fileSize - 769 e verifique o marcador. Um leitor normal, sem o marcador, recorre à paleta de 16 cores do cabeçalho (as pré-visualizações do kbot usam a paleta do TA). O TA 3.1c usa sempre os últimos 768 bytes como paleta, com ou sem marcador, e recusa um ficheiro com menos de 896 bytes (cabeçalho mais paleta).

Note

Numa instalação do TA, a paleta incorporada é normalmente a paleta padrão do TA, mas nem sempre. Os retratos específicos de missões às vezes trazem paletas próprias, e o palettes/guipal.pcx é propositadamente um transportador de paleta de 1×1 píxel, com uma variante ajustada para a interface.


O que o TA 3.1c faz com um PCX

Do cabeçalho, o jogo só lê os campos de fabricante, de versão e de dimensões:

  • Carrega apenas a versão 5 e recusa um ficheiro cujo XMax/YMax seja inferior ao mínimo ou que tenha menos de 896 bytes.
  • Ignora Encoding, BitsPerPixel, NumPlanes e BytesPerLine: todos os ficheiros são descodificados por séries (run-length) como dados de 8 bits num único plano, Width bytes por linha. Por isso, um ficheiro de 24 bits aparece como lixo e as linhas com preenchimento ficam desalinhadas.
  • 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 normal.
  • Dados de píxeis que acabem antes do tempo não o fazem parar: continua a desenhar a partir do último byte que leu.

Os editores de imagem leem estes ficheiros da forma normal, por isso o fundo de um mod pode parecer certo num editor (e na pré-visualização do kbot) e errado no jogo. O kbot pcx describe termina com uma secção TA 3.1c que lista todas as diferenças (o pcx.Reader.Compat do kbot-io), a ferramenta MCP pcx_describe devolve-as como game_loads / game_issues, e o explorador de recursos mostra-as num emblema 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

Isto diz tudo o que precisa para renderizar o ficheiro à mão: imagem de 96×96, 41 índices da paleta distintos em uso, paleta acrescentada no fim. O kbot pcx convert -f png exporta-o como PNG com paleta, para que os índices de cor sobrevivam a uma ida e volta para PCX.


TA: Kingdoms — o truque do "PCX de paleta"

Quando converte um .gaf do TA para o TA: Kingdoms, o motor precisa de uma paleta, mas o .taf (o .gaf com outra extensão) não tem nenhuma. O truque é colocar um PCX de 1×1 com o mesmo nome base ao lado do .taf:

ui/mywidget.taf      ← animation data
ui/mywidget.pcx      ← 1×1 paletted PCX carrying the 256-colour palette

O motor lê a paleta incorporada em mywidget.pcx e ignora o seu píxel. Nas animações de unidades e de elementos, a paleta está implícita em cada lado (Aramon, Taros, Veruna, Zhon); nas animações da interface, coloca-se um PCX por sequência.

A armadilha mais comum aqui é usar uma paleta exportada pelo PSP, que tem valores RGB ligeiramente diferentes dos da paleta canónica do TA e pode fazer o TA:K falhar em algumas sequências. O kbot pal swatch despeja qualquer paleta para um PNG, para a poder comparar com a de referência.


Armadilhas

Warning

No TA, escreva BytesPerLine igual à largura. Um leitor normal descarta o preenchimento quando BytesPerLine > Width, mas o TA 3.1c ignora BytesPerLine e descodifica Width bytes por linha, por isso o preenchimento desalinha todas as linhas seguintes. O kbot pcx describe avisa disso.

  • O jogo usa os últimos 768 bytes como paleta, quer o marcador 0x0C venha antes deles, quer não. Os leitores normais, sem o marcador, recorrem à paleta de 16 cores do cabeçalho.
  • No TA, só a versão 5 carrega.
  • O campo PaletteInfo do cabeçalho não é fiável. Trate-o apenas como indicativo; assuma paletas a cores, a não ser que todas as entradas da paleta incorporada tenham R==G==B.
  • Os ficheiros unitpics/*.pcx do TA têm 96×96 por convenção, sem que o formato o imponha. De vez em quando, há mods que trazem retratos maiores ou não quadrados.

Tamanhos típicos

Recurso Dimensões Tamanho do ficheiro
unitpics/*.pcx (retrato de unidade) 96 × 96 3–12 KB
Painel da interface (bitmaps/*.pcx) variadas, muitas vezes 64–256 px de largura 5–80 KB
Pré-visualização de mapa 252 × 252 30–80 KB
Transportador de paleta do TA:K 1 × 1 ~800 bytes (cabeçalho + paleta)
Custo fixo do cabeçalho sempre 128 bytes —
Paleta incorporada sempre 769 bytes (quando existe) —

Ver também

  • PAL — quando a paleta está num ficheiro separado.
  • GAF — os ficheiros .taf do TA: Kingdoms andam aos pares com um .pcx que transporta a paleta.
  • Glossário — imagem com paleta, paleta incorporada.