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.tafque tiene al lado.
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/YMaxsea menor que su mínimo o que ocupe menos de 896 bytes. - Ignora
Encoding,BitsPerPixel,NumPlanesyBytesPerLine: todos los archivos se decodifican por longitud de tramos como datos de 8 bits en un solo plano, conWidthbytes 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
PaletteInfode 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/*.pcxde 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) | — |