/
gifworker.js
99 lines (85 loc) · 2.91 KB
/
gifworker.js
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import {
quantize,
applyPalette,
prequantize,
GIFEncoder,
findTransparentIndex,
nearestColorIndexWithDistance,
colorSnap,
} from "gifenc";
const DEFAULT_BACKGROUND = [0, 0, 0];
self.addEventListener("message", (ev) => {
const opts = ev.data;
const data = opts.data;
const {
frame,
width,
height,
delay,
knownColors,
quantizeWithAlpha = true,
backgroundColor = DEFAULT_BACKGROUND,
} = opts;
let transparent = Boolean(opts.transparent);
const maxColors = 256;
// Three modes:
// Opaque (no transparency)
// Transparent w/ Alpha: input is RGBA, first pixel with 0 alpha becomes transparnet
// Transparent w/o Alpha: input is opaque RGB, pixel closest to BG color becomes transparent
// So we need to choose the format depending on the above move
let format = "rgb444";
if (transparent && quantizeWithAlpha) {
// Quantizer must support alpha channel
format = "rgba4444";
}
const uint32 = new Uint32Array(data.buffer);
// Pre-quantization: preparing the data so it's a bit easier
// to work with.
// with GIF we have 1-bit alpha only
prequantize(uint32, { roundRGB: 1, oneBitAlpha: true });
// Quantization to N colors with 1-bit alpha
const palette = quantize(uint32, maxColors, { format, oneBitAlpha: true });
// optionally snap colors to known palette
// TODO: evaluate whether this is really needed - quantizer seems to pick up
// exact colors 1:1 most of the time anyways?
if (knownColors) {
colorSnap(palette, knownColors);
}
// Handle transparency
let transparentIndex = 0;
if (transparent) {
if (quantizeWithAlpha) {
// Find the first pixel with zero alpha
// This is only possible if we quantize with a RGBA format
transparentIndex = palette.findIndex((p) => p[3] === 0);
} else {
// Find the color in palette that is closest to user-specified transparent color
const result = nearestColorIndexWithDistance(palette, backgroundColor);
// Special case here: what if a single frame of the animation doesn't include
// any transparent background? For example, the user has filled the whole screen with
// a color for 1 frame. Then we don't want to choose the wrong pixel and make it disappear.
// So let's only apply transparency for this frame if we find a close enough match.
const dist = Math.sqrt(result[1]);
if (dist <= 5) {
transparentIndex = result[0];
} else {
// This frame has no transparent pixels
transparent = false;
}
}
}
// Now get an indexed bitmap
const index = applyPalette(uint32, palette, format);
// encode single frame
const gif = GIFEncoder({ auto: false });
gif.writeFrame(index, width, height, {
first: frame === 0,
repeat: 0,
delay,
transparent,
transparentIndex,
palette,
});
const output = gif.bytesView();
self.postMessage({ frame, data: output }, [output.buffer]);
});