semmixcolor and perception

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 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.