All files / qoi / _decoder.ts

100.00% Branches 13/13
100.00% Lines 64/64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
x2
 
x2
 
 
 
 
x132
x22
x22
x5252
x22
x22
x22
x22
 
x50
x50
x940
x672
x112
x120
 
x94
 
x96
x96
x96
x96
x94
 
x132
x132
x132
x132
x132
x118
x128
x128
 
x132
x132
x132
x128
 
x130
x130
x130
x130
x130
x130
x258
 
x130
x130
x130
x130
x130
x130
x130
x130
x258
 
x130
x130
x130
x136
x136
x136
x130
x130
x128
x128
x94
x360
x22
x22











































































import { calcIndex } from "./_common.ts";

export function createDecoder(): (
  input: Uint8Array,
  i: number,
  o: number,
) => { i: number; o: number; c: number; isEnd: boolean } {
  const previousPixel = new Uint8Array([0, 0, 0, 255]);
  const seenPixels: Uint8Array[] = new Array(64)
    .fill(0)
    .map((_) => new Uint8Array([0, 0, 0, 0]));
  return function (
    data: Uint8Array,
    i: number,
    o: number,
  ): { i: number; o: number; c: number; isEnd: boolean } {
    let c = 0;
    for (; i <= data.length - 8; o += 4) {
      if ([0, 0, 0, 0, 0, 0, 0, 1].every((x, j) => x === data[i + j])) {
        return { i, o, c, isEnd: true };
      }
      ++c;

      if (data[i] === 0b11111111) {
        // QOI_OP_RGBA
        previousPixel.set(data.subarray(i + 1, i + 5));
        seenPixels[calcIndex(previousPixel, false)].set(previousPixel);
        data.set(previousPixel, o);
        i += 5;
      } else if (data[i] === 0b11111110) {
        // QOI_OP_RGB
        previousPixel.set(data.subarray(i + 1, i + 4));
        previousPixel[3] = 255;
        seenPixels[calcIndex(previousPixel, true)].set(previousPixel);
        data.set(previousPixel, o);
        i += 4;
      } else {
        switch (data[i] >> 6) {
          case 0:
            // QOI_OP_INDEX
            previousPixel.set(seenPixels[data[i++] & 0b00111111]);
            data.set(previousPixel, o);
            break;
          case 1:
            // QOI_OP_DIFF
            previousPixel[0] += (data[i] >> 4 & 0b11) - 2;
            previousPixel[1] += (data[i] >> 2 & 0b11) - 2;
            previousPixel[2] += (data[i++] & 0b11) - 2;
            seenPixels[calcIndex(previousPixel, false)].set(previousPixel);
            data.set(previousPixel, o);
            break;
          case 2: {
            // QOI_OP_LUMA
            const greenDiff = (data[i] & 0b00111111) - 32;
            previousPixel[0] += (data[++i] >> 4) + greenDiff - 8;
            previousPixel[1] += greenDiff;
            previousPixel[2] += (data[i++] & 0b00001111) + greenDiff - 8;
            seenPixels[calcIndex(previousPixel, false)].set(previousPixel);
            data.set(previousPixel, o);
            break;
          }
          default: { // 3
            // QOI_OP_RUN
            const run = data[i++] & 0b00111111;
            c += run;
            for (let j = 0; j < run; ++j) {
              data.set(previousPixel, o);
              o += 4;
            }
            data.set(previousPixel, o);
          }
        }
      }
    }
    return { i, o, c, isEnd: false };
  };
}