Todas las páginas de formatos ↓
Gráficos y multimedia

PCX: mapa de bits de ZSoft Paintbrush

Fuente: coreprime/kbot @ 0ab97bf · Editar en GitHub

PCX es un formato de mapa de bits de color indexado de principios de los noventa. Total Annihilation lo usa para los retratos de unidad (unitpics/*.pcx), los paneles de la interfaz y unas cuantas imágenes de vista previa de mapas. En TA: Kingdoms, un PCX de 1×1 sirve además como portador de las paletas por bando: el motor lee la paleta incrustada en el PCX y la aplica al archivo de animación .taf que tiene al lado.

Retrato de la unidad ARMCOM convertido desde PCX
unitpics/armcom.pcx → PNG (paleta de TA)

Tip

Pruébalo tú mismo.

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

Consulta la referencia de kbot pcx de la CLI.

Desde Go. Usa 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 es un formato de uso general, no de Cavedog

Cavedog no inventó PCX: es anterior a Windows. Así que, a diferencia de HPI, GAF o TNT, su estructura en disco ya era una especificación abierta cuando salió TA. Esta página solo trata lo que importa cuando encuentras un PCX dentro de una instalación de TA; para la especificación completa, consulta la referencia técnica original de ZSoft.

El subconjunto que le interesa a TA:

  • 8 bits por píxel, 1 plano: solo imágenes con paleta. Existen las variantes de 24 bits, de 4 bits y de varios planos, pero Cavedog no distribuye ninguna.
  • Codificación RLE (Encoding = 1). Los PCX sin comprimir son técnicamente válidos, pero poco habituales.
  • Paleta VGA de 256 colores añadida tras los datos de píxeles, precedida de un único byte de marca 0x0C. TA usa siempre esta disposición; la paleta de 16 colores de la cabecera se ignora.

Cabecera (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 en TA Notas
Manufacturer 0x0A Rechaza cualquier otro valor: no es un PCX.
Version 5 TA 3.1c solo carga la versión 5.
BitsPerPixel × NumPlanes 8 × 1 = 8 La única configuración que distribuye TA, y la única que decodifica el juego.
Encoding 1 RLE: ver más abajo. El juego decodifica con RLE diga lo que diga el byte.
BytesPerLine XMax − XMin + 1 Debe ser igual al ancho en TA. El juego lo ignora y lee exactamente tantos bytes por fila como el ancho; los editores de imágenes que rellenan las filas hasta un número par producen archivos cuyas filas se desplazan en el juego.

Tamaño de imagen calculado

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

El juego los calcula con enteros completos, así que es posible un ancho de 65 536, y rechaza un archivo cuyo máximo sea menor que su mínimo. Un lector estándar trata los bytes que sobran al final de cada línea de barrido (BytesPerLine − Width) como relleno; el juego no (ver más abajo).


Datos de píxeles RLE

Para cada una de las Height líneas de barrido, repite:

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
    }
}

Un lector estándar descarta después el relleno final (BytesPerLine − Width bytes). TA 3.1c, en cambio, decodifica exactamente Width píxeles por fila, de forma continua desde la cabecera, así que el relleno se convierte en los primeros píxeles de la fila siguiente; un tramo que cruza el final de una fila se recorta ahí.

Important

Los bytes literales con los dos bits altos activados tienen que escaparse con RLE. Un byte suelto con valor 0xC0–0xFF no es válido como literal; tienes que escribirlo como un tramo de longitud 1 (0xC1, 0xFF para un solo 0xFF). Los escritores que lo olvidan producen archivos que se decodifican bien durante una fila y después se desincronizan.


Paleta incrustada (256 entradas × 3 bytes)

Después del último byte RLE, el archivo termina con 769 bytes:

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

Para encontrar la paleta sin analizar el RLE, salta a fileSize - 769 y comprueba la marca. Un lector estándar, si no hay marca, recurre a la paleta de 16 colores de la cabecera (las vistas previas de kbot usan la paleta de TA). TA 3.1c siempre toma los últimos 768 bytes como paleta, haya marca o no, y rechaza un archivo de menos de 896 bytes (cabecera más paleta).

Note

En una instalación de TA, la paleta incrustada suele ser la paleta estándar de TA, pero no siempre. Los retratos propios de algunas misiones llevan a veces paletas privadas, y palettes/guipal.pcx es un portador de paleta de 1×1 píxel hecho a propósito, con una variante ajustada para la interfaz.


Qué hace TA 3.1c con un PCX

El juego solo lee de la cabecera los campos de fabricante, versión y extensión de la imagen:

  • Solo carga la versión 5 y rechaza un archivo cuyo XMax/YMax sea menor que su mínimo o que ocupe menos de 896 bytes.
  • Ignora Encoding, BitsPerPixel, NumPlanes y BytesPerLine: todos los archivos se decodifican por longitud de tramos como datos de 8 bits en un solo plano, con Width bytes por fila. Por eso un archivo de 24 bits se ve como basura y las filas con relleno se desplazan.
  • La paleta son los últimos 768 bytes, vaya lo que vaya delante.
  • Los fondos se dibujan opacos: el índice 0 de la paleta es un negro normal.
  • Que los datos de píxeles terminen antes de tiempo no lo detiene: sigue dibujando a partir del último byte que leyó.

Los editores de imágenes leen estos archivos de la manera estándar, así que el fondo de un mod puede verse bien en un editor (y en la vista previa de kbot) y mal en el juego. kbot pcx describe termina con una sección TA 3.1c que enumera cada diferencia (pcx.Reader.Compat de kbot-io), la herramienta MCP pcx_describe la devuelve como game_loads / game_issues, y el explorador de recursos la muestra como una insignia junto a la vista previa:

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

Ejemplo desarrollado: 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

Eso te dice todo lo que necesitas para renderizar el archivo a mano: imagen de 96×96, 41 índices de la paleta distintos en uso y la paleta añadida al final. kbot pcx convert -f png lo exporta como PNG con paleta, para que los índices de color sobrevivan al viaje de vuelta a PCX.


El truco del «PCX de paleta» en TA: Kingdoms

Cuando conviertes un .gaf de TA a TA: Kingdoms, el motor necesita una paleta, pero el .taf (un .gaf con otra extensión) no tiene ninguna. El truco es dejar junto al .taf un PCX de 1×1 con el mismo nombre base:

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

El motor lee la paleta incrustada de mywidget.pcx e ignora su píxel. En las animaciones de unidades y elementos, la paleta es implícita según el bando (Aramon, Taros, Veruna, Zhon); en las animaciones de la interfaz, dejas un PCX por secuencia.

El escollo más habitual aquí es usar una paleta exportada con PSP, que tiene valores RGB ligeramente distintos de los de la paleta canónica de TA y puede hacer que TA:K se cuelgue con algunas secuencias. kbot pal swatch vuelca cualquier paleta a un PNG para que puedas compararla con la de referencia.


Escollos

Warning

En TA, escribe un BytesPerLine igual al ancho. Un lector estándar descarta el relleno cuando BytesPerLine > Width, pero TA 3.1c ignora BytesPerLine y decodifica Width bytes por fila, así que el relleno tuerce todas las filas siguientes. kbot pcx describe avisa de ello.

  • El juego usa los 768 bytes finales como paleta, vayan o no precedidos de la marca 0x0C. Los lectores estándar, si no hay marca, recurren a la paleta de 16 colores de la cabecera.
  • En TA solo carga la versión 5.
  • El campo PaletteInfo de la cabecera no es fiable. Tómalo solo como orientativo; da por hecho que las paletas son de color salvo que en la paleta incrustada todas las entradas cumplan R==G==B.
  • Los archivos unitpics/*.pcx de TA miden 96×96 por convención, no porque lo imponga el formato. De vez en cuando, los mods distribuyen retratos más grandes o no cuadrados.

Tamaños habituales

Recurso Dimensiones Tamaño del archivo
unitpics/*.pcx (retrato de unidad) 96 × 96 3–12 KB
Panel de la interfaz (bitmaps/*.pcx) variable, a menudo de 64–256 px de ancho 5–80 KB
Vista previa de mapa 252 × 252 30–80 KB
Portador de paleta de TA:K 1 × 1 ~800 bytes (cabecera + paleta)
Sobrecarga de la cabecera siempre 128 bytes —
Paleta incrustada siempre 769 bytes (cuando la hay) —

Véase también

  • PAL: cuando la paleta está en un archivo aparte.
  • GAF: los archivos .taf de TA: Kingdoms van emparejados con un .pcx portador de la paleta.
  • Glosario: paletted image, embedded palette.