semmixkolor i percepcja

Kompresja LZW w GIF

Ta strona pokazuje, jak osiem pikseli przykładowego pliku zamienia się w pięć bajtów 04 12 30 13 05. Zasady są opisane na stronach kompresja LZW i LZW w GIF.

Dane wejściowe

piksele (indeksy palety): 0 0 1 1 0 3 3 1 minimalna długość kodu: m = 2 CLEAR = 4, END = 5, pierwszy wolny kod = 6, długość kodu = 3 bity

Przebieg

KrokwcWysłany kodBityNowy wpisDługość po kroku
start4 (CLEAR)33
100036 = 0 03
201037 = 0 13
311138 = 1 14 (następny wolny 9 > 8)
410149 = 1 04
5030410 = 0 34
6333411 = 3 34
7313412 = 3 14
koniec114
5 (END)4

Żaden z dopisanych wpisów nie został użyty, bo obraz jest za krótki, żeby ciągi zdążyły się powtórzyć. Dlatego 8 pikseli po 2 bity (16 bitów) zamieniło się w 36 bitów: kompresja zwiększyła dane. Przy prawdziwych obrazach słownik szybko się zapełnia długimi ciągami i jeden kod obejmuje wiele pikseli.

Pakowanie bitów

kod bity pozycja w strumieniu 4 100 0-2 0 000 3-5 0 000 6-8 ← przechodzi z bajtu 0 do bajtu 1 1 001 9-11 1 0001 12-15 0 0000 16-19 3 0011 20-23 3 0011 24-27 1 0001 28-31 5 0101 32-35 razem 36 bitów → 5 bajtów bajt 0 = bity 7..0 = 00 000 100 = 04 bajt 1 = 0001 001 0 = 12 bajt 2 = 0011 0000 = 30 bajt 3 = 0001 0011 = 13 bajt 4 = 0000 0101 = 05

W każdym bajcie wcześniejszy kod zajmuje młodsze bity, więc czytając bajt od lewej, widzi się kody od końca. Na przykład bajt 1 to: 4 bity drugiego kodu 1 (0001), 3 bity pierwszego kodu 1 (001) i najstarszy bit kodu 0, który zaczął się w bajcie 0 (0).

Koder

// data: indeksy pikseli, wynik: bajty LZW (przed podziałem na podbloki)
function gifLzwEncode(data, minCodeSize) {
  const clear = 1 << minCodeSize, eoi = clear + 1;
  let width, next, dict;
  const reset = () => { width = minCodeSize + 1; next = eoi + 1; dict = new Map(); };
  const bytes = [];
  let acc = 0, bits = 0;
  const emit = (code) => {
    acc |= code << bits; bits += width;
    while (bits >= 8) { bytes.push(acc & 255); acc >>= 8; bits -= 8; }
  };
  reset();
  emit(clear);
  let w = data[0];
  for (let i = 1; i < data.length; i++) {
    const c = data[i];
    const key = w * 256 + c;
    if (dict.has(key)) { w = dict.get(key); continue; }
    emit(w);
    dict.set(key, next++);
    if (next > 1 << width && width < 12) width++;
    if (next === 4096) { emit(clear); reset(); }   // słownik pełny: zacznij od nowa
    w = c;
  }
  emit(w);
  emit(eoi);
  if (bits > 0) bytes.push(acc & 255);
  return bytes;
}

gifLzwEncode([0, 0, 1, 1, 0, 3, 3, 1], 2);   // [0x04, 0x12, 0x30, 0x13, 0x05]

Koder przetestowano na losowych obrazach do 256 × 256 pikseli i 256 kolorów, także z wielokrotnym zapełnieniem słownika. Pliki odczytuje bez błędów niezależny dekoder systemowy.