Showing
5 changed files
with
266 additions
and
0 deletions
1 | +import GIF from "./lib/GIFEncoder"; | ||
2 | + | ||
3 | +class GifGenerator { | ||
4 | + constructor(canvas) { | ||
5 | + this.canvas = canvas; | ||
6 | + this.width = canvas.getWidth(); | ||
7 | + this.height = canvas.getHeight(); | ||
8 | + this.gif = new GIF(this.width, this.height); | ||
9 | + | ||
10 | + this.gif.writeHeader(); | ||
11 | + this.gif.setTransparent(null); | ||
12 | + this.gif.setRepeat(0); | ||
13 | + this.gif.setQuality(10); | ||
14 | + this.gif.setDither(false); | ||
15 | + this.gif.setGlobalPalette(false); | ||
16 | + } | ||
17 | + | ||
18 | + addFrame(delay = 0) { | ||
19 | + this.gif.setDelay(delay); | ||
20 | + this.gif.addFrame( | ||
21 | + this.canvas.getContext().getImageData(0, 0, this.width, this.height).data | ||
22 | + ); | ||
23 | + } | ||
24 | + | ||
25 | + render() { | ||
26 | + this.gif.finish(); | ||
27 | + const stream = this.gif.stream(); | ||
28 | + | ||
29 | + let bytes = []; | ||
30 | + stream.pages.map((page) => { | ||
31 | + bytes = bytes.concat([...page]); | ||
32 | + }); | ||
33 | + bytes = new Uint8Array(bytes); | ||
34 | + | ||
35 | + return new Blob([bytes], { type: "image/gif" }); | ||
36 | + } | ||
37 | +} | ||
38 | + | ||
39 | +window.GifGenerator = GifGenerator; | ... | ... |
gif-generator/src/lib/GIFEncoder.js
0 → 100644
This diff is collapsed. Click to expand it.
gif-generator/src/lib/LZWEncoder.js
0 → 100644
1 | +/* | ||
2 | + LZWEncoder.js | ||
3 | + | ||
4 | + Authors | ||
5 | + Kevin Weiner (original Java version - kweiner@fmsware.com) | ||
6 | + Thibault Imbert (AS3 version - bytearray.org) | ||
7 | + Johan Nordberg (JS version - code@johan-nordberg.com) | ||
8 | + | ||
9 | + Acknowledgements | ||
10 | + GIFCOMPR.C - GIF Image compression routines | ||
11 | + Lempel-Ziv compression based on 'compress'. GIF modifications by | ||
12 | + David Rowley (mgardi@watdcsu.waterloo.edu) | ||
13 | + GIF Image compression - modified 'compress' | ||
14 | + Based on: compress.c - File compression ala IEEE Computer, June 1984. | ||
15 | + By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) | ||
16 | + Jim McKie (decvax!mcvax!jim) | ||
17 | + Steve Davies (decvax!vax135!petsd!peora!srd) | ||
18 | + Ken Turkowski (decvax!decwrl!turtlevax!ken) | ||
19 | + James A. Woods (decvax!ihnp4!ames!jaw) | ||
20 | + Joe Orost (decvax!vax135!petsd!joe) | ||
21 | +*/ | ||
22 | + | ||
23 | +var EOF = -1; | ||
24 | +var BITS = 12; | ||
25 | +var HSIZE = 5003; // 80% occupancy | ||
26 | +var masks = [ | ||
27 | + 0x0000, | ||
28 | + 0x0001, | ||
29 | + 0x0003, | ||
30 | + 0x0007, | ||
31 | + 0x000f, | ||
32 | + 0x001f, | ||
33 | + 0x003f, | ||
34 | + 0x007f, | ||
35 | + 0x00ff, | ||
36 | + 0x01ff, | ||
37 | + 0x03ff, | ||
38 | + 0x07ff, | ||
39 | + 0x0fff, | ||
40 | + 0x1fff, | ||
41 | + 0x3fff, | ||
42 | + 0x7fff, | ||
43 | + 0xffff, | ||
44 | +]; | ||
45 | + | ||
46 | +function LZWEncoder(width, height, pixels, colorDepth) { | ||
47 | + var initCodeSize = Math.max(2, colorDepth); | ||
48 | + | ||
49 | + var accum = new Uint8Array(256); | ||
50 | + var htab = new Int32Array(HSIZE); | ||
51 | + var codetab = new Int32Array(HSIZE); | ||
52 | + | ||
53 | + var cur_accum, | ||
54 | + cur_bits = 0; | ||
55 | + var a_count; | ||
56 | + var free_ent = 0; // first unused entry | ||
57 | + var maxcode; | ||
58 | + | ||
59 | + // block compression parameters -- after all codes are used up, | ||
60 | + // and compression rate changes, start over. | ||
61 | + var clear_flg = false; | ||
62 | + | ||
63 | + // Algorithm: use open addressing double hashing (no chaining) on the | ||
64 | + // prefix code / next character combination. We do a variant of Knuth's | ||
65 | + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime | ||
66 | + // secondary probe. Here, the modular division first probe is gives way | ||
67 | + // to a faster exclusive-or manipulation. Also do block compression with | ||
68 | + // an adaptive reset, whereby the code table is cleared when the compression | ||
69 | + // ratio decreases, but after the table fills. The variable-length output | ||
70 | + // codes are re-sized at this point, and a special CLEAR code is generated | ||
71 | + // for the decompressor. Late addition: construct the table according to | ||
72 | + // file size for noticeable speed improvement on small files. Please direct | ||
73 | + // questions about this implementation to ames!jaw. | ||
74 | + var g_init_bits, ClearCode, EOFCode; | ||
75 | + | ||
76 | + // Add a character to the end of the current packet, and if it is 254 | ||
77 | + // characters, flush the packet to disk. | ||
78 | + function char_out(c, outs) { | ||
79 | + accum[a_count++] = c; | ||
80 | + if (a_count >= 254) flush_char(outs); | ||
81 | + } | ||
82 | + | ||
83 | + // Clear out the hash table | ||
84 | + // table clear for block compress | ||
85 | + function cl_block(outs) { | ||
86 | + cl_hash(HSIZE); | ||
87 | + free_ent = ClearCode + 2; | ||
88 | + clear_flg = true; | ||
89 | + output(ClearCode, outs); | ||
90 | + } | ||
91 | + | ||
92 | + // Reset code table | ||
93 | + function cl_hash(hsize) { | ||
94 | + for (var i = 0; i < hsize; ++i) htab[i] = -1; | ||
95 | + } | ||
96 | + | ||
97 | + function compress(init_bits, outs) { | ||
98 | + var fcode, c, i, ent, disp, hsize_reg, hshift; | ||
99 | + | ||
100 | + // Set up the globals: g_init_bits - initial number of bits | ||
101 | + g_init_bits = init_bits; | ||
102 | + | ||
103 | + // Set up the necessary values | ||
104 | + clear_flg = false; | ||
105 | + n_bits = g_init_bits; | ||
106 | + maxcode = MAXCODE(n_bits); | ||
107 | + | ||
108 | + ClearCode = 1 << (init_bits - 1); | ||
109 | + EOFCode = ClearCode + 1; | ||
110 | + free_ent = ClearCode + 2; | ||
111 | + | ||
112 | + a_count = 0; // clear packet | ||
113 | + | ||
114 | + ent = nextPixel(); | ||
115 | + | ||
116 | + hshift = 0; | ||
117 | + for (fcode = HSIZE; fcode < 65536; fcode *= 2) ++hshift; | ||
118 | + hshift = 8 - hshift; // set hash code range bound | ||
119 | + hsize_reg = HSIZE; | ||
120 | + cl_hash(hsize_reg); // clear hash table | ||
121 | + | ||
122 | + output(ClearCode, outs); | ||
123 | + | ||
124 | + outer_loop: while ((c = nextPixel()) != EOF) { | ||
125 | + fcode = (c << BITS) + ent; | ||
126 | + i = (c << hshift) ^ ent; // xor hashing | ||
127 | + if (htab[i] === fcode) { | ||
128 | + ent = codetab[i]; | ||
129 | + continue; | ||
130 | + } else if (htab[i] >= 0) { | ||
131 | + // non-empty slot | ||
132 | + disp = hsize_reg - i; // secondary hash (after G. Knott) | ||
133 | + if (i === 0) disp = 1; | ||
134 | + do { | ||
135 | + if ((i -= disp) < 0) i += hsize_reg; | ||
136 | + if (htab[i] === fcode) { | ||
137 | + ent = codetab[i]; | ||
138 | + continue outer_loop; | ||
139 | + } | ||
140 | + } while (htab[i] >= 0); | ||
141 | + } | ||
142 | + output(ent, outs); | ||
143 | + ent = c; | ||
144 | + if (free_ent < 1 << BITS) { | ||
145 | + codetab[i] = free_ent++; // code -> hashtable | ||
146 | + htab[i] = fcode; | ||
147 | + } else { | ||
148 | + cl_block(outs); | ||
149 | + } | ||
150 | + } | ||
151 | + | ||
152 | + // Put out the final code. | ||
153 | + output(ent, outs); | ||
154 | + output(EOFCode, outs); | ||
155 | + } | ||
156 | + | ||
157 | + function encode(outs) { | ||
158 | + outs.writeByte(initCodeSize); // write "initial code size" byte | ||
159 | + remaining = width * height; // reset navigation variables | ||
160 | + curPixel = 0; | ||
161 | + compress(initCodeSize + 1, outs); // compress and write the pixel data | ||
162 | + outs.writeByte(0); // write block terminator | ||
163 | + } | ||
164 | + | ||
165 | + // Flush the packet to disk, and reset the accumulator | ||
166 | + function flush_char(outs) { | ||
167 | + if (a_count > 0) { | ||
168 | + outs.writeByte(a_count); | ||
169 | + outs.writeBytes(accum, 0, a_count); | ||
170 | + a_count = 0; | ||
171 | + } | ||
172 | + } | ||
173 | + | ||
174 | + function MAXCODE(n_bits) { | ||
175 | + return (1 << n_bits) - 1; | ||
176 | + } | ||
177 | + | ||
178 | + // Return the next pixel from the image | ||
179 | + function nextPixel() { | ||
180 | + if (remaining === 0) return EOF; | ||
181 | + --remaining; | ||
182 | + var pix = pixels[curPixel++]; | ||
183 | + return pix & 0xff; | ||
184 | + } | ||
185 | + | ||
186 | + function output(code, outs) { | ||
187 | + cur_accum &= masks[cur_bits]; | ||
188 | + | ||
189 | + if (cur_bits > 0) cur_accum |= code << cur_bits; | ||
190 | + else cur_accum = code; | ||
191 | + | ||
192 | + cur_bits += n_bits; | ||
193 | + | ||
194 | + while (cur_bits >= 8) { | ||
195 | + char_out(cur_accum & 0xff, outs); | ||
196 | + cur_accum >>= 8; | ||
197 | + cur_bits -= 8; | ||
198 | + } | ||
199 | + | ||
200 | + // If the next entry is going to be too big for the code size, | ||
201 | + // then increase it, if possible. | ||
202 | + if (free_ent > maxcode || clear_flg) { | ||
203 | + if (clear_flg) { | ||
204 | + maxcode = MAXCODE((n_bits = g_init_bits)); | ||
205 | + clear_flg = false; | ||
206 | + } else { | ||
207 | + ++n_bits; | ||
208 | + if (n_bits == BITS) maxcode = 1 << BITS; | ||
209 | + else maxcode = MAXCODE(n_bits); | ||
210 | + } | ||
211 | + } | ||
212 | + | ||
213 | + if (code == EOFCode) { | ||
214 | + // At EOF, write the rest of the buffer. | ||
215 | + while (cur_bits > 0) { | ||
216 | + char_out(cur_accum & 0xff, outs); | ||
217 | + cur_accum >>= 8; | ||
218 | + cur_bits -= 8; | ||
219 | + } | ||
220 | + flush_char(outs); | ||
221 | + } | ||
222 | + } | ||
223 | + | ||
224 | + this.encode = encode; | ||
225 | +} | ||
226 | + | ||
227 | +module.exports = LZWEncoder; |
gif-generator/src/lib/NeuQuant.js
0 → 100644
This diff is collapsed. Click to expand it.
gif-generator/src/lib/TypedNeuQuant.js
0 → 100644
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment