GAF — Graphics Animation Format
Fonte: coreprime/kbot @ 0ab97bf · Editar no GitHub
O GAF guarda animações de sprites em cor indexada. Quase todos os elementos 2D animados do Total Annihilation são GAF: cursores, ponteiros do rato, explosões, efeitos das unidades, o balançar das árvores, os ícones das armas, as bolhas de lava, tudo. O TA: Kingdoms reutiliza o mesmo contentor com a extensão
.tafpara as suas animações com paleta (a paleta vem de um ficheiro auxiliar). O TA:K inclui também uma variante truecolor do.taf, que guarda píxeis ARGB de 16 bits em vez de índices; essa tem página própria, TAF / TSF.
Tip
Experimente por si.
kbot gaf list anims/cursors.gaf # sequences, frame counts, loop flag
kbot gaf export anims/cursors.gaf --format gif --sequence 0 --target cursor.gif
kbot gaf dump anims/cursors.gaf --target ./cursors # one PNG per frame
Todas as opções estão na referência de kbot gaf
da linha de comandos.
A partir de Go. Use formats/gaf:
import "github.com/coreprime/kbot/formats/gaf"
r, _ := gaf.LoadFromFile("anims/cursors.gaf")
defer r.Close()
sequences, _ := r.ReadSequences()
for _, seq := range sequences {
fmt.Println(seq.Name, len(seq.Frames))
}
Visão geral
┌─ Header (12B) ─┐
│ ver N pad │
└────────┬───────┘
▼
┌─ Sequence offset table ─┐ N × uint32 absolute offsets
└───┬─────────────────────┘
▼
┌─ SequenceHeader (40B) ─┐
│ frameCount, loop word, │
│ +4 word, name[32] │
└─────────┬──────────────┘
▼
┌─ FrameListItem (8B) × frameCount ─┐ ptr + tick duration
└───┬───────────────────────────────┘
▼
┌─ FrameInfo (24B) ─┐
│ size, origin, │
│ transparency, │
│ compressed?, │
│ layers, +11 byte │
│ ptrPixelData │
└─────┬─────────────┘
▼
┌─ Pixel data: raw or run-encoded ─┐
└──────────────────────────────────┘
Um GAF é uma lista simples de sequências; cada sequência é uma lista de fotogramas; cada fotograma é um mapa de bits de largura × altura com índices da paleta, um ponto de origem e, opcionalmente, séries de transparência ao estilo RLE. Os fotogramas podem ter "subfotogramas", para que um passo lógico da animação seja a composição de várias camadas sobrepostas.
Cabeçalho (12 bytes)
typedef struct {
uint32 Version; // 0x00010100 (or 0 for a handful of stock GAFs)
uint32 SequenceCount; // Number of animations in the file
uint32 Unknown1; // Always 0
} GAFHeader;
| Campo | Notas |
|---|---|
Version |
0x00010100 em todas as ferramentas de terceiros. Os ficheiros anims/terrain.gaf e anims/vismasks.gaf da Cavedog vêm com Version == 0. O jogo não verifica esta palavra; o kbot lê qualquer valor (um valor desconhecido é assinalado com um aviso) e escreve 0x00010100. |
SequenceCount |
Seguido imediatamente de SequenceCount × uint32 deslocamentos absolutos no ficheiro, cada um a apontar para um SequenceHeader. O jogo usa apenas os 16 bits inferiores, lidos como valor com sinal: 1–32767 sequências, e nenhuma para zero ou um valor negativo. |
Unknown1 |
Sempre zero; escreva zero ao gravar. |
Sequência (40 bytes)
typedef struct {
uint16 FrameCount;
uint16 LoopFlags; // +2: low byte non-zero = the sequence loops
uint32 Unknown4; // +4: not used by the game; stock files store 0
char Name[32]; // NUL-padded, case-preserved
} GAFSequence;
O jogo repete a sequência em ciclo quando o byte inferior de LoopFlags
está definido, e reprodu-la uma só vez (e para no último fotograma) quando
é 0. Todas as sequências originais guardam 1. O kbot conserva as duas
palavras quando reescreve um ficheiro, e kbot gaf build dá o valor
original 1 a uma sequência sem sequence.csv.
Seguem-se FrameCount × FrameListItem:
typedef struct {
uint32 PtrFrameInfo; // → FrameInfo
uint32 Duration; // Game ticks (30 ticks = 1 second); low 16 bits used
} FrameListItem;
O jogo lê apenas os 16 bits inferiores de Duration e mostra cada
fotograma durante max(Duration, 1) ticks do seu relógio de animação de
30 Hz.
Os nomes das sequências contam: o jogo procura as animações pelo nome (por
exemplo, UnitInfo refere corpyro_smoke do script corpyro.gaf). Ao
compará-los, trate-os como ASCII, sem distinguir maiúsculas de
minúsculas.
Fotograma (24 bytes de informação + payload)
typedef struct {
uint16 Width;
uint16 Height;
int16 OriginX; // Render anchor, may be negative
int16 OriginY;
uint8 TransparencyIndex; // Key: transparent in raw frames
uint8 Compressed; // 0 = raw bitmap, non-zero = run-encoded
uint8 LayerCount; // +10: 0 = simple frame, >0 = composite of layers
uint8 Blend; // +11: set on a layer = drawn translucently
uint32 Unknown2; // +12: editor scratch, not used by the game
uint32 PtrFrameData; // → pixel bytes OR layer pointer table
uint32 Unknown3; // Always 0
} FrameInfo;
OriginX / OriginY indicam o ponto de ancoragem usado ao copiar (blit) o
fotograma para o mundo. Num sprite de explosão, a origem é normalmente o
centro da explosão; num cursor, é o píxel do ponto ativo (hotspot).
Fotogramas em camadas
Quando LayerCount > 0, PtrFrameData é um vetor de LayerCount × uint32
ponteiros para FrameInfo aninhados, e não dados de píxeis. Cada camada
é desenhada pela ordem do vetor (de trás para a frente), com o seu ponto
ativo sobre o ponto ativo do fotograma exterior. Uma camada com o byte +11
definido é desenhada através da tabela de translucidez em vez de ser copiada
(nenhum ficheiro original o define). O byte +10 e o byte +11 são campos
distintos: lê-los como uma única contagem de 16 bits transforma um byte
+11 definido em 256 camadas.
Dados de píxeis
TransparencyIndex é de cada fotograma, e não do ficheiro inteiro: o
GAF dos cursores, por exemplo, usa o índice 9 (o rosa do sistema) nalguns
fotogramas e o índice 0 noutros.
Não comprimido (Compressed == 0)
Width × Height bytes, de cima para baixo e da esquerda para a direita. O
jogo desenha todos os píxeis exceto os iguais a TransparencyIndex. As
texturas das unidades (textures/*.gaf) e as máscaras de visão
(anims/vismasks.gaf) são lidas como simples vetores de píxeis, por isso os
seus fotogramas têm de estar em bruto; todos os fotogramas originais desses
ficheiros estão.
Comprimido (Compressed != 0) — codificação RLE do TA
Os dados de píxeis são uma sucessão de Height linhas independentes. Cada
linha começa com um uint16 lineLength, seguido de um fluxo de bytes
codificado por RLE com exatamente lineLength bytes. Dentro desse fluxo,
cada bloco começa com uma máscara de 1 byte cujos dois bits inferiores
indicam o tipo de bloco:
| Teste da máscara | Tipo de bloco | Comprimento (píxeis) | Bytes seguintes |
|---|---|---|---|
mask & 0x01 |
Série transparente | count = mask >> 1 |
(nenhum: os píxeis são saltados) |
mask & 0x02 |
Série repetida | count = (mask >> 2) + 1 |
1 byte: o índice de cor a repetir |
| nenhum dos dois | Cópia literal | count = (mask >> 2) + 1 |
count bytes: os píxeis literais |
Assim, cada bloco tem no máximo 127 píxeis transparentes, 64 píxeis
repetidos ou 64 píxeis literais. Só os píxeis saltados são
transparentes: um píxel repetido ou literal é desenhado mesmo quando o seu
valor é igual a TransparencyIndex, e o índice 0 da paleta é um preto opaco
como outro qualquer. Se a linha codificada terminar antes de Width, o
kbot preenche-a com píxeis transparentes e assinala-o; o jogo continuaria a
ler os bytes seguintes. As camadas podem ser mais pequenas do que o
fotograma exterior e são posicionadas pelos seus próprios OriginX/Y.
Referência: formats/gaf/gaf.go readCompressed
e formats/gaf/writer.go compressRow.
Important
O GAF não tem paleta. O ficheiro só guarda índices de 8 bits. Para
renderizar a cores, precisa da paleta global do TA
(palettes/palette.pal) ou, no TA: Kingdoms, de uma paleta própria do
recurso, que fica fora do GAF. Veja [Resolução da paleta no TA: Kingdoms]
(#ta-kingdoms--palette-resolution) e Peculiaridade da transparência no TA:
Kingdoms para o mecanismo próprio do TAK.
kbot gaf export usa por omissão a paleta do TA incorporada e a regra
de transparência do jogo; ambas podem ser substituídas.
TA: Kingdoms — resolução da paleta
Note
Esta secção trata só do que muda no TA:K. O renderizador do TA
usa sempre palettes/palette.pal; a disposição dos bytes do GAF é idêntica
no TA e no TAK, mas o TAK passou a usar uma paleta própria de cada recurso.
O TA: Kingdoms usa o mesmo contentor .gaf (por vezes .taf) que o TA, mas
vai buscar a paleta a um ficheiro auxiliar de cada recurso. A instalação
comercial do TAK segue três convenções documentadas, por ordem de
prioridade:
.pcxcom o mesmo nome, ao lado do.gaf. Os recursos da interface e das animações trazem um ficheiro PCX de 1×1 com a paleta pretendida incorporada como bloco PLTE do PCX.anims/actionbuttons.gafé renderizado comanims/actionbuttons.pcx. Cerca de 38% dos ficheirosanims/*.gafdo TAK têm um ficheiro auxiliar destes..palcom o mesmo nome base empalettes/. Raro; abrange uns poucos recursos originais para os quais a Cavedog exportou uma paleta em bruto de 1024 bytes.Paleta do lado, pelo prefixo do nome do ficheiro. Os GAF de unidades e de elementos têm no nome um prefixo de 3 letras que indica o lado e usam a paleta desse lado:
Prefixo Lado Ficheiro de paleta ara*Aramon palettes/aramon.pcxtar*Taros palettes/taros.pcxver*Veruna palettes/veruna.pcxzon*Zhon palettes/zhon.pcxaid*Aiden (Iron Plague) palettes/aiden.pcxcre*Creon (Iron Plague) palettes/creon.pcx
Se nenhuma destas resultar, o resolvedor recorre a palettes/palette.pal
e, por fim, à paleta do TA incorporada. Na versão comercial do TAK, ambas
são idênticas byte a byte entre si e à do TA, pelo que, nos GAF de unidades
ou de elementos que não correspondem a nenhum prefixo de lado, as cores
ficam erradas sem uma substituição explícita.
O resolvedor do kbot
internal/palettepick
implementa a cadeia acima. Todos os pontos de entrada que renderizam um GAF
passam por ele:
- Explorador web. As rotas
/png/…e/apng/…fazem a deteção automática por omissão; o visualizador de GAF mostra uma lista pendente de paletas com todas as candidatas que o resolvedor encontra, mais uma entradaauto. Passe?palette=<vfs-path>para forçar uma delas (a lista pendente faz isso por si). kbot gaf export/kbot gaf dump. Passe--palette <path.pcx|path.pal>para a substituir; caso contrário, é usada a paleta do TA incorporada (a linha de comandos não executa o resolvedor do VFS, porque não tem contexto de VFS, a menos que a envolva comkbot mount).- Ferramenta MCP
gaf_export. Aceita um argumentopaletteopcional (um caminho dentro da pastagame-dataconfigurada); sem ele, executa o resolvedor completo.
Portar um GAF do TA para o TA: Kingdoms
A disposição dos bytes do GAF é idêntica no TA e no TAK; só muda a origem
da paleta. Para portar um .gaf do TA:
- Crie um PCX com paleta de 1×1, com a paleta do TA incorporada.
- Guarde-o ao lado do GAF com o mesmo nome base (por exemplo,
MyGaf.gaf+MyGaf.pcx).
Isto basta para o TAK o renderizar com as cores originais do TA.
TA: Kingdoms — peculiaridade da transparência
Note
Esta secção trata só do que muda no TA:K. O byte
TransparencyIndex da secção Fotograma é
determinante para o renderizador do TA, mas os autores dos conteúdos do
TAK não o mantiveram coerente com os dados de píxeis reais dos fotogramas
não comprimidos.
Muitos GAF de atlas de texturas do TAK (tudo o que está em textures/*.gaf
e uma boa parte de anims/*.gaf) têm um fotograma não comprimido cujo
metadado TransparencyIndex indica uma constante (normalmente 9) que
nunca aparece nos dados de píxeis reais; entretanto, o artista preencheu a
margem "transparente" com outro índice da paleta (muitas vezes 5). Uma
leitura literal dos metadados produz um fundo colorido opaco onde o
renderizador do TA teria produzido transparência.
O renderizador do TA nunca adivinha: usa a chave guardada nos fotogramas em
bruto e os comandos de salto nos comprimidos. Para os atlas do TAK, o kbot
oferece uma heurística de deteção pelos cantos, opcional
(TransparencyModeHeuristic, veja EffectiveTransparencyIndex em
formats/gaf do kbot-io), que só se aplica a fotogramas em bruto:
- Se
TransparencyIndexaparecer em qualquer ponto do buffer de píxeis, confia nos metadados. - Caso contrário, lê os quatro cantos. Se os quatro tiverem o mesmo valor, usa esse valor como índice transparente efetivo. Isto salva os fotogramas não comprimidos do TAK em que o artista desenhou uma margem uniforme.
- Caso contrário, recorre ao
TransparencyIndexguardado no disco (o que preserva a renderização de texturas que ocupam toda a área, comodungwormB1, cujos cantos são píxeis da unidade; aí a deteção automática, e bem, não substitui nada).
Os fotogramas comprimidos e compostos usam sempre os seus píxeis saltados. O byte no disco nunca é reescrito: quem volta a escrever o ficheiro vê o valor original. Um fotograma do TA totalmente opaco com uma margem uniforme perderia essa margem com a heurística, por isso as renderizações do TA nunca a usam.
Substituir a transparência ao renderizar
gaf.RenderOptions disponibiliza estes modos:
| Modo | Comportamento | Utilização |
|---|---|---|
TransparencyModeAuto / TransparencyModeMetadata |
A regra do jogo: os píxeis da chave de um fotograma em bruto e os píxeis saltados de um fotograma comprimido são transparentes; o índice 0 é preto opaco (o valor zero e a predefinição em todo o kbot para o TA). | Visualização, dumps, idas e voltas. |
TransparencyModeHeuristic |
Palpite pelos cantos nos fotogramas em bruto (ver acima), a regra do jogo nos restantes. É o que o estúdio usa nos sprites do TA: Kingdoms. | Atlas de texturas do TAK. |
TransparencyModeNone |
Todos os píxeis são opacos. | Inspecionar a cor-chave. |
TransparencyModeIndex |
Usa um índice indicado por quem chama. | Substituição na interface; depuração. |
Todas as entradas da paleta exportada são opacas, exceto uma posição transparente por exportação, normalmente a chave do fotograma. Quando um fotograma desenha píxeis cujo valor é igual à sua chave (ou os fotogramas de uma animação usam as chaves uns dos outros como cores), a posição transparente passa a ser um índice que nenhum píxel desenhado usa, para que a imagem conserve todos os índices da paleta.
O visualizador de GAF do explorador de recursos oferece Default / Game rule / Corner guess / None numa lista pendente ao lado do seletor de
paleta, e passa a escolha como ?transparency=game|heuristic|none|<N> em
cada pedido PNG/GIF/APNG. A predefinição é a regra do jogo nas instalações
do TA e o palpite pelos cantos nas do TA: Kingdoms.
A chave da cache inclui a etiqueta da transparência, por isso mudar de modo
não devolve renderizações desatualizadas.
A ferramenta MCP gaf_export segue a mesma predefinição: a regra do jogo,
exceto numa pasta game-data registada como TA: Kingdoms (um contexto
takingdoms do kbot), que recebe o palpite pelos cantos; o resultado indica
o modo usado. kbot gaf export lê um ficheiro simples, sem contexto de
jogo, por isso usa por omissão a regra do jogo; passe --transparency heuristic nos atlas do TA: Kingdoms. kbot gaf dump usa sempre a regra do
jogo, porque kbot gaf build volta a ler as chaves guardadas a partir das
imagens.
Exemplo comentado — cursors.gaf
$ kbot gaf list anims/cursors.gaf
GAF: 22 sequence(s), version 0x00010100
# Name Frames Loops Duration (ticks) Duration (sec)
─ ──── ────── ───── ──────────────── ──────────────
0 cursormove 8 yes 64 2.13
1 cursorgrn 1 yes 10 0.33
2 cursorselect 2 yes 12 0.40
...
Um fotograma típico de cursormove:
Width: 32
Height: 32
OriginX: 16 ← hotspot in the middle of the cursor
OriginY: 16
Transparency: 0 ← this frame's key (compressed, so only skips are transparent)
Compressed: 1 ← run-encoded
LayerCount: 0 ← flat (no sub-frames)
Construir um GAF
kbot gaf dump escreve uma estrutura de pastas que kbot gaf build volta a
ler:
cursors/
├── cursormove/
│ ├── sequence.csv ← index,name,loop_flags,unknown4
│ ├── frames.csv ← frame,width,height,origin_x,origin_y,transparency,
│ │ duration_ticks,duration_sec,storage,blend
│ ├── animated.png ← preview only; not read back
│ ├── 0.png
│ ├── 1.png
│ └── ...
└── ...
kbot gaf dump anims/cursors.gaf --target ./cursors
# edit some PNGs / tweak frames.csv or sequence.csv
kbot gaf build ./cursors --target ./cursors-rebuilt.gaf
kbot gaf roundtrip anims/ # check dump/build keeps what the game reads
As imagens dos fotogramas são PNG indexados (ou GIF, com --format gif)
desenhados com a regra do jogo, por isso cada píxel mantém o seu índice da
paleta e só os píxeis transparentes são marcados. A construção copia de
volta esses índices e transforma os píxeis transparentes na chave do
fotograma; as imagens RGBA, por sua vez, são aproximadas à cor mais próxima
da paleta que não seja a chave.
sequence.csvrepõe a ordem da sequência, o seu nome exato, a palavra de ciclo e a palavra +4. Sem ele, o nome da pasta passa a ser o nome da sequência, e a sequência repete em ciclo, como todas as sequências originais.storage(rawoucompressed) eblend(o byte +11) repõem o cabeçalho de cada fotograma. Os fotogramas semstorageficam comprimidos, exceto os escritos emtextures/*.gafouanims/vismasks.gaf, que ficam todos em bruto, porque o jogo os lê como simples vetores de píxeis.--storage raw|compressedsobrepõe-se a ambos.- Os fotogramas compostos (em camadas) são extraídos como uma única imagem
achatada e reconstruídos como fotogramas simples;
kbot gaf roundtripindica quantos. - Um fotograma sem píxeis (largura ou altura 0) não tem imagem; basta a sua
linha em
frames.csvpara o reconstruir. - Uma subpasta sem
frames.csvnemsequence.csvé ignorada, com um aviso. Qualquer outra subpasta que não possa ser construída (uma imagem em falta ou ilegível, um valor inválido no CSV) interrompe a construção, para que nunca fique uma sequência de fora sem aviso.
Note
Os GAF do TA e do TA:K são idênticos byte a byte. O contentor é idêntico; só muda a origem da paleta. Veja Resolução da paleta no TA: Kingdoms acima, com a cadeia completa e a receita comentada para portar um GAF para o TAK.
Armadilhas
Warning
Uma sequência com o byte de ciclo a 0 é reproduzida uma vez e para. As ferramentas que escrevem a palavra +2 a zero congelam todos os elementos animados em que tocam. O kbot conserva a palavra, e as sequências novas construídas pelo kbot repetem em ciclo.
- Algumas sequências vêm com fotogramas de duração
0. O jogo mostra esses fotogramas durante um tick, como se a duração fosse 1. Não os "corrija" para um valor predefinido. - As linhas de píxeis comprimidas podem ser mais curtas do que
Width. O jogo continua a ler os bytes seguintes como comandos; o kbot preenche a linha com píxeis transparentes e assinala-o. Nenhum ficheiro original tem linhas assim. - As origens das camadas são pontos ativos: cada camada é colocada de modo que a sua origem coincida com a origem do fotograma exterior.
- O
.tafdo TA: Kingdoms tem duas variantes. O kbot lê um.tafcom paleta como um GAF normal, mas o TA:K também inclui ficheiros.taftruecolor (píxeis ARGB de 16 bits, sem paleta). Verifique o byte do formato do fotograma para os distinguir e consulte TAF / TSF para a variante truecolor. anims/terrain.gafeanims/vismasks.gaftêmVersion == 0. O jogo ignora a palavra da versão.- O número de fotogramas por sequência não tem limite:
cursorteleporttem 46 fotogramas. Não assuma um máximo fixo ao dimensionar buffers.
Tamanhos típicos
| Recurso | Intervalo observado nos GAF da Cavedog |
|---|---|
anims/cursors.gaf |
~30 KB, 22 sequências, 1–46 fotogramas cada |
| Efeito de unidade (por exemplo, o clarão do disparo) | 5–20 KB, 1 sequência, 4–12 fotogramas |
Explosão (família anims/explosions.gaf) |
50–250 KB por ficheiro |
anims/textures.gaf (a folha de texturas principal) |
~12 MB (o maior GAF do TA) |
anims/terrain.gaf |
~1.5 MB |
| Dados por fotograma | Normalmente <1 KB; os fotogramas grandes (explosões de 256×256) chegam a 10+ KB |
Exemplos reais no catálogo de referência
GAF notáveis da instalação do TA; extraia qualquer um com
kbot hpi extract e depois use kbot gaf list / kbot gaf export:
anims/cursors.gaf— 22 sequências, cada uma com uma animação de cursor da interface. As sequências mais pequenas (cursorgrn,cursorred) têm um único fotograma; a maior (cursorteleport) tem 46 fotogramas ao longo de 460 ticks. Aparece na secção do exemplo comentado, acima.anims/textures.gaf— ~12 MB, a folha de mosaicos principal referida pelas cadeias de caracteresOffsetToTextureNamede todos os modelos 3DO. Explore-a comkbot gaf list anims/textures.gaf | head.- Família
anims/explosions.gaf— GAF de efeitos típicos:lasersht.gaf,acid.gaf,bigexpls.gaf; cada um contém várias sequências de explosão com nome. anims/armcom.gaf/corcom.gaf— os efeitos do Comandante de cada lado (círculo de seleção, laser de construção).anims/terrain.gaf+anims/vismasks.gaf— os únicos GAF originais comVersion == 0no cabeçalho (convém sabê-lo quando se escreve um analisador).
Ver também
- TAF / TSF — a variante truecolor
.tafdo TA: Kingdoms, construída sobre este mesmo contentor, e a sua forma de texto editável. - PAL — a tabela de cores necessária para renderizar os fotogramas GAF.
- PCX — como o TA:K fornece uma paleta a um
.tafcom paleta. - 3DO — quando um "sprite" é na verdade um modelo 3D texturizado (a maioria das explosões é GAF; a maioria dos projéteis é 3DO + clarão do disparo em GAF).
- Glossário — subfotograma, origem, tick.