Block order
GIF allows a fairly free arrangement of blocks, but not an arbitrary one. The specification describes the order as a grammar: what a file consists of, and what its parts consist of.
The grammar
GIF file = header screen [global color table] data* trailer
data = graphic block | special block
graphic block = [graphic control $F9] rendering block
rendering block = image $2C [local color table] LZW data
| plain text $01
special block = application extension $FF
| comment $FE
* any number of repetitions, including zero
[] optional element
The rules that follow
- The header and screen descriptor always come first, in that order, followed directly by the global color table if there is one.
- An $F9 block applies only to the nearest rendering block and must come immediately before it. A comment or application block between them is an error, though most decoders tolerate it.
- At most one $F9 before each image.
- Comments and application blocks may appear anywhere between graphic blocks.
- A file may contain no image at all. It is then valid, if empty.
- The trailer
3Bends the file. A decoder should ignore anything after it.
The example file
header GIF89a
screen 4 × 2, global table of 4 colors
palette 12 bytes
special block comment "semmix.pl"
graphic block $F9 + image $2C
trailer 3B
The bytes are broken down in the introduction.
Animation
An animated GIF is simply many graphic blocks in a row, each with its own $F9 carrying a delay:
GIF89a screen global color table
$FF NETSCAPE2.0, loop forever
$F9 delay 10, disposal 1 $2C frame 1 (whole screen)
$F9 delay 10, disposal 1 $2C frame 2 (changed rectangle only)
$F9 delay 10, disposal 1 $2C frame 3
3B
Delay and disposal details are on the graphic control page, looping on the application extension page.
A decoder loop
let pos = 13; // after header and screen descriptor
if (b[10] & 0x80) pos += 3 * (2 << (b[10] & 7)); // global color table
let control = null;
while (pos < b.length) {
const id = b[pos];
if (id === 0x3B) break; // trailer
if (id === 0x21 && b[pos + 1] === 0xF9) { // graphic control
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); // other extensions
} else if (id === 0x2C) {
const img = readImage(b, pos); // see image block
// ... decompress img.data, draw using control
control = null;
pos = img.next;
} else {
throw new Error('unknown block ' + id.toString(16));
}
}
The skipExtension and readImage functions are on the extension blocks and image block pages.