semmixcolor and perception

Application extension ($FF)

The application extension lets programs store their own data in a GIF. Each block has an identifier, so a program recognizes its own data and skips the rest. The most famous of these blocks is NETSCAPE2.0, which makes animations loop.

Structure

21 FF extension, application label 0B fixed block length: always 11 8 bytes application identifier (ASCII) 3 bytes authentication code (usually a version) data sub-blocks application-specific content 00 terminator

NETSCAPE2.0: animation looping

The GIF89a specification does not say how many times to play an animation. In 1995 Netscape Navigator 2.0 added its own block with a loop count, and it became the de facto standard. Without it most browsers play an animation only once.

21 FF 0B 4E 45 54 53 43 41 50 45 "NETSCAPE" 32 2E 30 "2.0" 03 sub-block: 3 bytes 01 sub-block id: loop count 00 00 loop count: 0 = forever 00 terminator

The loop count is 16 bits, little-endian. 05 00 means 5 loops, but browsers differ on whether the first play counts as a loop. The block should sit right after the global color table, before the first frame. Some programs write "ANIMEXTS1.0" with the same content.

XMP metadata

Adobe stores XMP metadata (author, rights, keywords) in a block with the identifier "XMP Data" and code "XMP". The XML is not split into normal sub-blocks but written directly, followed by a 258-byte "magic" trailer. The trailer is designed so that a decoder reading the bytes as sub-block lengths always lands on a terminator and skips the whole block correctly.

ICC color profile

The "ICCRGBG1" block with code "012" carries an ICC profile in sub-blocks. With it a GIF can state which color space its palette is in (see color management). Rarely used: GIF palettes are usually assumed to be sRGB.

// loop count from a NETSCAPE2.0 block; pos points at byte 21 of a 21 FF block
function readLoopCount(b, pos) {
  const id = String.fromCharCode(...b.subarray(pos + 3, pos + 14));
  if (id !== 'NETSCAPE2.0' && id !== 'ANIMEXTS1.0') return null;
  const sub = pos + 14;                    // sub-block: 03 01 lo hi
  return b[sub] === 3 && b[sub + 1] === 1 ? b[sub + 2] | (b[sub + 3] << 8) : null;
}