오윤석

gif generator 기본 기능

- 프레임 생성
- 렌더링
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;
......
1 +/*
2 + GIFEncoder.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 +
10 +var NeuQuant = require("./TypedNeuQuant.js");
11 +var LZWEncoder = require("./LZWEncoder.js");
12 +
13 +function ByteArray() {
14 + this.page = -1;
15 + this.pages = [];
16 + this.newPage();
17 +}
18 +
19 +ByteArray.pageSize = 4096;
20 +ByteArray.charMap = {};
21 +
22 +for (var i = 0; i < 256; i++) ByteArray.charMap[i] = String.fromCharCode(i);
23 +
24 +ByteArray.prototype.newPage = function () {
25 + this.pages[++this.page] = new Uint8Array(ByteArray.pageSize);
26 + this.cursor = 0;
27 +};
28 +
29 +ByteArray.prototype.getData = function () {
30 + var rv = "";
31 + for (var p = 0; p < this.pages.length; p++) {
32 + for (var i = 0; i < ByteArray.pageSize; i++) {
33 + rv += ByteArray.charMap[this.pages[p][i]];
34 + }
35 + }
36 + return rv;
37 +};
38 +
39 +ByteArray.prototype.writeByte = function (val) {
40 + if (this.cursor >= ByteArray.pageSize) this.newPage();
41 + this.pages[this.page][this.cursor++] = val;
42 +};
43 +
44 +ByteArray.prototype.writeUTFBytes = function (string) {
45 + for (var l = string.length, i = 0; i < l; i++)
46 + this.writeByte(string.charCodeAt(i));
47 +};
48 +
49 +ByteArray.prototype.writeBytes = function (array, offset, length) {
50 + for (var l = length || array.length, i = offset || 0; i < l; i++)
51 + this.writeByte(array[i]);
52 +};
53 +
54 +function GIFEncoder(width, height) {
55 + // image size
56 + this.width = ~~width;
57 + this.height = ~~height;
58 +
59 + // transparent color if given
60 + this.transparent = null;
61 +
62 + // transparent index in color table
63 + this.transIndex = 0;
64 +
65 + // -1 = no repeat, 0 = forever. anything else is repeat count
66 + this.repeat = -1;
67 +
68 + // frame delay (hundredths)
69 + this.delay = 0;
70 +
71 + this.image = null; // current frame
72 + this.pixels = null; // BGR byte array from frame
73 + this.indexedPixels = null; // converted frame indexed to palette
74 + this.colorDepth = null; // number of bit planes
75 + this.colorTab = null; // RGB palette
76 + this.neuQuant = null; // NeuQuant instance that was used to generate this.colorTab.
77 + this.usedEntry = new Array(); // active palette entries
78 + this.palSize = 7; // color table size (bits-1)
79 + this.dispose = -1; // disposal code (-1 = use default)
80 + this.firstFrame = true;
81 + this.sample = 10; // default sample interval for quantizer
82 + this.dither = false; // default dithering
83 + this.globalPalette = false;
84 +
85 + this.out = new ByteArray();
86 +}
87 +
88 +/*
89 + Sets the delay time between each frame, or changes it for subsequent frames
90 + (applies to last frame added)
91 +*/
92 +GIFEncoder.prototype.setDelay = function (milliseconds) {
93 + this.delay = Math.round(milliseconds / 10);
94 +};
95 +
96 +/*
97 + Sets frame rate in frames per second.
98 +*/
99 +GIFEncoder.prototype.setFrameRate = function (fps) {
100 + this.delay = Math.round(100 / fps);
101 +};
102 +
103 +/*
104 + Sets the GIF frame disposal code for the last added frame and any
105 + subsequent frames.
106 +
107 + Default is 0 if no transparent color has been set, otherwise 2.
108 +*/
109 +GIFEncoder.prototype.setDispose = function (disposalCode) {
110 + if (disposalCode >= 0) this.dispose = disposalCode;
111 +};
112 +
113 +/*
114 + Sets the number of times the set of GIF frames should be played.
115 +
116 + -1 = play once
117 + 0 = repeat indefinitely
118 +
119 + Default is -1
120 +
121 + Must be invoked before the first image is added
122 +*/
123 +
124 +GIFEncoder.prototype.setRepeat = function (repeat) {
125 + this.repeat = repeat;
126 +};
127 +
128 +/*
129 + Sets the transparent color for the last added frame and any subsequent
130 + frames. Since all colors are subject to modification in the quantization
131 + process, the color in the final palette for each frame closest to the given
132 + color becomes the transparent color for that frame. May be set to null to
133 + indicate no transparent color.
134 +*/
135 +GIFEncoder.prototype.setTransparent = function (color) {
136 + this.transparent = color;
137 +};
138 +
139 +/*
140 + Adds next GIF frame. The frame is not written immediately, but is
141 + actually deferred until the next frame is received so that timing
142 + data can be inserted. Invoking finish() flushes all frames.
143 +*/
144 +GIFEncoder.prototype.addFrame = function (imageData) {
145 + this.image = imageData;
146 +
147 + this.colorTab =
148 + this.globalPalette && this.globalPalette.slice ? this.globalPalette : null;
149 +
150 + this.getImagePixels(); // convert to correct format if necessary
151 + this.analyzePixels(); // build color table & map pixels
152 +
153 + if (this.globalPalette === true) this.globalPalette = this.colorTab;
154 +
155 + if (this.firstFrame) {
156 + this.writeLSD(); // logical screen descriptior
157 + this.writePalette(); // global color table
158 + if (this.repeat >= 0) {
159 + // use NS app extension to indicate reps
160 + this.writeNetscapeExt();
161 + }
162 + }
163 +
164 + this.writeGraphicCtrlExt(); // write graphic control extension
165 + this.writeImageDesc(); // image descriptor
166 + if (!this.firstFrame && !this.globalPalette) this.writePalette(); // local color table
167 + this.writePixels(); // encode and write pixel data
168 +
169 + this.firstFrame = false;
170 +};
171 +
172 +/*
173 + Adds final trailer to the GIF stream, if you don't call the finish method
174 + the GIF stream will not be valid.
175 +*/
176 +GIFEncoder.prototype.finish = function () {
177 + this.out.writeByte(0x3b); // gif trailer
178 +};
179 +
180 +/*
181 + Sets quality of color quantization (conversion of images to the maximum 256
182 + colors allowed by the GIF specification). Lower values (minimum = 1)
183 + produce better colors, but slow processing significantly. 10 is the
184 + default, and produces good color mapping at reasonable speeds. Values
185 + greater than 20 do not yield significant improvements in speed.
186 +*/
187 +GIFEncoder.prototype.setQuality = function (quality) {
188 + if (quality < 1) quality = 1;
189 + this.sample = quality;
190 +};
191 +
192 +/*
193 + Sets dithering method. Available are:
194 + - FALSE no dithering
195 + - TRUE or FloydSteinberg
196 + - FalseFloydSteinberg
197 + - Stucki
198 + - Atkinson
199 + You can add '-serpentine' to use serpentine scanning
200 +*/
201 +GIFEncoder.prototype.setDither = function (dither) {
202 + if (dither === true) dither = "FloydSteinberg";
203 + this.dither = dither;
204 +};
205 +
206 +/*
207 + Sets global palette for all frames.
208 + You can provide TRUE to create global palette from first picture.
209 + Or an array of r,g,b,r,g,b,...
210 +*/
211 +GIFEncoder.prototype.setGlobalPalette = function (palette) {
212 + this.globalPalette = palette;
213 +};
214 +
215 +/*
216 + Returns global palette used for all frames.
217 + If setGlobalPalette(true) was used, then this function will return
218 + calculated palette after the first frame is added.
219 +*/
220 +GIFEncoder.prototype.getGlobalPalette = function () {
221 + return (
222 + (this.globalPalette &&
223 + this.globalPalette.slice &&
224 + this.globalPalette.slice(0)) ||
225 + this.globalPalette
226 + );
227 +};
228 +
229 +/*
230 + Writes GIF file header
231 +*/
232 +GIFEncoder.prototype.writeHeader = function () {
233 + this.out.writeUTFBytes("GIF89a");
234 +};
235 +
236 +/*
237 + Analyzes current frame colors and creates color map.
238 +*/
239 +GIFEncoder.prototype.analyzePixels = function () {
240 + if (!this.colorTab) {
241 + this.neuQuant = new NeuQuant(this.pixels, this.sample);
242 + this.neuQuant.buildColormap(); // create reduced palette
243 + this.colorTab = this.neuQuant.getColormap();
244 + }
245 +
246 + // map image pixels to new palette
247 + if (this.dither) {
248 + this.ditherPixels(
249 + this.dither.replace("-serpentine", ""),
250 + this.dither.match(/-serpentine/) !== null
251 + );
252 + } else {
253 + this.indexPixels();
254 + }
255 +
256 + this.pixels = null;
257 + this.colorDepth = 8;
258 + this.palSize = 7;
259 +
260 + // get closest match to transparent color if specified
261 + if (this.transparent !== null) {
262 + this.transIndex = this.findClosest(this.transparent, true);
263 + }
264 +};
265 +
266 +/*
267 + Index pixels, without dithering
268 +*/
269 +GIFEncoder.prototype.indexPixels = function (imgq) {
270 + var nPix = this.pixels.length / 3;
271 + this.indexedPixels = new Uint8Array(nPix);
272 + var k = 0;
273 + for (var j = 0; j < nPix; j++) {
274 + var index = this.findClosestRGB(
275 + this.pixels[k++] & 0xff,
276 + this.pixels[k++] & 0xff,
277 + this.pixels[k++] & 0xff
278 + );
279 + this.usedEntry[index] = true;
280 + this.indexedPixels[j] = index;
281 + }
282 +};
283 +
284 +/*
285 + Taken from http://jsbin.com/iXofIji/2/edit by PAEz
286 +*/
287 +GIFEncoder.prototype.ditherPixels = function (kernel, serpentine) {
288 + var kernels = {
289 + FalseFloydSteinberg: [
290 + [3 / 8, 1, 0],
291 + [3 / 8, 0, 1],
292 + [2 / 8, 1, 1],
293 + ],
294 + FloydSteinberg: [
295 + [7 / 16, 1, 0],
296 + [3 / 16, -1, 1],
297 + [5 / 16, 0, 1],
298 + [1 / 16, 1, 1],
299 + ],
300 + Stucki: [
301 + [8 / 42, 1, 0],
302 + [4 / 42, 2, 0],
303 + [2 / 42, -2, 1],
304 + [4 / 42, -1, 1],
305 + [8 / 42, 0, 1],
306 + [4 / 42, 1, 1],
307 + [2 / 42, 2, 1],
308 + [1 / 42, -2, 2],
309 + [2 / 42, -1, 2],
310 + [4 / 42, 0, 2],
311 + [2 / 42, 1, 2],
312 + [1 / 42, 2, 2],
313 + ],
314 + Atkinson: [
315 + [1 / 8, 1, 0],
316 + [1 / 8, 2, 0],
317 + [1 / 8, -1, 1],
318 + [1 / 8, 0, 1],
319 + [1 / 8, 1, 1],
320 + [1 / 8, 0, 2],
321 + ],
322 + };
323 +
324 + if (!kernel || !kernels[kernel]) {
325 + throw "Unknown dithering kernel: " + kernel;
326 + }
327 +
328 + var ds = kernels[kernel];
329 + var index = 0,
330 + height = this.height,
331 + width = this.width,
332 + data = this.pixels;
333 + var direction = serpentine ? -1 : 1;
334 +
335 + this.indexedPixels = new Uint8Array(this.pixels.length / 3);
336 +
337 + for (var y = 0; y < height; y++) {
338 + if (serpentine) direction = direction * -1;
339 +
340 + for (
341 + var x = direction == 1 ? 0 : width - 1, xend = direction == 1 ? width : 0;
342 + x !== xend;
343 + x += direction
344 + ) {
345 + index = y * width + x;
346 + // Get original colour
347 + var idx = index * 3;
348 + var r1 = data[idx];
349 + var g1 = data[idx + 1];
350 + var b1 = data[idx + 2];
351 +
352 + // Get converted colour
353 + idx = this.findClosestRGB(r1, g1, b1);
354 + this.usedEntry[idx] = true;
355 + this.indexedPixels[index] = idx;
356 + idx *= 3;
357 + var r2 = this.colorTab[idx];
358 + var g2 = this.colorTab[idx + 1];
359 + var b2 = this.colorTab[idx + 2];
360 +
361 + var er = r1 - r2;
362 + var eg = g1 - g2;
363 + var eb = b1 - b2;
364 +
365 + for (
366 + var i = direction == 1 ? 0 : ds.length - 1,
367 + end = direction == 1 ? ds.length : 0;
368 + i !== end;
369 + i += direction
370 + ) {
371 + var x1 = ds[i][1]; // *direction; // Should this by timesd by direction?..to make the kernel go in the opposite direction....got no idea....
372 + var y1 = ds[i][2];
373 + if (x1 + x >= 0 && x1 + x < width && y1 + y >= 0 && y1 + y < height) {
374 + var d = ds[i][0];
375 + idx = index + x1 + y1 * width;
376 + idx *= 3;
377 +
378 + data[idx] = Math.max(0, Math.min(255, data[idx] + er * d));
379 + data[idx + 1] = Math.max(0, Math.min(255, data[idx + 1] + eg * d));
380 + data[idx + 2] = Math.max(0, Math.min(255, data[idx + 2] + eb * d));
381 + }
382 + }
383 + }
384 + }
385 +};
386 +
387 +/*
388 + Returns index of palette color closest to c
389 +*/
390 +GIFEncoder.prototype.findClosest = function (c, used) {
391 + return this.findClosestRGB(
392 + (c & 0xff0000) >> 16,
393 + (c & 0x00ff00) >> 8,
394 + c & 0x0000ff,
395 + used
396 + );
397 +};
398 +
399 +GIFEncoder.prototype.findClosestRGB = function (r, g, b, used) {
400 + if (this.colorTab === null) return -1;
401 +
402 + if (this.neuQuant && !used) {
403 + return this.neuQuant.lookupRGB(r, g, b);
404 + }
405 +
406 + var c = b | (g << 8) | (r << 16);
407 +
408 + var minpos = 0;
409 + var dmin = 256 * 256 * 256;
410 + var len = this.colorTab.length;
411 +
412 + for (var i = 0, index = 0; i < len; index++) {
413 + var dr = r - (this.colorTab[i++] & 0xff);
414 + var dg = g - (this.colorTab[i++] & 0xff);
415 + var db = b - (this.colorTab[i++] & 0xff);
416 + var d = dr * dr + dg * dg + db * db;
417 + if ((!used || this.usedEntry[index]) && d < dmin) {
418 + dmin = d;
419 + minpos = index;
420 + }
421 + }
422 +
423 + return minpos;
424 +};
425 +
426 +/*
427 + Extracts image pixels into byte array pixels
428 + (removes alphachannel from canvas imagedata)
429 +*/
430 +GIFEncoder.prototype.getImagePixels = function () {
431 + var w = this.width;
432 + var h = this.height;
433 + this.pixels = new Uint8Array(w * h * 3);
434 +
435 + var data = this.image;
436 + var srcPos = 0;
437 + var count = 0;
438 +
439 + for (var i = 0; i < h; i++) {
440 + for (var j = 0; j < w; j++) {
441 + this.pixels[count++] = data[srcPos++];
442 + this.pixels[count++] = data[srcPos++];
443 + this.pixels[count++] = data[srcPos++];
444 + srcPos++;
445 + }
446 + }
447 +};
448 +
449 +/*
450 + Writes Graphic Control Extension
451 +*/
452 +GIFEncoder.prototype.writeGraphicCtrlExt = function () {
453 + this.out.writeByte(0x21); // extension introducer
454 + this.out.writeByte(0xf9); // GCE label
455 + this.out.writeByte(4); // data block size
456 +
457 + var transp, disp;
458 + if (this.transparent === null) {
459 + transp = 0;
460 + disp = 0; // dispose = no action
461 + } else {
462 + transp = 1;
463 + disp = 2; // force clear if using transparent color
464 + }
465 +
466 + if (this.dispose >= 0) {
467 + disp = this.dispose & 7; // user override
468 + }
469 + disp <<= 2;
470 +
471 + // packed fields
472 + this.out.writeByte(
473 + 0 | // 1:3 reserved
474 + disp | // 4:6 disposal
475 + 0 | // 7 user input - 0 = none
476 + transp // 8 transparency flag
477 + );
478 +
479 + this.writeShort(this.delay); // delay x 1/100 sec
480 + this.out.writeByte(this.transIndex); // transparent color index
481 + this.out.writeByte(0); // block terminator
482 +};
483 +
484 +/*
485 + Writes Image Descriptor
486 +*/
487 +GIFEncoder.prototype.writeImageDesc = function () {
488 + this.out.writeByte(0x2c); // image separator
489 + this.writeShort(0); // image position x,y = 0,0
490 + this.writeShort(0);
491 + this.writeShort(this.width); // image size
492 + this.writeShort(this.height);
493 +
494 + // packed fields
495 + if (this.firstFrame || this.globalPalette) {
496 + // no LCT - GCT is used for first (or only) frame
497 + this.out.writeByte(0);
498 + } else {
499 + // specify normal LCT
500 + this.out.writeByte(
501 + 0x80 | // 1 local color table 1=yes
502 + 0 | // 2 interlace - 0=no
503 + 0 | // 3 sorted - 0=no
504 + 0 | // 4-5 reserved
505 + this.palSize // 6-8 size of color table
506 + );
507 + }
508 +};
509 +
510 +/*
511 + Writes Logical Screen Descriptor
512 +*/
513 +GIFEncoder.prototype.writeLSD = function () {
514 + // logical screen size
515 + this.writeShort(this.width);
516 + this.writeShort(this.height);
517 +
518 + // packed fields
519 + this.out.writeByte(
520 + 0x80 | // 1 : global color table flag = 1 (gct used)
521 + 0x70 | // 2-4 : color resolution = 7
522 + 0x00 | // 5 : gct sort flag = 0
523 + this.palSize // 6-8 : gct size
524 + );
525 +
526 + this.out.writeByte(0); // background color index
527 + this.out.writeByte(0); // pixel aspect ratio - assume 1:1
528 +};
529 +
530 +/*
531 + Writes Netscape application extension to define repeat count.
532 +*/
533 +GIFEncoder.prototype.writeNetscapeExt = function () {
534 + this.out.writeByte(0x21); // extension introducer
535 + this.out.writeByte(0xff); // app extension label
536 + this.out.writeByte(11); // block size
537 + this.out.writeUTFBytes("NETSCAPE2.0"); // app id + auth code
538 + this.out.writeByte(3); // sub-block size
539 + this.out.writeByte(1); // loop sub-block id
540 + this.writeShort(this.repeat); // loop count (extra iterations, 0=repeat forever)
541 + this.out.writeByte(0); // block terminator
542 +};
543 +
544 +/*
545 + Writes color table
546 +*/
547 +GIFEncoder.prototype.writePalette = function () {
548 + this.out.writeBytes(this.colorTab);
549 + var n = 3 * 256 - this.colorTab.length;
550 + for (var i = 0; i < n; i++) this.out.writeByte(0);
551 +};
552 +
553 +GIFEncoder.prototype.writeShort = function (pValue) {
554 + this.out.writeByte(pValue & 0xff);
555 + this.out.writeByte((pValue >> 8) & 0xff);
556 +};
557 +
558 +/*
559 + Encodes and writes pixel data
560 +*/
561 +GIFEncoder.prototype.writePixels = function () {
562 + var enc = new LZWEncoder(
563 + this.width,
564 + this.height,
565 + this.indexedPixels,
566 + this.colorDepth
567 + );
568 + enc.encode(this.out);
569 +};
570 +
571 +/*
572 + Retrieves the GIF stream
573 +*/
574 +GIFEncoder.prototype.stream = function () {
575 + return this.out;
576 +};
577 +
578 +module.exports = GIFEncoder;
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;
1 +/* NeuQuant Neural-Net Quantization Algorithm
2 + * ------------------------------------------
3 + *
4 + * Copyright (c) 1994 Anthony Dekker
5 + *
6 + * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
7 + * See "Kohonen neural networks for optimal colour quantization"
8 + * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
9 + * for a discussion of the algorithm.
10 + * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
11 + *
12 + * Any party obtaining a copy of these files from the author, directly or
13 + * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
14 + * world-wide, paid up, royalty-free, nonexclusive right and license to deal
15 + * in this software and documentation files (the "Software"), including without
16 + * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 + * and/or sell copies of the Software, and to permit persons who receive
18 + * copies from any such party to do so, with the only requirement being
19 + * that this copyright notice remain intact.
20 + *
21 + * (JavaScript port 2012 by Johan Nordberg)
22 + */
23 +
24 +function toInt(v) {
25 + return ~~v;
26 +}
27 +
28 +var ncycles = 100; // number of learning cycles
29 +var netsize = 256; // number of colors used
30 +var maxnetpos = netsize - 1;
31 +
32 +// defs for freq and bias
33 +var netbiasshift = 4; // bias for colour values
34 +var intbiasshift = 16; // bias for fractions
35 +var intbias = 1 << intbiasshift;
36 +var gammashift = 10;
37 +var gamma = 1 << gammashift;
38 +var betashift = 10;
39 +var beta = intbias >> betashift; /* beta = 1/1024 */
40 +var betagamma = intbias << (gammashift - betashift);
41 +
42 +// defs for decreasing radius factor
43 +var initrad = netsize >> 3; // for 256 cols, radius starts
44 +var radiusbiasshift = 6; // at 32.0 biased by 6 bits
45 +var radiusbias = 1 << radiusbiasshift;
46 +var initradius = initrad * radiusbias; //and decreases by a
47 +var radiusdec = 30; // factor of 1/30 each cycle
48 +
49 +// defs for decreasing alpha factor
50 +var alphabiasshift = 10; // alpha starts at 1.0
51 +var initalpha = 1 << alphabiasshift;
52 +var alphadec; // biased by 10 bits
53 +
54 +/* radbias and alpharadbias used for radpower calculation */
55 +var radbiasshift = 8;
56 +var radbias = 1 << radbiasshift;
57 +var alpharadbshift = alphabiasshift + radbiasshift;
58 +var alpharadbias = 1 << alpharadbshift;
59 +
60 +// four primes near 500 - assume no image has a length so large that it is
61 +// divisible by all four primes
62 +var prime1 = 499;
63 +var prime2 = 491;
64 +var prime3 = 487;
65 +var prime4 = 503;
66 +var minpicturebytes = 3 * prime4;
67 +
68 +/*
69 + Constructor: NeuQuant
70 +
71 + Arguments:
72 +
73 + pixels - array of pixels in RGB format
74 + samplefac - sampling factor 1 to 30 where lower is better quality
75 +
76 + >
77 + > pixels = [r, g, b, r, g, b, r, g, b, ..]
78 + >
79 +*/
80 +function NeuQuant(pixels, samplefac) {
81 + var network; // int[netsize][4]
82 + var netindex; // for network lookup - really 256
83 +
84 + // bias and freq arrays for learning
85 + var bias;
86 + var freq;
87 + var radpower;
88 +
89 + /*
90 + Private Method: init
91 +
92 + sets up arrays
93 + */
94 + function init() {
95 + network = [];
96 + netindex = [];
97 + bias = [];
98 + freq = [];
99 + radpower = [];
100 +
101 + var i, v;
102 + for (i = 0; i < netsize; i++) {
103 + v = (i << (netbiasshift + 8)) / netsize;
104 + network[i] = [v, v, v];
105 + freq[i] = intbias / netsize;
106 + bias[i] = 0;
107 + }
108 + }
109 +
110 + /*
111 + Private Method: unbiasnet
112 +
113 + unbiases network to give byte values 0..255 and record position i to prepare for sort
114 + */
115 + function unbiasnet() {
116 + for (var i = 0; i < netsize; i++) {
117 + network[i][0] >>= netbiasshift;
118 + network[i][1] >>= netbiasshift;
119 + network[i][2] >>= netbiasshift;
120 + network[i][3] = i; // record color number
121 + }
122 + }
123 +
124 + /*
125 + Private Method: altersingle
126 +
127 + moves neuron *i* towards biased (b,g,r) by factor *alpha*
128 + */
129 + function altersingle(alpha, i, b, g, r) {
130 + network[i][0] -= (alpha * (network[i][0] - b)) / initalpha;
131 + network[i][1] -= (alpha * (network[i][1] - g)) / initalpha;
132 + network[i][2] -= (alpha * (network[i][2] - r)) / initalpha;
133 + }
134 +
135 + /*
136 + Private Method: alterneigh
137 +
138 + moves neurons in *radius* around index *i* towards biased (b,g,r) by factor *alpha*
139 + */
140 + function alterneigh(radius, i, b, g, r) {
141 + var lo = Math.abs(i - radius);
142 + var hi = Math.min(i + radius, netsize);
143 +
144 + var j = i + 1;
145 + var k = i - 1;
146 + var m = 1;
147 +
148 + var p, a;
149 + while (j < hi || k > lo) {
150 + a = radpower[m++];
151 +
152 + if (j < hi) {
153 + p = network[j++];
154 + p[0] -= (a * (p[0] - b)) / alpharadbias;
155 + p[1] -= (a * (p[1] - g)) / alpharadbias;
156 + p[2] -= (a * (p[2] - r)) / alpharadbias;
157 + }
158 +
159 + if (k > lo) {
160 + p = network[k--];
161 + p[0] -= (a * (p[0] - b)) / alpharadbias;
162 + p[1] -= (a * (p[1] - g)) / alpharadbias;
163 + p[2] -= (a * (p[2] - r)) / alpharadbias;
164 + }
165 + }
166 + }
167 +
168 + /*
169 + Private Method: contest
170 +
171 + searches for biased BGR values
172 + */
173 + function contest(b, g, r) {
174 + /*
175 + finds closest neuron (min dist) and updates freq
176 + finds best neuron (min dist-bias) and returns position
177 + for frequently chosen neurons, freq[i] is high and bias[i] is negative
178 + bias[i] = gamma * ((1 / netsize) - freq[i])
179 + */
180 +
181 + var bestd = ~(1 << 31);
182 + var bestbiasd = bestd;
183 + var bestpos = -1;
184 + var bestbiaspos = bestpos;
185 +
186 + var i, n, dist, biasdist, betafreq;
187 + for (i = 0; i < netsize; i++) {
188 + n = network[i];
189 +
190 + dist = Math.abs(n[0] - b) + Math.abs(n[1] - g) + Math.abs(n[2] - r);
191 + if (dist < bestd) {
192 + bestd = dist;
193 + bestpos = i;
194 + }
195 +
196 + biasdist = dist - (bias[i] >> (intbiasshift - netbiasshift));
197 + if (biasdist < bestbiasd) {
198 + bestbiasd = biasdist;
199 + bestbiaspos = i;
200 + }
201 +
202 + betafreq = freq[i] >> betashift;
203 + freq[i] -= betafreq;
204 + bias[i] += betafreq << gammashift;
205 + }
206 +
207 + freq[bestpos] += beta;
208 + bias[bestpos] -= betagamma;
209 +
210 + return bestbiaspos;
211 + }
212 +
213 + /*
214 + Private Method: inxbuild
215 +
216 + sorts network and builds netindex[0..255]
217 + */
218 + function inxbuild() {
219 + var i,
220 + j,
221 + p,
222 + q,
223 + smallpos,
224 + smallval,
225 + previouscol = 0,
226 + startpos = 0;
227 + for (i = 0; i < netsize; i++) {
228 + p = network[i];
229 + smallpos = i;
230 + smallval = p[1]; // index on g
231 + // find smallest in i..netsize-1
232 + for (j = i + 1; j < netsize; j++) {
233 + q = network[j];
234 + if (q[1] < smallval) {
235 + // index on g
236 + smallpos = j;
237 + smallval = q[1]; // index on g
238 + }
239 + }
240 + q = network[smallpos];
241 + // swap p (i) and q (smallpos) entries
242 + if (i != smallpos) {
243 + j = q[0];
244 + q[0] = p[0];
245 + p[0] = j;
246 + j = q[1];
247 + q[1] = p[1];
248 + p[1] = j;
249 + j = q[2];
250 + q[2] = p[2];
251 + p[2] = j;
252 + j = q[3];
253 + q[3] = p[3];
254 + p[3] = j;
255 + }
256 + // smallval entry is now in position i
257 +
258 + if (smallval != previouscol) {
259 + netindex[previouscol] = (startpos + i) >> 1;
260 + for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
261 + previouscol = smallval;
262 + startpos = i;
263 + }
264 + }
265 + netindex[previouscol] = (startpos + maxnetpos) >> 1;
266 + for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; // really 256
267 + }
268 +
269 + /*
270 + Private Method: inxsearch
271 +
272 + searches for BGR values 0..255 and returns a color index
273 + */
274 + function inxsearch(b, g, r) {
275 + var a, p, dist;
276 +
277 + var bestd = 1000; // biggest possible dist is 256*3
278 + var best = -1;
279 +
280 + var i = netindex[g]; // index on g
281 + var j = i - 1; // start at netindex[g] and work outwards
282 +
283 + while (i < netsize || j >= 0) {
284 + if (i < netsize) {
285 + p = network[i];
286 + dist = p[1] - g; // inx key
287 + if (dist >= bestd) i = netsize;
288 + // stop iter
289 + else {
290 + i++;
291 + if (dist < 0) dist = -dist;
292 + a = p[0] - b;
293 + if (a < 0) a = -a;
294 + dist += a;
295 + if (dist < bestd) {
296 + a = p[2] - r;
297 + if (a < 0) a = -a;
298 + dist += a;
299 + if (dist < bestd) {
300 + bestd = dist;
301 + best = p[3];
302 + }
303 + }
304 + }
305 + }
306 + if (j >= 0) {
307 + p = network[j];
308 + dist = g - p[1]; // inx key - reverse dif
309 + if (dist >= bestd) j = -1;
310 + // stop iter
311 + else {
312 + j--;
313 + if (dist < 0) dist = -dist;
314 + a = p[0] - b;
315 + if (a < 0) a = -a;
316 + dist += a;
317 + if (dist < bestd) {
318 + a = p[2] - r;
319 + if (a < 0) a = -a;
320 + dist += a;
321 + if (dist < bestd) {
322 + bestd = dist;
323 + best = p[3];
324 + }
325 + }
326 + }
327 + }
328 + }
329 +
330 + return best;
331 + }
332 +
333 + /*
334 + Private Method: learn
335 +
336 + "Main Learning Loop"
337 + */
338 + function learn() {
339 + var i;
340 +
341 + var lengthcount = pixels.length;
342 + var alphadec = toInt(30 + (samplefac - 1) / 3);
343 + var samplepixels = toInt(lengthcount / (3 * samplefac));
344 + var delta = toInt(samplepixels / ncycles);
345 + var alpha = initalpha;
346 + var radius = initradius;
347 +
348 + var rad = radius >> radiusbiasshift;
349 +
350 + if (rad <= 1) rad = 0;
351 + for (i = 0; i < rad; i++)
352 + radpower[i] = toInt(
353 + alpha * (((rad * rad - i * i) * radbias) / (rad * rad))
354 + );
355 +
356 + var step;
357 + if (lengthcount < minpicturebytes) {
358 + samplefac = 1;
359 + step = 3;
360 + } else if (lengthcount % prime1 !== 0) {
361 + step = 3 * prime1;
362 + } else if (lengthcount % prime2 !== 0) {
363 + step = 3 * prime2;
364 + } else if (lengthcount % prime3 !== 0) {
365 + step = 3 * prime3;
366 + } else {
367 + step = 3 * prime4;
368 + }
369 +
370 + var b, g, r, j;
371 + var pix = 0; // current pixel
372 +
373 + i = 0;
374 + while (i < samplepixels) {
375 + b = (pixels[pix] & 0xff) << netbiasshift;
376 + g = (pixels[pix + 1] & 0xff) << netbiasshift;
377 + r = (pixels[pix + 2] & 0xff) << netbiasshift;
378 +
379 + j = contest(b, g, r);
380 +
381 + altersingle(alpha, j, b, g, r);
382 + if (rad !== 0) alterneigh(rad, j, b, g, r); // alter neighbours
383 +
384 + pix += step;
385 + if (pix >= lengthcount) pix -= lengthcount;
386 +
387 + i++;
388 +
389 + if (delta === 0) delta = 1;
390 + if (i % delta === 0) {
391 + alpha -= alpha / alphadec;
392 + radius -= radius / radiusdec;
393 + rad = radius >> radiusbiasshift;
394 +
395 + if (rad <= 1) rad = 0;
396 + for (j = 0; j < rad; j++)
397 + radpower[j] = toInt(
398 + alpha * (((rad * rad - j * j) * radbias) / (rad * rad))
399 + );
400 + }
401 + }
402 + }
403 +
404 + /*
405 + Method: buildColormap
406 +
407 + 1. initializes network
408 + 2. trains it
409 + 3. removes misconceptions
410 + 4. builds colorindex
411 + */
412 + function buildColormap() {
413 + init();
414 + learn();
415 + unbiasnet();
416 + inxbuild();
417 + }
418 + this.buildColormap = buildColormap;
419 +
420 + /*
421 + Method: getColormap
422 +
423 + builds colormap from the index
424 +
425 + returns array in the format:
426 +
427 + >
428 + > [r, g, b, r, g, b, r, g, b, ..]
429 + >
430 + */
431 + function getColormap() {
432 + var map = [];
433 + var index = [];
434 +
435 + for (var i = 0; i < netsize; i++) index[network[i][3]] = i;
436 +
437 + var k = 0;
438 + for (var l = 0; l < netsize; l++) {
439 + var j = index[l];
440 + map[k++] = network[j][0];
441 + map[k++] = network[j][1];
442 + map[k++] = network[j][2];
443 + }
444 + return map;
445 + }
446 + this.getColormap = getColormap;
447 +
448 + /*
449 + Method: lookupRGB
450 +
451 + looks for the closest *r*, *g*, *b* color in the map and
452 + returns its index
453 + */
454 + this.lookupRGB = inxsearch;
455 +}
456 +
457 +module.exports = NeuQuant;
1 +/* NeuQuant Neural-Net Quantization Algorithm
2 + * ------------------------------------------
3 + *
4 + * Copyright (c) 1994 Anthony Dekker
5 + *
6 + * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
7 + * See "Kohonen neural networks for optimal colour quantization"
8 + * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
9 + * for a discussion of the algorithm.
10 + * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
11 + *
12 + * Any party obtaining a copy of these files from the author, directly or
13 + * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
14 + * world-wide, paid up, royalty-free, nonexclusive right and license to deal
15 + * in this software and documentation files (the "Software"), including without
16 + * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 + * and/or sell copies of the Software, and to permit persons who receive
18 + * copies from any such party to do so, with the only requirement being
19 + * that this copyright notice remain intact.
20 + *
21 + * (JavaScript port 2012 by Johan Nordberg)
22 + */
23 +
24 +var ncycles = 100; // number of learning cycles
25 +var netsize = 256; // number of colors used
26 +var maxnetpos = netsize - 1;
27 +
28 +// defs for freq and bias
29 +var netbiasshift = 4; // bias for colour values
30 +var intbiasshift = 16; // bias for fractions
31 +var intbias = 1 << intbiasshift;
32 +var gammashift = 10;
33 +var gamma = 1 << gammashift;
34 +var betashift = 10;
35 +var beta = intbias >> betashift; /* beta = 1/1024 */
36 +var betagamma = intbias << (gammashift - betashift);
37 +
38 +// defs for decreasing radius factor
39 +var initrad = netsize >> 3; // for 256 cols, radius starts
40 +var radiusbiasshift = 6; // at 32.0 biased by 6 bits
41 +var radiusbias = 1 << radiusbiasshift;
42 +var initradius = initrad * radiusbias; //and decreases by a
43 +var radiusdec = 30; // factor of 1/30 each cycle
44 +
45 +// defs for decreasing alpha factor
46 +var alphabiasshift = 10; // alpha starts at 1.0
47 +var initalpha = 1 << alphabiasshift;
48 +var alphadec; // biased by 10 bits
49 +
50 +/* radbias and alpharadbias used for radpower calculation */
51 +var radbiasshift = 8;
52 +var radbias = 1 << radbiasshift;
53 +var alpharadbshift = alphabiasshift + radbiasshift;
54 +var alpharadbias = 1 << alpharadbshift;
55 +
56 +// four primes near 500 - assume no image has a length so large that it is
57 +// divisible by all four primes
58 +var prime1 = 499;
59 +var prime2 = 491;
60 +var prime3 = 487;
61 +var prime4 = 503;
62 +var minpicturebytes = 3 * prime4;
63 +
64 +/*
65 + Constructor: NeuQuant
66 +
67 + Arguments:
68 +
69 + pixels - array of pixels in RGB format
70 + samplefac - sampling factor 1 to 30 where lower is better quality
71 +
72 + >
73 + > pixels = [r, g, b, r, g, b, r, g, b, ..]
74 + >
75 +*/
76 +function NeuQuant(pixels, samplefac) {
77 + var network; // int[netsize][4]
78 + var netindex; // for network lookup - really 256
79 +
80 + // bias and freq arrays for learning
81 + var bias;
82 + var freq;
83 + var radpower;
84 +
85 + /*
86 + Private Method: init
87 +
88 + sets up arrays
89 + */
90 + function init() {
91 + network = [];
92 + netindex = new Int32Array(256);
93 + bias = new Int32Array(netsize);
94 + freq = new Int32Array(netsize);
95 + radpower = new Int32Array(netsize >> 3);
96 +
97 + var i, v;
98 + for (i = 0; i < netsize; i++) {
99 + v = (i << (netbiasshift + 8)) / netsize;
100 + network[i] = new Float64Array([v, v, v, 0]);
101 + //network[i] = [v, v, v, 0]
102 + freq[i] = intbias / netsize;
103 + bias[i] = 0;
104 + }
105 + }
106 +
107 + /*
108 + Private Method: unbiasnet
109 +
110 + unbiases network to give byte values 0..255 and record position i to prepare for sort
111 + */
112 + function unbiasnet() {
113 + for (var i = 0; i < netsize; i++) {
114 + network[i][0] >>= netbiasshift;
115 + network[i][1] >>= netbiasshift;
116 + network[i][2] >>= netbiasshift;
117 + network[i][3] = i; // record color number
118 + }
119 + }
120 +
121 + /*
122 + Private Method: altersingle
123 +
124 + moves neuron *i* towards biased (b,g,r) by factor *alpha*
125 + */
126 + function altersingle(alpha, i, b, g, r) {
127 + network[i][0] -= (alpha * (network[i][0] - b)) / initalpha;
128 + network[i][1] -= (alpha * (network[i][1] - g)) / initalpha;
129 + network[i][2] -= (alpha * (network[i][2] - r)) / initalpha;
130 + }
131 +
132 + /*
133 + Private Method: alterneigh
134 +
135 + moves neurons in *radius* around index *i* towards biased (b,g,r) by factor *alpha*
136 + */
137 + function alterneigh(radius, i, b, g, r) {
138 + var lo = Math.abs(i - radius);
139 + var hi = Math.min(i + radius, netsize);
140 +
141 + var j = i + 1;
142 + var k = i - 1;
143 + var m = 1;
144 +
145 + var p, a;
146 + while (j < hi || k > lo) {
147 + a = radpower[m++];
148 +
149 + if (j < hi) {
150 + p = network[j++];
151 + p[0] -= (a * (p[0] - b)) / alpharadbias;
152 + p[1] -= (a * (p[1] - g)) / alpharadbias;
153 + p[2] -= (a * (p[2] - r)) / alpharadbias;
154 + }
155 +
156 + if (k > lo) {
157 + p = network[k--];
158 + p[0] -= (a * (p[0] - b)) / alpharadbias;
159 + p[1] -= (a * (p[1] - g)) / alpharadbias;
160 + p[2] -= (a * (p[2] - r)) / alpharadbias;
161 + }
162 + }
163 + }
164 +
165 + /*
166 + Private Method: contest
167 +
168 + searches for biased BGR values
169 + */
170 + function contest(b, g, r) {
171 + /*
172 + finds closest neuron (min dist) and updates freq
173 + finds best neuron (min dist-bias) and returns position
174 + for frequently chosen neurons, freq[i] is high and bias[i] is negative
175 + bias[i] = gamma * ((1 / netsize) - freq[i])
176 + */
177 +
178 + var bestd = ~(1 << 31);
179 + var bestbiasd = bestd;
180 + var bestpos = -1;
181 + var bestbiaspos = bestpos;
182 +
183 + var i, n, dist, biasdist, betafreq;
184 + for (i = 0; i < netsize; i++) {
185 + n = network[i];
186 +
187 + dist = Math.abs(n[0] - b) + Math.abs(n[1] - g) + Math.abs(n[2] - r);
188 + if (dist < bestd) {
189 + bestd = dist;
190 + bestpos = i;
191 + }
192 +
193 + biasdist = dist - (bias[i] >> (intbiasshift - netbiasshift));
194 + if (biasdist < bestbiasd) {
195 + bestbiasd = biasdist;
196 + bestbiaspos = i;
197 + }
198 +
199 + betafreq = freq[i] >> betashift;
200 + freq[i] -= betafreq;
201 + bias[i] += betafreq << gammashift;
202 + }
203 +
204 + freq[bestpos] += beta;
205 + bias[bestpos] -= betagamma;
206 +
207 + return bestbiaspos;
208 + }
209 +
210 + /*
211 + Private Method: inxbuild
212 +
213 + sorts network and builds netindex[0..255]
214 + */
215 + function inxbuild() {
216 + var i,
217 + j,
218 + p,
219 + q,
220 + smallpos,
221 + smallval,
222 + previouscol = 0,
223 + startpos = 0;
224 + for (i = 0; i < netsize; i++) {
225 + p = network[i];
226 + smallpos = i;
227 + smallval = p[1]; // index on g
228 + // find smallest in i..netsize-1
229 + for (j = i + 1; j < netsize; j++) {
230 + q = network[j];
231 + if (q[1] < smallval) {
232 + // index on g
233 + smallpos = j;
234 + smallval = q[1]; // index on g
235 + }
236 + }
237 + q = network[smallpos];
238 + // swap p (i) and q (smallpos) entries
239 + if (i != smallpos) {
240 + j = q[0];
241 + q[0] = p[0];
242 + p[0] = j;
243 + j = q[1];
244 + q[1] = p[1];
245 + p[1] = j;
246 + j = q[2];
247 + q[2] = p[2];
248 + p[2] = j;
249 + j = q[3];
250 + q[3] = p[3];
251 + p[3] = j;
252 + }
253 + // smallval entry is now in position i
254 +
255 + if (smallval != previouscol) {
256 + netindex[previouscol] = (startpos + i) >> 1;
257 + for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
258 + previouscol = smallval;
259 + startpos = i;
260 + }
261 + }
262 + netindex[previouscol] = (startpos + maxnetpos) >> 1;
263 + for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; // really 256
264 + }
265 +
266 + /*
267 + Private Method: inxsearch
268 +
269 + searches for BGR values 0..255 and returns a color index
270 + */
271 + function inxsearch(b, g, r) {
272 + var a, p, dist;
273 +
274 + var bestd = 1000; // biggest possible dist is 256*3
275 + var best = -1;
276 +
277 + var i = netindex[g]; // index on g
278 + var j = i - 1; // start at netindex[g] and work outwards
279 +
280 + while (i < netsize || j >= 0) {
281 + if (i < netsize) {
282 + p = network[i];
283 + dist = p[1] - g; // inx key
284 + if (dist >= bestd) i = netsize;
285 + // stop iter
286 + else {
287 + i++;
288 + if (dist < 0) dist = -dist;
289 + a = p[0] - b;
290 + if (a < 0) a = -a;
291 + dist += a;
292 + if (dist < bestd) {
293 + a = p[2] - r;
294 + if (a < 0) a = -a;
295 + dist += a;
296 + if (dist < bestd) {
297 + bestd = dist;
298 + best = p[3];
299 + }
300 + }
301 + }
302 + }
303 + if (j >= 0) {
304 + p = network[j];
305 + dist = g - p[1]; // inx key - reverse dif
306 + if (dist >= bestd) j = -1;
307 + // stop iter
308 + else {
309 + j--;
310 + if (dist < 0) dist = -dist;
311 + a = p[0] - b;
312 + if (a < 0) a = -a;
313 + dist += a;
314 + if (dist < bestd) {
315 + a = p[2] - r;
316 + if (a < 0) a = -a;
317 + dist += a;
318 + if (dist < bestd) {
319 + bestd = dist;
320 + best = p[3];
321 + }
322 + }
323 + }
324 + }
325 + }
326 +
327 + return best;
328 + }
329 +
330 + /*
331 + Private Method: learn
332 +
333 + "Main Learning Loop"
334 + */
335 + function learn() {
336 + var i;
337 +
338 + var lengthcount = pixels.length;
339 + var alphadec = 30 + (samplefac - 1) / 3;
340 + var samplepixels = lengthcount / (3 * samplefac);
341 + var delta = ~~(samplepixels / ncycles);
342 + var alpha = initalpha;
343 + var radius = initradius;
344 +
345 + var rad = radius >> radiusbiasshift;
346 +
347 + if (rad <= 1) rad = 0;
348 + for (i = 0; i < rad; i++)
349 + radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
350 +
351 + var step;
352 + if (lengthcount < minpicturebytes) {
353 + samplefac = 1;
354 + step = 3;
355 + } else if (lengthcount % prime1 !== 0) {
356 + step = 3 * prime1;
357 + } else if (lengthcount % prime2 !== 0) {
358 + step = 3 * prime2;
359 + } else if (lengthcount % prime3 !== 0) {
360 + step = 3 * prime3;
361 + } else {
362 + step = 3 * prime4;
363 + }
364 +
365 + var b, g, r, j;
366 + var pix = 0; // current pixel
367 +
368 + i = 0;
369 + while (i < samplepixels) {
370 + b = (pixels[pix] & 0xff) << netbiasshift;
371 + g = (pixels[pix + 1] & 0xff) << netbiasshift;
372 + r = (pixels[pix + 2] & 0xff) << netbiasshift;
373 +
374 + j = contest(b, g, r);
375 +
376 + altersingle(alpha, j, b, g, r);
377 + if (rad !== 0) alterneigh(rad, j, b, g, r); // alter neighbours
378 +
379 + pix += step;
380 + if (pix >= lengthcount) pix -= lengthcount;
381 +
382 + i++;
383 +
384 + if (delta === 0) delta = 1;
385 + if (i % delta === 0) {
386 + alpha -= alpha / alphadec;
387 + radius -= radius / radiusdec;
388 + rad = radius >> radiusbiasshift;
389 +
390 + if (rad <= 1) rad = 0;
391 + for (j = 0; j < rad; j++)
392 + radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
393 + }
394 + }
395 + }
396 +
397 + /*
398 + Method: buildColormap
399 +
400 + 1. initializes network
401 + 2. trains it
402 + 3. removes misconceptions
403 + 4. builds colorindex
404 + */
405 + function buildColormap() {
406 + init();
407 + learn();
408 + unbiasnet();
409 + inxbuild();
410 + }
411 + this.buildColormap = buildColormap;
412 +
413 + /*
414 + Method: getColormap
415 +
416 + builds colormap from the index
417 +
418 + returns array in the format:
419 +
420 + >
421 + > [r, g, b, r, g, b, r, g, b, ..]
422 + >
423 + */
424 + function getColormap() {
425 + var map = [];
426 + var index = [];
427 +
428 + for (var i = 0; i < netsize; i++) index[network[i][3]] = i;
429 +
430 + var k = 0;
431 + for (var l = 0; l < netsize; l++) {
432 + var j = index[l];
433 + map[k++] = network[j][0];
434 + map[k++] = network[j][1];
435 + map[k++] = network[j][2];
436 + }
437 + return map;
438 + }
439 + this.getColormap = getColormap;
440 +
441 + /*
442 + Method: lookupRGB
443 +
444 + looks for the closest *r*, *g*, *b* color in the map and
445 + returns its index
446 + */
447 + this.lookupRGB = inxsearch;
448 +}
449 +
450 +module.exports = NeuQuant;