semmixcolor and perception

Logical screen descriptor

Right after the header come 7 bytes describing the "logical screen": the area on which every image in the file is drawn. This block also says whether the file has a global color table and how large it is.

Structure

BytesFieldExample
0-1screen width (little-endian)04 00 = 4
2-3screen height02 00 = 2
4flagsF1
5background color index00
6pixel aspect ratio00

The flag byte

bit 7 global color table present (1) or not (0) bits 6-4 color resolution - 1 bit 3 table sorted by decreasing frequency bits 2-0 N: the table has 2^(N+1) colors F1 = 11110001 │└┬┘│└┬┘ │ │ │ └─ N = 1 → 2^2 = 4 colors, 12 bytes of palette │ │ └─── 0: not sorted │ └───── 7: the original had 8 bits per component └─────── 1: global color table present
NColorsPalette bytesLZW minimum code size
0262
14122
28243
316484
72567688

The palette always has a power-of-two number of entries. An image in 5 colors needs an 8-color palette with 3 entries unused. The last column belongs to the image block: for 2-color palettes the minimum code size is still 2, because GIF does not allow 1.

The color resolution field (bits 6-4) is informational and practically unused (see color resolution). The sort flag once helped decoders with fewer colors pick the most important ones.

Background and aspect

Background color is a global palette index used to fill the screen wherever no image reaches. Web browsers usually ignore it and show transparency there.

Pixel aspect: 0 means "not given"; any other value N gives (N + 15) / 64. See pixel aspect ratio.

Global color table

If bit 7 is set, the palette colors follow the screen descriptor directly: 3 bytes (R, G, B) per color, with no separators. In the example 4 colors take 12 bytes:

C8 50 28 index 0 (200, 80, 40) FF FF FF index 1 (255, 255, 255) 00 00 00 index 2 (0, 0, 0) 1B 98 E0 index 3 (27, 152, 224)

The global table applies to every image that has no local table of its own. The values carry no color space information, so in practice they are treated as sRGB.

function readScreen(b, pos) {                      // pos: right after the header
  const w = b[pos] | (b[pos + 1] << 8), h = b[pos + 2] | (b[pos + 3] << 8);
  const flags = b[pos + 4];
  const hasGct = (flags & 0x80) !== 0;
  const gctSize = 2 << (flags & 0x07);            // 2^(N+1)
  return { w, h, hasGct, gctSize, bg: b[pos + 5], aspect: b[pos + 6] };
}