Blok obrazu ($2C)
Właściwy obraz to trzy części: 10-bajtowy deskryptor, opcjonalna paleta lokalna i skompresowane piksele. W pliku może być wiele bloków obrazu: tak powstają animacje i obrazy składane z kilku kawałków.
Deskryptor obrazu
| Bajty | Pole | Przykład |
|---|---|---|
| 0 | separator 2C (",") | 2C |
| 1-2 | położenie lewej krawędzi na ekranie logicznym | 00 00 = 0 |
| 3-4 | położenie górnej krawędzi | 00 00 = 0 |
| 5-6 | szerokość obrazu | 04 00 = 4 |
| 7-8 | wysokość obrazu | 02 00 = 2 |
| 9 | flagi | 00 |
Obraz nie musi wypełniać całego ekranu logicznego. Animacje często zapisują w kolejnych klatkach tylko prostokąt, który się zmienił, z odpowiednim położeniem. To znacznie zmniejsza plik.
Bajt flag
Przy ustawionym bicie 6 wiersze idą w kolejności czterech przebiegów, opisanej w artykule o przeplocie.
Paleta lokalna
Jeśli bit 7 jest ustawiony, zaraz za deskryptorem jest paleta lokalna: 3 · 2^(N+1) bajtów, zbudowana jak globalna. Obowiązuje tylko dla tego obrazu. Dzięki temu każda klatka animacji może mieć własne 256 kolorów, a cały plik więcej niż 256.
Dane obrazu
Minimalna długość kodu to liczba bitów potrzebna na numer koloru (2 dla palet 2- i 4-kolorowych, 3 dla 8 kolorów, aż do 8 dla 256). Kody LZW zaczynają się od jednego bitu więcej (zob. LZW w GIF).
Dalej idą podbloki danych, po najwyżej 255 bajtów, i terminator 00 (zob. ogólna budowa). Przed dekompresją podbloki skleja się w jeden ciąg bajtów. Granice podbloków nie mają nic wspólnego z granicami kodów: kod może zaczynać się w jednym podbloku i kończyć w następnym.
Po rozkodowaniu dane to po prostu indeksy kolorów, wiersz po wierszu, od lewej do prawej:
function readImage(b, pos) { // pos wskazuje bajt 2C
const u16 = (i) => b[i] | (b[i + 1] << 8);
const desc = { left: u16(pos + 1), top: u16(pos + 3), w: u16(pos + 5), h: u16(pos + 7) };
const flags = b[pos + 9];
pos += 10;
if (flags & 0x80) pos += 3 * (2 << (flags & 7)); // pomiń paletę lokalną
const minCodeSize = b[pos++];
const data = [];
for (let n = b[pos++]; n !== 0; n = b[pos++]) { // sklej podbloki
data.push(...b.subarray(pos, pos + n));
pos += n;
}
return { ...desc, interlaced: (flags & 0x40) !== 0, minCodeSize, data, next: pos };
}