Kolejność bloków
GIF pozwala na dość swobodne ułożenie bloków, ale nie na dowolne. Specyfikacja opisuje kolejność jak gramatykę: z czego składa się plik, a z czego jego części.
Gramatyka
plik GIF = nagłówek ekran [paleta globalna] dane* trailer
dane = blok graficzny | blok specjalny
blok graficzny = [sterowanie grafiką $F9] blok rysujący
blok rysujący = obraz $2C [paleta lokalna] dane LZW
| tekst graficzny $01
blok specjalny = rozszerzenie aplikacji $FF
| komentarz $FE
* dowolna liczba powtórzeń, także zero
[] element opcjonalny
Zasady, które z tego wynikają
- Nagłówek i opis ekranu są zawsze na początku, w tej kolejności, a paleta globalna, jeśli jest, zaraz po nich.
- Blok $F9 dotyczy tylko najbliższego bloku rysującego i musi stać bezpośrednio przed nim. Komentarz albo blok aplikacji między nimi to błąd, choć większość dekoderów go toleruje.
- Najwyżej jeden $F9 przed każdym obrazem.
- Komentarze i bloki aplikacji mogą stać w dowolnym miejscu między blokami graficznymi.
- Plik może nie mieć żadnego obrazu. Jest wtedy poprawny, choć pusty.
- Trailer
3Bkończy plik. Wszystko po nim dekoder powinien zignorować.
Przykładowy plik
nagłówek GIF89a
ekran 4 × 2, paleta globalna 4 kolory
paleta 12 bajtów
blok specjalny komentarz "semmix.pl"
blok graficzny $F9 + obraz $2C
trailer 3B
Bajty są rozpisane we wstępie.
Animacja
Animowany GIF to po prostu wiele bloków graficznych po kolei, każdy z własnym $F9 z opóźnieniem:
GIF89a ekran paleta globalna
$FF NETSCAPE2.0, pętla w nieskończoność
$F9 opóźnienie 10, usuwanie 1 $2C klatka 1 (cały ekran)
$F9 opóźnienie 10, usuwanie 1 $2C klatka 2 (tylko zmieniony prostokąt)
$F9 opóźnienie 10, usuwanie 1 $2C klatka 3
3B
Szczegóły opóźnienia i usuwania klatek są na stronie sterowanie grafiką, a pętli na stronie rozszerzenie aplikacji.
Pętla dekodera
let pos = 13; // po nagłówku i opisie ekranu
if (b[10] & 0x80) pos += 3 * (2 << (b[10] & 7)); // paleta globalna
let control = null;
while (pos < b.length) {
const id = b[pos];
if (id === 0x3B) break; // trailer
if (id === 0x21 && b[pos + 1] === 0xF9) { // sterowanie grafiką
control = { flags: b[pos + 3], delay: b[pos + 4] | (b[pos + 5] << 8), transparent: b[pos + 6] };
pos = skipExtension(b, pos);
} else if (id === 0x21) {
pos = skipExtension(b, pos); // inne rozszerzenia
} else if (id === 0x2C) {
const img = readImage(b, pos); // zob. blok obrazu
// ... dekompresja img.data, rysowanie z ustawieniami control
control = null;
pos = img.next;
} else {
throw new Error('nieznany blok ' + id.toString(16));
}
}
Funkcje skipExtension i readImage są na stronach bloki rozszerzeń i blok obrazu.