├── Actionscript
├── GIFEncoder.as
├── LZWEncoder.as
├── NeuQuant.as
├── README
└── convert.rb
├── Demos
├── b64.js
├── canvascycle
│ ├── bitmap.js
│ ├── cookie.js
│ ├── framecount.js
│ ├── gif.gif
│ ├── index.html
│ ├── main.js
│ ├── oop.js
│ ├── palette.js
│ ├── scenes.js
│ ├── style.css
│ ├── test.html
│ ├── tools.js
│ └── tween.js
├── clock.gif
├── clock.html
├── converted_animation.gif
├── raw_canvas.png
└── test.html
├── GIFEncoder.js
├── LICENSE
├── LZWEncoder.js
├── NeuQuant.js
├── README.md
├── anim.js
├── animWorker.js
└── b64.js
/Actionscript/GIFEncoder.as:
--------------------------------------------------------------------------------
1 | /**
2 | * This class lets you encode animated GIF files
3 | * Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
5 | * @author Thibault Imbert (AS3 version - bytearray.org)
6 | * @version 0.1 AS3 implementation
7 | */
8 |
9 | package org.bytearray.gif.encoder
10 | {
11 | import flash.utils.ByteArray;
12 | import flash.display.BitmapData;
13 | import flash.display.Bitmap;
14 | import org.bytearray.gif.encoder.NeuQuant
15 | import flash.net.URLRequestHeader;
16 | import flash.net.URLRequestMethod;
17 | import flash.net.URLRequest;
18 | import flash.net.navigateToURL;
19 |
20 | public class GIFEncoder
21 | {
22 | private var width:int // image size
23 | private var height:int;
24 | private var transparent:* = null; // transparent color if given
25 | private var transIndex:int; // transparent index in color table
26 | private var repeat:int = -1; // no repeat
27 | private var delay:int = 0; // frame delay (hundredths)
28 | private var started:Boolean = false; // ready to output frames
29 | private var out:ByteArray;
30 | private var image:Bitmap; // current frame
31 | private var pixels:ByteArray; // BGR byte array from frame
32 | private var indexedPixels:ByteArray // converted frame indexed to palette
33 | private var colorDepth:int; // number of bit planes
34 | private var colorTab:ByteArray; // RGB palette
35 | private var usedEntry:Array = new Array; // active palette entries
36 | private var palSize:int = 7; // color table size (bits-1)
37 | private var dispose:int = -1; // disposal code (-1 = use default)
38 | private var closeStream:Boolean = false; // close stream when finished
39 | private var firstFrame:Boolean = true;
40 | private var sizeSet:Boolean = false; // if false, get size from first frame
41 | private var sample:int = 10; // default sample interval for quantizer
42 |
43 | /**
44 | * Sets the delay time between each frame, or changes it for subsequent frames
45 | * (applies to last frame added)
46 | * int delay time in milliseconds
47 | * @param ms
48 | */
49 |
50 | public function setDelay(ms:int):void
51 | {
52 |
53 | delay = Math.round(ms / 10);
54 |
55 | }
56 |
57 | /**
58 | * Sets the GIF frame disposal code for the last added frame and any
59 | *
60 | * subsequent frames. Default is 0 if no transparent color has been set,
61 | * otherwise 2.
62 | * @param code
63 | * int disposal code.
64 | */
65 |
66 | public function setDispose(code:int):void
67 | {
68 |
69 | if (code >= 0) dispose = code;
70 |
71 | }
72 |
73 | /**
74 | * Sets the number of times the set of GIF frames should be played. Default is
75 | * 1; 0 means play indefinitely. Must be invoked before the first image is
76 | * added.
77 | *
78 | * @param iter
79 | * int number of iterations.
80 | * @return
81 | */
82 |
83 | public function setRepeat(iter:int):void
84 | {
85 |
86 | if (iter >= 0) repeat = iter;
87 |
88 | }
89 |
90 | /**
91 | * Sets the transparent color for the last added frame and any subsequent
92 | * frames. Since all colors are subject to modification in the quantization
93 | * process, the color in the final palette for each frame closest to the given
94 | * color becomes the transparent color for that frame. May be set to null to
95 | * indicate no transparent color.
96 | * @param
97 | * Color to be treated as transparent on display.
98 | */
99 |
100 | public function setTransparent(c:Number):void
101 | {
102 |
103 | transparent = c;
104 |
105 | }
106 |
107 | /**
108 | * The addFrame method takes an incoming BitmapData object to create each frames
109 | * @param
110 | * BitmapData object to be treated as a GIF's frame
111 | */
112 |
113 | public function addFrame(im:BitmapData):Boolean
114 | {
115 |
116 | if ((im == null) || !started || out == null)
117 |
118 | {
119 | throw new Error ("Please call start method before calling addFrame");
120 | return false;
121 | }
122 |
123 | var ok:Boolean = true;
124 |
125 | try {
126 |
127 | image = new Bitmap ( im );
128 | if (!sizeSet) setSize(image.width, image.height);
129 | getImagePixels(); // convert to correct format if necessary
130 | analyzePixels(); // build color table & map pixels
131 |
132 | if (firstFrame)
133 | {
134 | writeLSD(); // logical screen descriptior
135 | writePalette(); // global color table
136 | if (repeat >= 0)
137 | {
138 | // use NS app extension to indicate reps
139 | writeNetscapeExt();
140 | }
141 | }
142 |
143 | writeGraphicCtrlExt(); // write graphic control extension
144 | writeImageDesc(); // image descriptor
145 | if (!firstFrame) writePalette(); // local color table
146 | writePixels(); // encode and write pixel data
147 | firstFrame = false;
148 | } catch (e:Error) {
149 | ok = false;
150 | }
151 |
152 | return ok;
153 |
154 | }
155 |
156 | /**
157 | * Adds final trailer to the GIF stream, if you don't call the finish method
158 | * the GIF stream will not be valid.
159 | */
160 |
161 | public function finish():Boolean
162 | {
163 | if (!started) return false;
164 | var ok:Boolean = true;
165 | started = false;
166 | try {
167 | out.writeByte(0x3b); // gif trailer
168 | } catch (e:Error) {
169 | ok = false;
170 | }
171 |
172 | return ok;
173 |
174 | }
175 |
176 | /**
177 | * Resets some members so that a new stream can be started.
178 | * This method is actually called by the start method
179 | */
180 |
181 | private function reset ( ):void
182 | {
183 |
184 | // reset for subsequent use
185 | transIndex = 0;
186 | image = null;
187 | pixels = null;
188 | indexedPixels = null;
189 | colorTab = null;
190 | closeStream = false;
191 | firstFrame = true;
192 |
193 | }
194 |
195 | /**
196 | * * Sets frame rate in frames per second. Equivalent to
197 | * setDelay(1000/fps)
.
198 | * @param fps
199 | * float frame rate (frames per second)
200 | */
201 |
202 | public function setFrameRate(fps:Number):void
203 | {
204 |
205 | if (fps != 0xf) delay = Math.round(100/fps);
206 |
207 | }
208 |
209 | /**
210 | * Sets quality of color quantization (conversion of images to the maximum 256
211 | * colors allowed by the GIF specification). Lower values (minimum = 1)
212 | * produce better colors, but slow processing significantly. 10 is the
213 | * default, and produces good color mapping at reasonable speeds. Values
214 | * greater than 20 do not yield significant improvements in speed.
215 | * @param quality
216 | * int greater than 0.
217 | * @return
218 | */
219 |
220 | public function setQuality(quality:int):void
221 | {
222 |
223 | if (quality < 1) quality = 1;
224 | sample = quality;
225 |
226 | }
227 |
228 | /**
229 | * Sets the GIF frame size. The default size is the size of the first frame
230 | * added if this method is not invoked.
231 | * @param w
232 | * int frame width.
233 | * @param h
234 | * int frame width.
235 | */
236 |
237 | private function setSize(w:int, h:int):void
238 | {
239 |
240 | if (started && !firstFrame) return;
241 | width = w;
242 | height = h;
243 | if (width < 1)width = 320;
244 | if (height < 1)height = 240;
245 | sizeSet = true
246 |
247 | }
248 |
249 | /**
250 | * Initiates GIF file creation on the given stream.
251 | * @param os
252 | * OutputStream on which GIF images are written.
253 | * @return false if initial write failed.
254 | *
255 | */
256 |
257 | public function start():Boolean
258 | {
259 |
260 | reset();
261 | var ok:Boolean = true;
262 | closeStream = false;
263 | out = new ByteArray;
264 | try {
265 | out.writeUTFBytes("GIF89a"); // header
266 | } catch (e:Error) {
267 | ok = false;
268 | }
269 |
270 | return started = ok;
271 |
272 | }
273 |
274 | /**
275 | * Analyzes image colors and creates color map.
276 | */
277 |
278 | private function analyzePixels():void
279 | {
280 |
281 | var len:int = pixels.length;
282 | var nPix:int = len / 3;
283 | indexedPixels = new ByteArray;
284 | var nq:NeuQuant = new NeuQuant(pixels, len, sample);
285 | // initialize quantizer
286 | colorTab = nq.process(); // create reduced palette
287 | // map image pixels to new palette
288 | var k:int = 0;
289 | for (var j:int = 0; j < nPix; j++) {
290 | var index:int = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
291 | usedEntry[index] = true;
292 | indexedPixels[j] = index;
293 | }
294 | pixels = null;
295 | colorDepth = 8;
296 | palSize = 7;
297 | // get closest match to transparent color if specified
298 | if (transparent != null) {
299 | transIndex = findClosest(transparent);
300 | }
301 | }
302 |
303 | /**
304 | * Returns index of palette color closest to c
305 | *
306 | */
307 |
308 | private function findClosest(c:Number):int
309 | {
310 |
311 | if (colorTab == null) return -1;
312 | var r:int = (c & 0xFF0000) >> 16;
313 | var g:int = (c & 0x00FF00) >> 8;
314 | var b:int = (c & 0x0000FF);
315 | var minpos:int = 0;
316 | var dmin:int = 256 * 256 * 256;
317 | var len:int = colorTab.length;
318 |
319 | for (var i:int = 0; i < len;) {
320 | var dr:int = r - (colorTab[i++] & 0xff);
321 | var dg:int = g - (colorTab[i++] & 0xff);
322 | var db:int = b - (colorTab[i] & 0xff);
323 | var d:int = dr * dr + dg * dg + db * db;
324 | var index:int = i / 3;
325 | if (usedEntry[index] && (d < dmin)) {
326 | dmin = d;
327 | minpos = index;
328 | }
329 | i++;
330 | }
331 | return minpos;
332 |
333 | }
334 |
335 | /**
336 | * Extracts image pixels into byte array "pixels
337 | */
338 |
339 | private function getImagePixels():void
340 | {
341 |
342 | var w:int = image.width;
343 | var h:int = image.height;
344 | pixels = new ByteArray;
345 |
346 | var count:int = 0;
347 |
348 | for ( var i:int = 0; i < h; i++ )
349 | {
350 |
351 | for (var j:int = 0; j < w; j++ )
352 | {
353 |
354 | var pixel:Number = image.bitmapData.getPixel( j, i );
355 |
356 | pixels[count] = (pixel & 0xFF0000) >> 16;
357 | count++;
358 | pixels[count] = (pixel & 0x00FF00) >> 8;
359 | count++;
360 | pixels[count] = (pixel & 0x0000FF);
361 | count++;
362 |
363 | }
364 |
365 | }
366 |
367 | }
368 |
369 | /**
370 | * Writes Graphic Control Extension
371 | */
372 |
373 | private function writeGraphicCtrlExt():void
374 | {
375 |
376 | out.writeByte(0x21); // extension introducer
377 | out.writeByte(0xf9); // GCE label
378 | out.writeByte(4); // data block size
379 | var transp:int
380 | var disp:int;
381 | if (transparent == null) {
382 | transp = 0;
383 | disp = 0; // dispose = no action
384 | } else {
385 | transp = 1;
386 | disp = 2; // force clear if using transparent color
387 | }
388 | if (dispose >= 0) {
389 | disp = dispose & 7; // user override
390 | }
391 | disp <<= 2;
392 | // packed fields
393 | out.writeByte(0 | // 1:3 reserved
394 | disp | // 4:6 disposal
395 | 0 | // 7 user input - 0 = none
396 | transp); // 8 transparency flag
397 |
398 | WriteShort(delay); // delay x 1/100 sec
399 | out.writeByte(transIndex); // transparent color index
400 | out.writeByte(0); // block terminator
401 |
402 | }
403 |
404 | /**
405 | * Writes Image Descriptor
406 | */
407 |
408 | private function writeImageDesc():void
409 | {
410 |
411 | out.writeByte(0x2c); // image separator
412 | WriteShort(0); // image position x,y = 0,0
413 | WriteShort(0);
414 | WriteShort(width); // image size
415 | WriteShort(height);
416 |
417 | // packed fields
418 | if (firstFrame) {
419 | // no LCT - GCT is used for first (or only) frame
420 | out.writeByte(0);
421 | } else {
422 | // specify normal LCT
423 | out.writeByte(0x80 | // 1 local color table 1=yes
424 | 0 | // 2 interlace - 0=no
425 | 0 | // 3 sorted - 0=no
426 | 0 | // 4-5 reserved
427 | palSize); // 6-8 size of color table
428 | }
429 | }
430 |
431 | /**
432 | * Writes Logical Screen Descriptor
433 | */
434 |
435 | private function writeLSD():void
436 | {
437 |
438 | // logical screen size
439 | WriteShort(width);
440 | WriteShort(height);
441 | // packed fields
442 | out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used)
443 | 0x70 | // 2-4 : color resolution = 7
444 | 0x00 | // 5 : gct sort flag = 0
445 | palSize)); // 6-8 : gct size
446 |
447 | out.writeByte(0); // background color index
448 | out.writeByte(0); // pixel aspect ratio - assume 1:1
449 |
450 | }
451 |
452 | /**
453 | * Writes Netscape application extension to define repeat count.
454 | */
455 |
456 | private function writeNetscapeExt():void
457 | {
458 |
459 | out.writeByte(0x21); // extension introducer
460 | out.writeByte(0xff); // app extension label
461 | out.writeByte(11); // block size
462 | out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
463 | out.writeByte(3); // sub-block size
464 | out.writeByte(1); // loop sub-block id
465 | WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
466 | out.writeByte(0); // block terminator
467 |
468 | }
469 |
470 | /**
471 | * Writes color table
472 | */
473 |
474 | private function writePalette():void
475 | {
476 |
477 | out.writeBytes(colorTab, 0, colorTab.length);
478 | var n:int = (3 * 256) - colorTab.length;
479 | for (var i:int = 0; i < n; i++) out.writeByte(0);
480 |
481 | }
482 |
483 | private function WriteShort (pValue:int):void
484 | {
485 |
486 | out.writeByte( pValue & 0xFF );
487 | out.writeByte( (pValue >> 8) & 0xFF);
488 |
489 | }
490 |
491 | /**
492 | * Encodes and writes pixel data
493 | */
494 |
495 | private function writePixels():void
496 | {
497 |
498 | var myencoder:LZWEncoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
499 | myencoder.encode(out);
500 |
501 | }
502 |
503 | /**
504 | * retrieves the GIF stream
505 | */
506 | public function get stream ( ):ByteArray
507 | {
508 |
509 | return out;
510 |
511 | }
512 |
513 | }
514 | }
--------------------------------------------------------------------------------
/Actionscript/LZWEncoder.as:
--------------------------------------------------------------------------------
1 | /**
2 | * This class handles LZW encoding
3 | * Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
5 | * @author Thibault Imbert (AS3 version - bytearray.org)
6 | * @version 0.1 AS3 implementation
7 | */
8 |
9 | package org.bytearray.gif.encoder
10 | {
11 | import flash.utils.ByteArray;
12 |
13 | public class LZWEncoder
14 | {
15 | private static var EOF:int = -1;
16 | private var imgW:int;
17 | private var imgH:int
18 | private var pixAry:ByteArray;
19 | private var initCodeSize:int;
20 | private var remaining:int;
21 | private var curPixel:int;
22 |
23 | // GIFCOMPR.C - GIF Image compression routines
24 | // Lempel-Ziv compression based on 'compress'. GIF modifications by
25 | // David Rowley (mgardi@watdcsu.waterloo.edu)
26 | // General DEFINEs
27 |
28 | private static var BITS:int = 12;
29 | private static var HSIZE:int = 5003; // 80% occupancy
30 |
31 | // GIF Image compression - modified 'compress'
32 | // Based on: compress.c - File compression ala IEEE Computer, June 1984.
33 | // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
34 | // Jim McKie (decvax!mcvax!jim)
35 | // Steve Davies (decvax!vax135!petsd!peora!srd)
36 | // Ken Turkowski (decvax!decwrl!turtlevax!ken)
37 | // James A. Woods (decvax!ihnp4!ames!jaw)
38 | // Joe Orost (decvax!vax135!petsd!joe)
39 |
40 | private var n_bits:int // number of bits/code
41 | private var maxbits:int = BITS; // user settable max # bits/code
42 | private var maxcode:int // maximum code, given n_bits
43 | private var maxmaxcode:int = 1 << BITS; // should NEVER generate this code
44 | private var htab:Array = new Array;
45 | private var codetab:Array = new Array;
46 | private var hsize:int = HSIZE; // for dynamic table sizing
47 | private var free_ent:int = 0; // first unused entry
48 |
49 | // block compression parameters -- after all codes are used up,
50 | // and compression rate changes, start over.
51 |
52 | private var clear_flg:Boolean = false;
53 |
54 | // Algorithm: use open addressing double hashing (no chaining) on the
55 | // prefix code / next character combination. We do a variant of Knuth's
56 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
57 | // secondary probe. Here, the modular division first probe is gives way
58 | // to a faster exclusive-or manipulation. Also do block compression with
59 | // an adaptive reset, whereby the code table is cleared when the compression
60 | // ratio decreases, but after the table fills. The variable-length output
61 | // codes are re-sized at this point, and a special CLEAR code is generated
62 | // for the decompressor. Late addition: construct the table according to
63 | // file size for noticeable speed improvement on small files. Please direct
64 | // questions about this implementation to ames!jaw.
65 |
66 | private var g_init_bits:int;
67 | private var ClearCode:int;
68 | private var EOFCode:int;
69 |
70 | // output
71 | // Output the given code.
72 | // Inputs:
73 | // code: A n_bits-bit integer. If == -1, then EOF. This assumes
74 | // that n_bits =< wordsize - 1.
75 | // Outputs:
76 | // Outputs code to the file.
77 | // Assumptions:
78 | // Chars are 8 bits long.
79 | // Algorithm:
80 | // Maintain a BITS character long buffer (so that 8 codes will
81 | // fit in it exactly). Use the VAX insv instruction to insert each
82 | // code in turn. When the buffer fills up empty it and start over.
83 |
84 | private var cur_accum:int = 0;
85 | private var cur_bits:int = 0;
86 | private var masks:Array = [ 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF ];
87 |
88 | // Number of characters so far in this 'packet'
89 | private var a_count:int;
90 |
91 | // Define the storage for the packet accumulator
92 | private var accum:ByteArray = new ByteArray;
93 |
94 | public function LZWEncoder (width:int, height:int, pixels:ByteArray, color_depth:int)
95 | {
96 |
97 | imgW = width;
98 | imgH = height;
99 | pixAry = pixels;
100 | initCodeSize = Math.max(2, color_depth);
101 |
102 | }
103 |
104 | // Add a character to the end of the current packet, and if it is 254
105 | // characters, flush the packet to disk.
106 | private function char_out(c:Number, outs:ByteArray):void
107 | {
108 |
109 | accum[a_count++] = c;
110 | if (a_count >= 254)flush_char(outs);
111 |
112 | }
113 |
114 | // Clear out the hash table
115 | // table clear for block compress
116 |
117 | private function cl_block(outs:ByteArray):void
118 | {
119 |
120 | cl_hash(hsize);
121 | free_ent = ClearCode + 2;
122 | clear_flg = true;
123 | output(ClearCode, outs);
124 |
125 | }
126 |
127 | // reset code table
128 | private function cl_hash(hsize:int):void
129 | {
130 |
131 | for (var i:int = 0; i < hsize; ++i) htab[i] = -1;
132 |
133 | }
134 |
135 | public function compress(init_bits:int, outs:ByteArray):void
136 |
137 | {
138 |
139 | var fcode:int;
140 | var i:int /* = 0 */;
141 | var c:int;
142 | var ent:int;
143 | var disp:int;
144 | var hsize_reg:int;
145 | var hshift:int;
146 |
147 | // Set up the globals: g_init_bits - initial number of bits
148 | g_init_bits = init_bits;
149 |
150 | // Set up the necessary values
151 | clear_flg = false;
152 | n_bits = g_init_bits;
153 | maxcode = MAXCODE(n_bits);
154 |
155 | ClearCode = 1 << (init_bits - 1);
156 | EOFCode = ClearCode + 1;
157 | free_ent = ClearCode + 2;
158 |
159 | a_count = 0; // clear packet
160 |
161 | ent = nextPixel();
162 |
163 | hshift = 0;
164 | for (fcode = hsize; fcode < 65536; fcode *= 2)
165 | ++hshift;
166 | hshift = 8 - hshift; // set hash code range bound
167 |
168 | hsize_reg = hsize;
169 | cl_hash(hsize_reg); // clear hash table
170 |
171 | output(ClearCode, outs);
172 |
173 | outer_loop: while ((c = nextPixel()) != EOF)
174 |
175 | {
176 |
177 | fcode = (c << maxbits) + ent;
178 | i = (c << hshift) ^ ent; // xor hashing
179 |
180 | if (htab[i] == fcode)
181 | {
182 | ent = codetab[i];
183 | continue;
184 | } else if (htab[i] >= 0) // non-empty slot
185 | {
186 | disp = hsize_reg - i; // secondary hash (after G. Knott)
187 | if (i == 0)
188 | disp = 1;
189 | do
190 | {
191 |
192 | if ((i -= disp) < 0) i += hsize_reg;
193 |
194 | if (htab[i] == fcode)
195 | {
196 | ent = codetab[i];
197 | continue outer_loop;
198 | }
199 | } while (htab[i] >= 0);
200 | }
201 |
202 | output(ent, outs);
203 | ent = c;
204 | if (free_ent < maxmaxcode)
205 | {
206 | codetab[i] = free_ent++; // code -> hashtable
207 | htab[i] = fcode;
208 | } else cl_block(outs);
209 | }
210 |
211 | // Put out the final code.
212 | output(ent, outs);
213 | output(EOFCode, outs);
214 |
215 | }
216 |
217 | // ----------------------------------------------------------------------------
218 | public function encode(os:ByteArray):void
219 | {
220 |
221 | os.writeByte(initCodeSize); // write "initial code size" byte
222 | remaining = imgW * imgH; // reset navigation variables
223 | curPixel = 0;
224 | compress(initCodeSize + 1, os); // compress and write the pixel data
225 | os.writeByte(0); // write block terminator
226 |
227 | }
228 |
229 | // Flush the packet to disk, and reset the accumulator
230 | private function flush_char(outs:ByteArray):void
231 | {
232 |
233 | if (a_count > 0)
234 | {
235 | outs.writeByte(a_count);
236 | outs.writeBytes(accum, 0, a_count);
237 | a_count = 0;
238 | }
239 |
240 | }
241 |
242 | private function MAXCODE(n_bits:int):int
243 | {
244 |
245 | return (1 << n_bits) - 1;
246 |
247 | }
248 |
249 | // ----------------------------------------------------------------------------
250 | // Return the next pixel from the image
251 | // ----------------------------------------------------------------------------
252 |
253 | private function nextPixel():int
254 | {
255 |
256 | if (remaining == 0) return EOF;
257 |
258 | --remaining;
259 |
260 | var pix:Number = pixAry[curPixel++];
261 |
262 | return pix & 0xff;
263 |
264 | }
265 |
266 | private function output(code:int, outs:ByteArray):void
267 |
268 | {
269 |
270 | cur_accum &= masks[cur_bits];
271 |
272 | if (cur_bits > 0) cur_accum |= (code << cur_bits);
273 | else cur_accum = code;
274 |
275 | cur_bits += n_bits;
276 |
277 | while (cur_bits >= 8)
278 |
279 | {
280 |
281 | char_out((cur_accum & 0xff), outs);
282 | cur_accum >>= 8;
283 | cur_bits -= 8;
284 |
285 | }
286 |
287 | // If the next entry is going to be too big for the code size,
288 | // then increase it, if possible.
289 |
290 | if (free_ent > maxcode || clear_flg)
291 | {
292 |
293 | if (clear_flg)
294 | {
295 |
296 | maxcode = MAXCODE(n_bits = g_init_bits);
297 | clear_flg = false;
298 |
299 | } else
300 | {
301 |
302 | ++n_bits;
303 |
304 | if (n_bits == maxbits) maxcode = maxmaxcode;
305 |
306 | else maxcode = MAXCODE(n_bits);
307 |
308 | }
309 |
310 | }
311 |
312 | if (code == EOFCode)
313 | {
314 |
315 | // At EOF, write the rest of the buffer.
316 | while (cur_bits > 0)
317 | {
318 |
319 | char_out((cur_accum & 0xff), outs);
320 | cur_accum >>= 8;
321 | cur_bits -= 8;
322 | }
323 |
324 |
325 | flush_char(outs);
326 |
327 | }
328 |
329 | }
330 |
331 | }
332 |
333 | }
--------------------------------------------------------------------------------
/Actionscript/NeuQuant.as:
--------------------------------------------------------------------------------
1 | /*
2 | * NeuQuant Neural-Net Quantization Algorithm
3 | * ------------------------------------------
4 | *
5 | * Copyright (c) 1994 Anthony Dekker
6 | *
7 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
8 | * "Kohonen neural networks for optimal colour quantization" in "Network:
9 | * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
10 | * the algorithm.
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 in
15 | * this software and documentation files (the "Software"), including without
16 | * limitation the rights to use, copy, modify, merge, publish, distribute,
17 | * sublicense, and/or sell copies of the Software, and to permit persons who
18 | * receive copies from any such party to do so, with the only requirement being
19 | * that this copyright notice remain intact.
20 | */
21 |
22 | /*
23 | * This class handles Neural-Net quantization algorithm
24 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
25 | * @author Thibault Imbert (AS3 version - bytearray.org)
26 | * @version 0.1 AS3 implementation
27 | */
28 |
29 | package org.bytearray.gif.encoder
30 | {
31 | import flash.utils.ByteArray;
32 |
33 | public class NeuQuant
34 | {
35 | private static var netsize:int = 256; /* number of colours used */
36 |
37 | /* four primes near 500 - assume no image has a length so large */
38 | /* that it is divisible by all four primes */
39 |
40 | private static var prime1:int = 499;
41 | private static var prime2:int = 491;
42 | private static var prime3:int = 487;
43 | private static var prime4:int = 503;
44 | private static var minpicturebytes:int = (3 * prime4);
45 |
46 | /* minimum size for input image */
47 | /*
48 | * Program Skeleton ---------------- [select samplefac in range 1..30] [read
49 | * image from input file] pic = (unsigned char*) malloc(3*width*height);
50 | * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
51 | * image header, using writecolourmap(f)] inxbuild(); write output image using
52 | * inxsearch(b,g,r)
53 | */
54 |
55 | /*
56 | * Network Definitions -------------------
57 | */
58 |
59 | private static var maxnetpos:int = (netsize - 1);
60 | private static var netbiasshift:int = 4; /* bias for colour values */
61 | private static var ncycles:int = 100; /* no. of learning cycles */
62 |
63 | /* defs for freq and bias */
64 | private static var intbiasshift:int = 16; /* bias for fractions */
65 | private static var intbias:int = (1 << intbiasshift);
66 | private static var gammashift:int = 10; /* gamma = 1024 */
67 | private static var gamma:int = (1 << gammashift);
68 | private static var betashift:int = 10;
69 | private static var beta:int = (intbias >> betashift); /* beta = 1/1024 */
70 | private static var betagamma:int = (intbias << (gammashift - betashift));
71 |
72 | /* defs for decreasing radius factor */
73 | private static var initrad:int = (netsize >> 3); /*
74 | * for 256 cols, radius
75 | * starts
76 | */
77 |
78 | private static var radiusbiasshift:int = 6; /* at 32.0 biased by 6 bits */
79 | private static var radiusbias:int = (1 << radiusbiasshift);
80 | private static var initradius:int = (initrad * radiusbias); /*
81 | * and
82 | * decreases
83 | * by a
84 | */
85 |
86 | private static var radiusdec:int = 30; /* factor of 1/30 each cycle */
87 |
88 | /* defs for decreasing alpha factor */
89 | private static var alphabiasshift:int = 10; /* alpha starts at 1.0 */
90 | private static var initalpha:int = (1 << alphabiasshift);
91 | private var alphadec:int /* biased by 10 bits */
92 |
93 | /* radbias and alpharadbias used for radpower calculation */
94 | private static var radbiasshift:int = 8;
95 | private static var radbias:int = (1 << radbiasshift);
96 | private static var alpharadbshift:int = (alphabiasshift + radbiasshift);
97 |
98 | private static var alpharadbias:int = (1 << alpharadbshift);
99 |
100 | /*
101 | * Types and Global Variables --------------------------
102 | */
103 |
104 | private var thepicture:ByteArray/* the input image itself */
105 | private var lengthcount:int; /* lengthcount = H*W*3 */
106 | private var samplefac:int; /* sampling factor 1..30 */
107 |
108 | // typedef int pixel[4]; /* BGRc */
109 | private var network:Array; /* the network itself - [netsize][4] */
110 | protected var netindex:Array = new Array();
111 |
112 | /* for network lookup - really 256 */
113 | private var bias:Array = new Array();
114 |
115 | /* bias and freq arrays for learning */
116 | private var freq:Array = new Array();
117 | private var radpower:Array = new Array();
118 |
119 | public function NeuQuant(thepic:ByteArray, len:int, sample:int)
120 | {
121 |
122 | var i:int;
123 | var p:Array;
124 |
125 | thepicture = thepic;
126 | lengthcount = len;
127 | samplefac = sample;
128 |
129 | network = new Array(netsize);
130 |
131 | for (i = 0; i < netsize; i++)
132 | {
133 |
134 | network[i] = new Array(4);
135 | p = network[i];
136 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
137 | freq[i] = intbias / netsize; /* 1/netsize */
138 | bias[i] = 0;
139 | }
140 |
141 | }
142 |
143 | private function colorMap():ByteArray
144 | {
145 |
146 | var map:ByteArray = new ByteArray;
147 | var index:Array = new Array(netsize);
148 | for (var i:int = 0; i < netsize; i++)
149 | index[network[i][3]] = i;
150 | var k:int = 0;
151 | for (var l:int = 0; l < netsize; l++) {
152 | var j:int = index[l];
153 | map[k++] = (network[j][0]);
154 | map[k++] = (network[j][1]);
155 | map[k++] = (network[j][2]);
156 | }
157 | return map;
158 |
159 | }
160 |
161 | /*
162 | * Insertion sort of network and building of netindex[0..255] (to do after
163 | * unbias)
164 | * -------------------------------------------------------------------------------
165 | */
166 |
167 | private function inxbuild():void
168 | {
169 |
170 | var i:int;
171 | var j:int;
172 | var smallpos:int;
173 | var smallval:int;
174 | var p:Array;
175 | var q:Array;
176 | var previouscol:int
177 | var startpos:int
178 |
179 | previouscol = 0;
180 | startpos = 0;
181 | for (i = 0; i < netsize; i++)
182 | {
183 |
184 | p = network[i];
185 | smallpos = i;
186 | smallval = p[1]; /* index on g */
187 | /* find smallest in i..netsize-1 */
188 | for (j = i + 1; j < netsize; j++)
189 | {
190 | q = network[j];
191 | if (q[1] < smallval)
192 | { /* index on g */
193 |
194 | smallpos = j;
195 | smallval = q[1]; /* index on g */
196 | }
197 | }
198 |
199 | q = network[smallpos];
200 | /* swap p (i) and q (smallpos) entries */
201 |
202 | if (i != smallpos)
203 | {
204 |
205 | j = q[0];
206 | q[0] = p[0];
207 | p[0] = j;
208 | j = q[1];
209 | q[1] = p[1];
210 | p[1] = j;
211 | j = q[2];
212 | q[2] = p[2];
213 | p[2] = j;
214 | j = q[3];
215 | q[3] = p[3];
216 | p[3] = j;
217 |
218 | }
219 |
220 | /* smallval entry is now in position i */
221 |
222 | if (smallval != previouscol)
223 |
224 | {
225 |
226 | netindex[previouscol] = (startpos + i) >> 1;
227 |
228 | for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
229 |
230 | previouscol = smallval;
231 | startpos = i;
232 |
233 | }
234 |
235 | }
236 |
237 | netindex[previouscol] = (startpos + maxnetpos) >> 1;
238 | for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */
239 |
240 | }
241 |
242 | /*
243 | * Main Learning Loop ------------------
244 | */
245 |
246 | private function learn():void
247 |
248 | {
249 |
250 | var i:int;
251 | var j:int;
252 | var b:int;
253 | var g:int
254 | var r:int;
255 | var radius:int;
256 | var rad:int;
257 | var alpha:int;
258 | var step:int;
259 | var delta:int;
260 | var samplepixels:int;
261 | var p:ByteArray;
262 | var pix:int;
263 | var lim:int;
264 |
265 | if (lengthcount < minpicturebytes) samplefac = 1;
266 |
267 | alphadec = 30 + ((samplefac - 1) / 3);
268 | p = thepicture;
269 | pix = 0;
270 | lim = lengthcount;
271 | samplepixels = lengthcount / (3 * samplefac);
272 | delta = samplepixels / ncycles;
273 | alpha = initalpha;
274 | radius = initradius;
275 |
276 | rad = radius >> radiusbiasshift;
277 | if (rad <= 1) rad = 0;
278 |
279 | for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
280 |
281 |
282 | if (lengthcount < minpicturebytes) step = 3;
283 |
284 | else if ((lengthcount % prime1) != 0) step = 3 * prime1;
285 |
286 | else
287 |
288 | {
289 |
290 | if ((lengthcount % prime2) != 0) step = 3 * prime2;
291 |
292 | else
293 |
294 | {
295 |
296 | if ((lengthcount % prime3) != 0) step = 3 * prime3;
297 |
298 | else step = 3 * prime4;
299 |
300 | }
301 |
302 | }
303 |
304 | i = 0;
305 |
306 | while (i < samplepixels)
307 |
308 | {
309 |
310 | b = (p[pix + 0] & 0xff) << netbiasshift;
311 | g = (p[pix + 1] & 0xff) << netbiasshift;
312 | r = (p[pix + 2] & 0xff) << netbiasshift;
313 | j = contest(b, g, r);
314 |
315 | altersingle(alpha, j, b, g, r);
316 |
317 | if (rad != 0) alterneigh(rad, j, b, g, r); /* alter neighbours */
318 |
319 | pix += step;
320 |
321 | if (pix >= lim) pix -= lengthcount;
322 |
323 | i++;
324 |
325 | if (delta == 0) delta = 1;
326 |
327 | if (i % delta == 0)
328 |
329 | {
330 |
331 | alpha -= alpha / alphadec;
332 | radius -= radius / radiusdec;
333 | rad = radius >> radiusbiasshift;
334 |
335 | if (rad <= 1) rad = 0;
336 |
337 | for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
338 |
339 | }
340 |
341 | }
342 |
343 | }
344 |
345 | /*
346 | ** Search for BGR values 0..255 (after net is unbiased) and return colour
347 | * index
348 | * ----------------------------------------------------------------------------
349 | */
350 |
351 | public function map(b:int, g:int, r:int):int
352 |
353 | {
354 |
355 | var i:int;
356 | var j:int;
357 | var dist:int
358 | var a:int;
359 | var bestd:int;
360 | var p:Array;
361 | var best:int;
362 |
363 | bestd = 1000; /* biggest possible dist is 256*3 */
364 | best = -1;
365 | i = netindex[g]; /* index on g */
366 | j = i - 1; /* start at netindex[g] and work outwards */
367 |
368 | while ((i < netsize) || (j >= 0))
369 |
370 | {
371 |
372 | if (i < netsize)
373 |
374 | {
375 |
376 | p = network[i];
377 |
378 | dist = p[1] - g; /* inx key */
379 |
380 | if (dist >= bestd) i = netsize; /* stop iter */
381 |
382 | else
383 |
384 | {
385 |
386 | i++;
387 |
388 | if (dist < 0) dist = -dist;
389 |
390 | a = p[0] - b;
391 |
392 | if (a < 0) a = -a;
393 |
394 | dist += a;
395 |
396 | if (dist < bestd)
397 |
398 | {
399 |
400 | a = p[2] - r;
401 |
402 | if (a < 0) a = -a;
403 |
404 | dist += a;
405 |
406 | if (dist < bestd)
407 |
408 | {
409 |
410 | bestd = dist;
411 | best = p[3];
412 |
413 | }
414 |
415 | }
416 |
417 | }
418 |
419 | }
420 |
421 | if (j >= 0)
422 | {
423 |
424 | p = network[j];
425 |
426 | dist = g - p[1]; /* inx key - reverse dif */
427 |
428 | if (dist >= bestd) j = -1; /* stop iter */
429 |
430 | else
431 | {
432 |
433 | j--;
434 | if (dist < 0) dist = -dist;
435 | a = p[0] - b;
436 | if (a < 0) a = -a;
437 | dist += a;
438 |
439 | if (dist < bestd)
440 |
441 | {
442 |
443 | a = p[2] - r;
444 | if (a < 0)a = -a;
445 | dist += a;
446 | if (dist < bestd)
447 | {
448 | bestd = dist;
449 | best = p[3];
450 | }
451 |
452 | }
453 |
454 | }
455 |
456 | }
457 |
458 | }
459 |
460 | return (best);
461 |
462 | }
463 |
464 | public function process():ByteArray
465 | {
466 |
467 | learn();
468 | unbiasnet();
469 | inxbuild();
470 | return colorMap();
471 |
472 | }
473 |
474 | /*
475 | * Unbias network to give byte values 0..255 and record position i to prepare
476 | * for sort
477 | * -----------------------------------------------------------------------------------
478 | */
479 |
480 | private function unbiasnet():void
481 |
482 | {
483 |
484 | var i:int;
485 | var j:int;
486 |
487 | for (i = 0; i < netsize; i++)
488 | {
489 | network[i][0] >>= netbiasshift;
490 | network[i][1] >>= netbiasshift;
491 | network[i][2] >>= netbiasshift;
492 | network[i][3] = i; /* record colour no */
493 | }
494 |
495 | }
496 |
497 | /*
498 | * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
499 | * radpower[|i-j|]
500 | * ---------------------------------------------------------------------------------
501 | */
502 |
503 | private function alterneigh(rad:int, i:int, b:int, g:int, r:int):void
504 |
505 | {
506 |
507 | var j:int;
508 | var k:int;
509 | var lo:int;
510 | var hi:int;
511 | var a:int;
512 | var m:int;
513 |
514 | var p:Array;
515 |
516 | lo = i - rad;
517 | if (lo < -1) lo = -1;
518 |
519 | hi = i + rad;
520 |
521 | if (hi > netsize) hi = netsize;
522 |
523 | j = i + 1;
524 | k = i - 1;
525 | m = 1;
526 |
527 | while ((j < hi) || (k > lo))
528 |
529 | {
530 |
531 | a = radpower[m++];
532 |
533 | if (j < hi)
534 |
535 | {
536 |
537 | p = network[j++];
538 |
539 | try {
540 |
541 | p[0] -= (a * (p[0] - b)) / alpharadbias;
542 | p[1] -= (a * (p[1] - g)) / alpharadbias;
543 | p[2] -= (a * (p[2] - r)) / alpharadbias;
544 |
545 | } catch (e:Error) {} // prevents 1.3 miscompilation
546 |
547 | }
548 |
549 | if (k > lo)
550 |
551 | {
552 |
553 | p = network[k--];
554 |
555 | try
556 | {
557 |
558 | p[0] -= (a * (p[0] - b)) / alpharadbias;
559 | p[1] -= (a * (p[1] - g)) / alpharadbias;
560 | p[2] -= (a * (p[2] - r)) / alpharadbias;
561 |
562 | } catch (e:Error) {}
563 |
564 | }
565 |
566 | }
567 |
568 | }
569 |
570 | /*
571 | * Move neuron i towards biased (b,g,r) by factor alpha
572 | * ----------------------------------------------------
573 | */
574 |
575 | private function altersingle(alpha:int, i:int, b:int, g:int, r:int):void
576 | {
577 |
578 | /* alter hit neuron */
579 | var n:Array = network[i];
580 | n[0] -= (alpha * (n[0] - b)) / initalpha;
581 | n[1] -= (alpha * (n[1] - g)) / initalpha;
582 | n[2] -= (alpha * (n[2] - r)) / initalpha;
583 |
584 | }
585 |
586 | /*
587 | * Search for biased BGR values ----------------------------
588 | */
589 |
590 | private function contest(b:int, g:int, r:int):int
591 | {
592 |
593 | /* finds closest neuron (min dist) and updates freq */
594 | /* finds best neuron (min dist-bias) and returns position */
595 | /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
596 | /* bias[i] = gamma*((1/netsize)-freq[i]) */
597 |
598 | var i:int;
599 | var dist:int;
600 | var a:int;
601 | var biasdist:int;
602 | var betafreq:int;
603 | var bestpos:int;
604 | var bestbiaspos:int;
605 | var bestd:int;
606 | var bestbiasd:int;
607 | var n:Array;
608 |
609 | bestd = ~(1 << 31);
610 | bestbiasd = bestd;
611 | bestpos = -1;
612 | bestbiaspos = bestpos;
613 |
614 | for (i = 0; i < netsize; i++)
615 |
616 | {
617 |
618 | n = network[i];
619 | dist = n[0] - b;
620 |
621 | if (dist < 0) dist = -dist;
622 |
623 | a = n[1] - g;
624 |
625 | if (a < 0) a = -a;
626 |
627 | dist += a;
628 |
629 | a = n[2] - r;
630 |
631 | if (a < 0) a = -a;
632 |
633 | dist += a;
634 |
635 | if (dist < bestd)
636 |
637 | {
638 |
639 | bestd = dist;
640 | bestpos = i;
641 |
642 | }
643 |
644 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
645 |
646 | if (biasdist < bestbiasd)
647 |
648 | {
649 |
650 | bestbiasd = biasdist;
651 | bestbiaspos = i;
652 |
653 | }
654 |
655 | betafreq = (freq[i] >> betashift);
656 | freq[i] -= betafreq;
657 | bias[i] += (betafreq << gammashift);
658 |
659 | }
660 |
661 | freq[bestpos] += beta;
662 | bias[bestpos] -= betagamma;
663 | return (bestbiaspos);
664 |
665 | }
666 |
667 | }
668 |
669 | }
--------------------------------------------------------------------------------
/Actionscript/README:
--------------------------------------------------------------------------------
1 | From: http://code.google.com/p/as3gif/downloads/detail?name=GIFPlayer%200.6.zip
2 |
3 |
4 | Convert.rb is my awesome script which converts AS to JS by getting rid of types and some other stuff
5 |
--------------------------------------------------------------------------------
/Actionscript/convert.rb:
--------------------------------------------------------------------------------
1 | ARGF.each_line do |line|
2 | line.gsub! /public function (\w+)/, 'var \1 = exports.\1 = function \1'
3 | line.gsub! /private function (\w+)/, 'var \1 = function \1'
4 | line.gsub! /public class (\w+)/, '\1 = function()'
5 | line.gsub! /private static /, '/*private_static*/ '
6 | line.gsub! /private /, '/*private*/ '
7 | line.gsub! /protected /, '/*protected*/ '
8 | line.gsub! /import/, '//import'
9 | line.gsub! /function get /, 'function '
10 | line.gsub! /\:([A-Za-z\*]+)/, '/*\1*/'
11 | puts line
12 | end
13 |
--------------------------------------------------------------------------------
/Demos/b64.js:
--------------------------------------------------------------------------------
1 | function encode64(input) {
2 | var output = "", i = 0, l = input.length,
3 | key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
4 | chr1, chr2, chr3, enc1, enc2, enc3, enc4;
5 | while (i < l) {
6 | chr1 = input.charCodeAt(i++);
7 | chr2 = input.charCodeAt(i++);
8 | chr3 = input.charCodeAt(i++);
9 | enc1 = chr1 >> 2;
10 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
11 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
12 | enc4 = chr3 & 63;
13 | if (isNaN(chr2)) enc3 = enc4 = 64;
14 | else if (isNaN(chr3)) enc4 = 64;
15 | output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);
16 | }
17 | return output;
18 | }
19 |
--------------------------------------------------------------------------------
/Demos/canvascycle/bitmap.js:
--------------------------------------------------------------------------------
1 | // 8-bit Bitmap for use in HTML5 Canvas
2 | // Copyright (c) 2010 Joseph Huckaby.
3 | // Released under the LGPL v3.0: http://www.opensource.org/licenses/lgpl-3.0.html
4 |
5 | Class.create( 'Bitmap', {
6 |
7 | width: 0,
8 | height: 0,
9 | pixels: null,
10 | palette: null,
11 | drawCount: 0,
12 | optPixels: null,
13 |
14 | __construct: function(img) {
15 | // class constructor
16 | this.width = img.width;
17 | this.height = img.height;
18 | this.palette = new Palette( img.colors, img.cycles );
19 | this.pixels = img.pixels;
20 | },
21 |
22 | optimize: function() {
23 | // prepare bitmap for optimized rendering (only refresh pixels that changed)
24 | var optColors = [];
25 | for (var idx = 0; idx < 256; idx++) optColors[idx] = 0;
26 |
27 | // mark animated colors in palette
28 | var cycles = this.palette.cycles;
29 | for (var idx = 0, len = cycles.length; idx < len; idx++) {
30 | var cycle = cycles[idx];
31 | if (cycle.rate) {
32 | // cycle is animated
33 | for (idy = cycle.low; idy <= cycle.high; idy++) {
34 | optColors[idy] = 1;
35 | }
36 | }
37 | }
38 |
39 | // create array of pixel offsets which are animated
40 | var optPixels = this.optPixels = [];
41 | var pixels = this.pixels;
42 | var j = 0;
43 | var i = 0;
44 | var x, y;
45 | var xmax = this.width, ymax = this.height;
46 |
47 | for (y = 0; y < ymax; y++) {
48 | for (x = 0; x < xmax; x++) {
49 | if (optColors[pixels[j]]) optPixels[i++] = j;
50 | j++;
51 | } // x loop
52 | } // y loop
53 | },
54 |
55 | render: function(imageData, optimize) {
56 | // render pixels into canvas imageData object
57 | var colors = this.palette.getRawTransformedColors();
58 | var data = imageData.data;
59 | var pixels = this.pixels;
60 |
61 | if (optimize && this.drawCount && this.optPixels) {
62 | // only redraw pixels that are part of animated cycles
63 | var optPixels = this.optPixels;
64 | var i, j, clr;
65 |
66 | for (var idx = 0, len = optPixels.length; idx < len; idx++) {
67 | j = optPixels[idx];
68 | clr = colors[ pixels[j] ];
69 | i = j * 4;
70 | data[i + 0] = clr[0]; // red
71 | data[i + 1] = clr[1]; // green
72 | data[i + 2] = clr[2]; // blue
73 | data[i + 3] = 255; // alpha
74 | }
75 | }
76 | else {
77 | // draw every single pixel
78 | var i = 0;
79 | var j = 0;
80 | var x, y, clr;
81 | var xmax = this.width, ymax = this.height;
82 |
83 | for (y = 0; y < ymax; y++) {
84 | for (x = 0; x < xmax; x++) {
85 | clr = colors[ pixels[j] ];
86 | data[i + 0] = clr[0]; // red
87 | data[i + 1] = clr[1]; // green
88 | data[i + 2] = clr[2]; // blue
89 | data[i + 3] = 255; // alpha
90 | i += 4;
91 | j++;
92 | }
93 | }
94 | }
95 |
96 | this.drawCount++;
97 | }
98 |
99 | } );
100 |
--------------------------------------------------------------------------------
/Demos/canvascycle/cookie.js:
--------------------------------------------------------------------------------
1 | /***
2 | * cookie.js
3 | * A simple cookie library supporting hash trees
4 | * Requires Joe Tools for merge_objects() and serialize().
5 | *
6 | * var tree = new CookieTree();
7 | * tree.set( "foo", "bar" );
8 | * tree.set( "complex", { hello: "there", array: [1,2,3] } );
9 | * tree.save();
10 | *
11 | * Copyright (c) 2007 Joseph Huckaby.
12 | * Released under the LGPL v3.0: http://www.opensource.org/licenses/lgpl-3.0.html
13 | */
14 |
15 | /* if (!window.merge_objects || !window.serialize)
16 | alert("ERROR: cookie.js requires tools.js."); */
17 |
18 | function CookieTree(args) {
19 | // class constructor
20 | if (args) {
21 | for (var key in args) this[key] = args[key];
22 | }
23 |
24 | if (!this.expires) {
25 | var now = new Date();
26 | now.setFullYear( now.getFullYear() + 10 ); // 10 years from now
27 | this.expires = now.toGMTString();
28 | }
29 |
30 | this.parse();
31 | };
32 |
33 | CookieTree.prototype.domain = location.hostname;
34 | CookieTree.prototype.path = location.pathname;
35 |
36 | CookieTree.prototype.parse = function() {
37 | // parse document.cookie into hash tree
38 | this.tree = {};
39 | var cookies = document.cookie.split(/\;\s*/);
40 | for (var idx = 0, len = cookies.length; idx < len; idx++) {
41 | var cookie_raw = cookies[idx];
42 | if (cookie_raw.match(/^CookieTree=(.+)$/)) {
43 | var cookie = null;
44 | var cookie_raw = unescape( RegExp.$1 );
45 | // Debug.trace("Cookie", "Parsing cookie: " + cookie_raw);
46 | try {
47 | eval( "cookie = " + cookie_raw + ";" );
48 | }
49 | catch (e) {
50 | // Debug.trace("Cookie", "Failed to parse cookie.");
51 | cookie = {};
52 | }
53 |
54 | this.tree = merge_objects( this.tree, cookie );
55 | idx = len;
56 | }
57 | }
58 | };
59 |
60 | CookieTree.prototype.get = function(key) {
61 | // get tree branch given value (top level)
62 | return this.tree[key];
63 | };
64 |
65 | CookieTree.prototype.set = function(key, value) {
66 | // set tree branch to given value (top level)
67 | this.tree[key] = value;
68 | };
69 |
70 | CookieTree.prototype.save = function() {
71 | // serialize tree and save back into document.cookie
72 | var cookie_raw = 'CookieTree=' + escape(serialize(this.tree));
73 |
74 | if (!this.path.match(/\/$/)) {
75 | this.path = this.path.replace(/\/[^\/]+$/, "") + '/';
76 | }
77 |
78 | cookie_raw += '; expires=' + this.expires;
79 | cookie_raw += '; domain=' + this.domain;
80 | cookie_raw += '; path=' + this.path;
81 |
82 | // Debug.trace("Cookie", "Saving cookie: " + cookie_raw);
83 |
84 | document.cookie = cookie_raw;
85 | };
86 |
87 | CookieTree.prototype.remove = function() {
88 | // remove cookie from document
89 | var cookie_raw = 'CookieTree={}';
90 |
91 | if (!this.path.match(/\/$/)) {
92 | this.path = this.path.replace(/\/[^\/]+$/, "") + '/';
93 | }
94 |
95 | var now = new Date();
96 | now.setFullYear( now.getFullYear() - 1 ); // last year
97 | cookie_raw += '; expires=' + now.toGMTString();
98 |
99 | cookie_raw += '; domain=' + this.domain;
100 | cookie_raw += '; path=' + this.path;
101 |
102 | document.cookie = cookie_raw;
103 | };
104 |
--------------------------------------------------------------------------------
/Demos/canvascycle/framecount.js:
--------------------------------------------------------------------------------
1 | // Basic Frame Counting Class
2 | // For displaying current and average frames per second
3 | // Author: Joseph Huckaby
4 | // Copyright (c) 2010 Joseph Huckaby
5 | // Usage: FrameCount.count() // for every frame
6 |
7 | var FrameCount = {
8 |
9 | current: 0,
10 | average: 0,
11 | frameCount: 0,
12 | lastSecond: 0,
13 | startTime: 0,
14 | totalFrames: 0,
15 | ie: !!navigator.userAgent.match(/MSIE/),
16 | visible: true,
17 |
18 | init: function() {
19 | // create floating widget
20 | if (this.visible) {
21 | var html = '
Waiting for frames...
';
22 |
23 | if (this.ie) {
24 | setTimeout( function() {
25 | document.body.insertAdjacentHTML('beforeEnd',
26 | '' + html + '
'
27 | );
28 | }, 1000 );
29 | }
30 | else {
31 | var div = document.createElement('DIV');
32 | div.style.position = 'fixed';
33 | div.style.zIndex = '9999';
34 | div.style.left = '0px';
35 | div.style.top = '0px';
36 | div.style.width = '100%';
37 | div.innerHTML = html;
38 | document.getElementsByTagName('body')[0].appendChild(div);
39 | }
40 | }
41 | },
42 |
43 | update: function() {
44 | // update display
45 | var div = document.getElementById('d_framecount');
46 | if (div) {
47 | var html = '';
48 |
49 | html += '';
50 | html += 'Current FPS: | ' + this.current + ' |
';
51 | html += 'Average FPS: | ' + this.average + ' |
';
52 | html += 'Total Frames: | ' + this.totalFrames + ' |
';
53 | html += '
';
54 |
55 | html += '
Reset';
56 |
57 | div.innerHTML = html;
58 | }
59 | },
60 |
61 | reset: function() {
62 | this.current = 0;
63 | this.average = 0;
64 | this.frameCount = 0;
65 | this.lastSecond = 0;
66 | this.startTime = 0;
67 | this.totalFrames = 0;
68 | this.update();
69 | },
70 |
71 | _now_epoch: function() {
72 | // return current date/time in hi-res epoch seconds
73 | var _mydate = new Date();
74 | return _mydate.getTime() / 1000;
75 | },
76 |
77 | count: function() {
78 | // advance one frame
79 | var _now = this._now_epoch();
80 | var _int_now = parseInt(_now, 10);
81 | if (_int_now != this.lastSecond) {
82 | this.totalFrames += this.frameCount;
83 | if (!this.startTime) this.startTime = _int_now;
84 | if (_int_now > this.startTime) this.average = this.totalFrames / (_int_now - this.startTime);
85 | else this.average = this.frameCount;
86 |
87 | this.current = this.frameCount;
88 | this.frameCount = 0;
89 | this.lastSecond = _int_now;
90 |
91 | if (this.visible) this.update();
92 | }
93 | this.frameCount++;
94 | }
95 |
96 | };
97 |
98 |
--------------------------------------------------------------------------------
/Demos/canvascycle/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antimatter15/jsgif/b46429c50a53d23b762d6ebb00b375aece3ed843/Demos/canvascycle/gif.gif
--------------------------------------------------------------------------------
/Demos/canvascycle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Canvas Cycle: True 8-bit Color Cycling with HTML5
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
40 |
41 |
42 |
SOUND:
43 |
46 |
47 |
ZOOM:
48 |
51 |
52 |
CYCLE SPEED:
53 |
56 |
57 |
CYCLE MODE:
58 |
61 |
62 |
PALETTE:
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
77 |
78 |
82 |
83 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/Demos/canvascycle/main.js:
--------------------------------------------------------------------------------
1 | // Color Cycling in HTML5 Canvas
2 | // BlendShift Technology conceived, designed and coded by Joseph Huckaby
3 | // Copyright (c) 2001-2002, 2010 Joseph Huckaby.
4 | // Released under the LGPL v3.0: http://www.opensource.org/licenses/lgpl-3.0.html
5 |
6 | var encoder = new GIFEncoder();
7 | encoder.setRepeat(0); //auto-loop
8 | encoder.setDelay(1000/40);
9 | encoder.start();
10 |
11 | FrameCount.visible = false;
12 |
13 | var CanvasCycle = {
14 |
15 | cookie: new CookieTree(),
16 | ctx: null,
17 | imageData: null,
18 | clock: 0,
19 | inGame: false,
20 | bmp: null,
21 | globalTimeStart: (new Date()).getTime(),
22 | inited: false,
23 | optTween: null,
24 | winSize: null,
25 | globalBrightness: 1.0,
26 | lastBrightness: 0,
27 | sceneIdx: -1,
28 | highlightColor: -1,
29 | defaultMaxVolume: 0.5,
30 |
31 | settings: {
32 | showOptions: false,
33 | targetFPS: 60,
34 | zoomFull: false,
35 | blendShiftEnabled: true,
36 | speedAdjust: 1.0,
37 | sound: true
38 | },
39 |
40 | contentSize: {
41 | width: 640,
42 | optionsWidth: 0,
43 | height: 480 + 40,
44 | scale: 1.0
45 | },
46 |
47 | init: function() {
48 | // called when DOM is ready
49 | if (!this.inited) {
50 | this.inited = true;
51 | $('container').style.display = 'block';
52 | $('d_options').style.display = 'none';
53 |
54 | FrameCount.init();
55 | this.handleResize();
56 |
57 | var pal_disp = $('palette_display');
58 | for (var idx = 0, len = 256; idx < len; idx++) {
59 | var div = document.createElement('div');
60 | div._idx = idx;
61 | div.id = 'pal_' + idx;
62 | div.className = 'palette_color';
63 | div.onmouseover = function() { CanvasCycle.highlightColor = this._idx; };
64 | div.onmouseout = function() { CanvasCycle.highlightColor = -1; };
65 | pal_disp.appendChild( div );
66 | }
67 | var div = document.createElement('div');
68 | div.className = 'clear';
69 | pal_disp.appendChild( div );
70 |
71 | // pick starting scene
72 | // var initialSceneIdx = Math.floor( Math.random() * scenes.length );
73 | var initialSceneIdx = 0;
74 |
75 | // populate scene menu
76 | var html = '';
77 | html += '';
83 | $('d_scene_selector').innerHTML = html;
84 |
85 | // read prefs from cookie
86 | var prefs = this.cookie.get('settings');
87 | if (prefs) {
88 | if (prefs.showOptions) this.toggleOptions();
89 | this.setRate( prefs.targetFPS );
90 | this.setZoom( prefs.zoomFull );
91 | this.setSpeed( prefs.speedAdjust );
92 | this.setBlendShift( prefs.blendShiftEnabled );
93 | this.setSound( prefs.sound );
94 | }
95 |
96 | // allow query to control sound
97 | if (location.href.match(/\bsound\=(\d+)/)) {
98 | this.setSound( !!parseInt(RegExp.$1, 10) );
99 | }
100 |
101 | this.loadImage( scenes[initialSceneIdx].name );
102 | this.sceneIdx = initialSceneIdx;
103 | }
104 | },
105 |
106 | jumpScene: function(dir) {
107 | // next or prev scene
108 | this.sceneIdx += dir;
109 | if (this.sceneIdx >= scenes.length) this.sceneIdx = 0;
110 | else if (this.sceneIdx < 0) this.sceneIdx = scenes.length - 1;
111 | $('fe_scene').selectedIndex = this.sceneIdx;
112 | this.switchScene( $('fe_scene') );
113 | },
114 |
115 | switchScene: function(menu) {
116 | // switch to new scene (grab menu selection)
117 | this.stopSceneAudio();
118 |
119 | var name = menu.options[menu.selectedIndex].value;
120 | this.sceneIdx = menu.selectedIndex;
121 |
122 | if (ua.mobile) {
123 | // no transitions on mobile devices, just switch as fast as possible
124 | this.inGame = false;
125 |
126 | this.ctx.clearRect(0, 0, this.bmp.width, this.bmp.height);
127 | this.ctx.fillStyle = "rgb(0,0,0)";
128 | this.ctx.fillRect (0, 0, this.bmp.width, this.bmp.height);
129 |
130 | CanvasCycle.globalBrightness = 1.0;
131 | CanvasCycle.loadImage( name );
132 | }
133 | else {
134 | TweenManager.removeAll({ category: 'scenefade' });
135 | TweenManager.tween({
136 | target: { value: this.globalBrightness, newSceneName: name },
137 | duration: Math.floor( this.settings.targetFPS / 2 ),
138 | mode: 'EaseInOut',
139 | algo: 'Quadratic',
140 | props: { value: 0.0 },
141 | onTweenUpdate: function(tween) {
142 | CanvasCycle.globalBrightness = tween.target.value;
143 | },
144 | onTweenComplete: function(tween) {
145 | CanvasCycle.loadImage( tween.target.newSceneName );
146 | },
147 | category: 'scenefade'
148 | });
149 | }
150 | },
151 |
152 | loadImage: function(name) {
153 | // load image JSON from the server
154 | var loading = $('d_loading');
155 | loading.style.left = '' + Math.floor( ((this.contentSize.width * this.contentSize.scale) / 2) - 16 ) + 'px';
156 | loading.style.top = '' + Math.floor( ((this.contentSize.height * this.contentSize.scale) / 2) - 16 ) + 'px';
157 | loading.show();
158 |
159 | var url = 'image.php?file='+name+'&callback=CanvasCycle.processImage';
160 | var scr = document.createElement('SCRIPT');
161 | scr.type = 'text/javascript';
162 | scr.src = url;
163 | document.getElementsByTagName('HEAD')[0].appendChild(scr);
164 | },
165 |
166 | processImage: function(img) {
167 | // initialize, receive image data from server
168 | $('d_loading').hide();
169 |
170 | this.bmp = new Bitmap(img);
171 | this.bmp.optimize();
172 |
173 | // $('d_debug').innerHTML = img.filename;
174 |
175 | var canvas = $('mycanvas');
176 | if (!canvas.getContext) return; // no canvas support
177 |
178 | if (!this.ctx) this.ctx = canvas.getContext('2d');
179 | this.ctx.clearRect(0, 0, this.bmp.width, this.bmp.height);
180 | this.ctx.fillStyle = "rgb(0,0,0)";
181 | this.ctx.fillRect (0, 0, this.bmp.width, this.bmp.height);
182 |
183 | if (!this.imageData) {
184 | if (this.ctx.createImageData) {
185 | this.imageData = this.ctx.createImageData( this.bmp.width, this.bmp.height );
186 | }
187 | else if (this.ctx.getImageData) {
188 | this.imageData = this.ctx.getImageData( 0, 0, this.bmp.width, this.bmp.height );
189 | }
190 | else return; // no canvas data support
191 | }
192 |
193 | if (ua.mobile) {
194 | // no transition on mobile devices
195 | this.globalBrightness = 1.0;
196 | }
197 | else {
198 | this.globalBrightness = 0.0;
199 | TweenManager.removeAll({ category: 'scenefade' });
200 | TweenManager.tween({
201 | target: { value: 0 },
202 | duration: Math.floor( this.settings.targetFPS / 2 ),
203 | mode: 'EaseInOut',
204 | algo: 'Quadratic',
205 | props: { value: 1.0 },
206 | onTweenUpdate: function(tween) {
207 | CanvasCycle.globalBrightness = tween.target.value;
208 | },
209 | category: 'scenefade'
210 | });
211 | }
212 |
213 | if (!this.inGame) {
214 | this.inGame = true;
215 | this.animate();
216 | }
217 |
218 | this.startSceneAudio();
219 | },
220 |
221 | animate: function() {
222 | // animate one frame. and schedule next
223 | if (this.inGame) {
224 | var colors = this.bmp.palette.colors;
225 |
226 | if (this.settings.showOptions) {
227 | for (var idx = 0, len = colors.length; idx < len; idx++) {
228 | var clr = colors[idx];
229 | var div = $('pal_'+idx);
230 | div.style.backgroundColor = 'rgb(' + clr.red + ',' + clr.green + ',' + clr.blue + ')';
231 | }
232 |
233 | if (this.clock % this.settings.targetFPS == 0) $('d_debug').innerHTML = 'FPS: ' + FrameCount.current;
234 | }
235 |
236 | this.bmp.palette.cycle( this.bmp.palette.baseColors, GetTickCount(), this.settings.speedAdjust, this.settings.blendShiftEnabled );
237 | if (this.highlightColor > -1) {
238 | this.bmp.palette.colors[ this.highlightColor ] = new Color(255, 255, 255);
239 | }
240 | if (this.globalBrightness < 1.0) {
241 | // bmp.palette.fadeToColor( pureBlack, 1.0 - globalBrightness, 1.0 );
242 | this.bmp.palette.burnOut( 1.0 - this.globalBrightness, 1.0 );
243 | }
244 | this.bmp.render( this.imageData, (this.lastBrightness == this.globalBrightness) && (this.highlightColor == this.lastHighlightColor) );
245 | this.lastBrightness = this.globalBrightness;
246 | this.lastHighlightColor = this.highlightColor;
247 |
248 | this.ctx.putImageData( this.imageData, 0, 0 );
249 |
250 | TweenManager.logic( this.clock );
251 | this.clock++;
252 | FrameCount.count();
253 | //this.scaleAnimate();
254 |
255 |
256 | if(this.clock > 100 && this.clock < 110){
257 | encoder.addFrame(this.ctx);
258 | console.log('added frame');
259 | }
260 |
261 |
262 | if (this.inGame) setTimeout( function() { CanvasCycle.animate(); }, 1000 / this.settings.targetFPS );
263 | }
264 | },
265 |
266 | scaleAnimate: function() {
267 | // handle scaling image up or down
268 | if (this.settings.zoomFull) {
269 | // scale up to full size
270 | var totalNativeWidth = this.contentSize.width + this.contentSize.optionsWidth;
271 | var maxScaleX = (this.winSize.width - 30) / totalNativeWidth;
272 |
273 | var totalNativeHeight = this.contentSize.height;
274 | var maxScaleY = (this.winSize.height - 30) / totalNativeHeight;
275 |
276 | var maxScale = Math.min( maxScaleX, maxScaleY );
277 |
278 | if (this.contentSize.scale != maxScale) {
279 | this.contentSize.scale += ((maxScale - this.contentSize.scale) / 8);
280 | if (Math.abs(this.contentSize.scale - maxScale) < 0.001) this.contentSize.scale = maxScale; // close enough
281 |
282 | var sty = $('mycanvas').style;
283 |
284 | if (ua.webkit) sty.webkitTransform = 'translate3d(0px, 0px, 0px) scale('+this.contentSize.scale+')';
285 | else if (ua.ff) sty.MozTransform = 'scale('+this.contentSize.scale+')';
286 | else if (ua.op) sty.OTransform = 'scale('+this.contentSize.scale+')';
287 | else sty.transform = 'scale('+this.contentSize.scale+')';
288 |
289 | sty.marginRight = '' + Math.floor( (this.contentSize.width * this.contentSize.scale) - this.contentSize.width ) + 'px';
290 | $('d_header').style.width = '' + Math.floor(this.contentSize.width * this.contentSize.scale) + 'px';
291 | this.repositionContainer();
292 | }
293 | }
294 | else {
295 | // scale back down to native
296 | if (this.contentSize.scale > 1.0) {
297 | this.contentSize.scale += ((1.0 - this.contentSize.scale) / 8);
298 | if (this.contentSize.scale < 1.001) this.contentSize.scale = 1.0; // close enough
299 |
300 | var sty = $('mycanvas').style;
301 |
302 | if (ua.webkit) sty.webkitTransform = 'translate3d(0px, 0px, 0px) scale('+this.contentSize.scale+')';
303 | else if (ua.ff) sty.MozTransform = 'scale('+this.contentSize.scale+')';
304 | else if (ua.op) sty.OTransform = 'scale('+this.contentSize.scale+')';
305 | else sty.transform = 'scale('+this.contentSize.scale+')';
306 |
307 | sty.marginRight = '' + Math.floor( (this.contentSize.width * this.contentSize.scale) - this.contentSize.width ) + 'px';
308 | $('d_header').style.width = '' + Math.floor(this.contentSize.width * this.contentSize.scale) + 'px';
309 | this.repositionContainer();
310 | }
311 | }
312 | },
313 |
314 | repositionContainer: function() {
315 | // reposition container element based on inner window size
316 | var div = $('container');
317 | if (div) {
318 | this.winSize = getInnerWindowSize();
319 | div.style.left = '' + Math.floor((this.winSize.width / 2) - (((this.contentSize.width * this.contentSize.scale) + this.contentSize.optionsWidth) / 2)) + 'px';
320 | div.style.top = '' + Math.floor((this.winSize.height / 2) - ((this.contentSize.height * this.contentSize.scale) / 2)) + 'px';
321 | }
322 | },
323 |
324 | handleResize: function() {
325 | // called when window resizes
326 | this.repositionContainer();
327 | if (this.settings.zoomFull) this.scaleAnimate();
328 | },
329 |
330 | saveSettings: function() {
331 | // save settings in cookie
332 | this.cookie.set( 'settings', this.settings );
333 | this.cookie.save();
334 | },
335 |
336 | startSceneAudio: function() {
337 | // start audio for current scene, if applicable
338 | var scene = scenes[ this.sceneIdx ];
339 | if (scene.sound && this.settings.sound && window.Audio) {
340 | if (this.audioTrack) {
341 | try { this.audioTrack.pause(); } catch(e) {;}
342 | }
343 | TweenManager.removeAll({ category: 'audio' });
344 |
345 | var ext = (ua.ff || ua.op) ? 'ogg' : 'mp3';
346 | var track = this.audioTrack = new Audio( 'audio/' + scene.sound + '.' + ext );
347 | track.volume = 0;
348 | track.loop = true;
349 | track.autobuffer = false;
350 | track.autoplay = true;
351 |
352 | track.addEventListener('canplaythrough', function() {
353 | track.play();
354 | TweenManager.tween({
355 | target: track,
356 | duration: Math.floor( CanvasCycle.settings.targetFPS * 2 ),
357 | mode: 'EaseOut',
358 | algo: 'Linear',
359 | props: { volume: scene.maxVolume || CanvasCycle.defaultMaxVolume },
360 | category: 'audio'
361 | });
362 | }, false);
363 |
364 | if (ua.iphone || ua.ipad) {
365 | // these may support audio, but just don't invoke events
366 | // try to force it
367 | setTimeout( function() { track.play(); track.volume = 1.0; }, 1000 );
368 | }
369 |
370 | if (ua.ff || ua.mobile) {
371 | // loop doesn't seem to work on FF or mobile devices, so let's force it
372 | track.addEventListener('ended', function() {
373 | track.currentTime = 0;
374 | track.play();
375 | }, false);
376 | }
377 |
378 | track.load();
379 | } // sound enabled and supported
380 | },
381 |
382 | stopSceneAudio: function() {
383 | // fade out and stop audio for current scene
384 | var scene = scenes[ this.sceneIdx ];
385 | if (scene.sound && this.settings.sound && window.Audio && this.audioTrack) {
386 | var track = this.audioTrack;
387 |
388 | if (ua.iphone || ua.ipad) {
389 | // no transition here, so just stop sound
390 | track.pause();
391 | }
392 | else {
393 | TweenManager.removeAll({ category: 'audio' });
394 | TweenManager.tween({
395 | target: track,
396 | duration: Math.floor( CanvasCycle.settings.targetFPS / 2 ),
397 | mode: 'EaseOut',
398 | algo: 'Linear',
399 | props: { volume: 0 },
400 | onTweenComplete: function(tween) {
401 | track.pause();
402 | },
403 | category: 'audio'
404 | });
405 | }
406 | }
407 | },
408 |
409 | toggleOptions: function() {
410 | var startValue, endValue;
411 | TweenManager.removeAll({ category: 'options' });
412 |
413 | if (!this.settings.showOptions) {
414 | startValue = 0;
415 | if (this.optTween) startValue = this.optTween.target.value;
416 | endValue = 1.0;
417 | $('d_options').style.display = '';
418 | $('d_options').style.opacity = startValue;
419 | $('btn_options_toggle').innerHTML = '« Hide Options';
420 | }
421 | else {
422 | startValue = 1.0;
423 | if (this.optTween) startValue = this.optTween.target.value;
424 | endValue = 0;
425 | $('btn_options_toggle').innerHTML = 'Show Options »';
426 | }
427 |
428 | this.optTween = TweenManager.tween({
429 | target: { value: startValue },
430 | duration: Math.floor( this.settings.targetFPS / 3 ),
431 | mode: 'EaseOut',
432 | algo: 'Quadratic',
433 | props: { value: endValue },
434 | onTweenUpdate: function(tween) {
435 | // $('d_options').style.left = '' + Math.floor(tween.target.value - 150) + 'px';
436 | $('d_options').style.opacity = tween.target.value;
437 | $('btn_options_toggle').style.left = '' + Math.floor(tween.target.value * 128) + 'px';
438 |
439 | CanvasCycle.contentSize.optionsWidth = Math.floor( tween.target.value * 150 );
440 | CanvasCycle.handleResize();
441 | },
442 | onTweenComplete: function(tween) {
443 | if (tween.target.value == 0) $('d_options').style.display = 'none';
444 | CanvasCycle.optTween = null;
445 | },
446 | category: 'options'
447 | });
448 |
449 | this.settings.showOptions = !this.settings.showOptions;
450 | this.saveSettings();
451 | },
452 |
453 | setZoom: function(enabled) {
454 | if (enabled != this.settings.zoomFull) {
455 | this.settings.zoomFull = enabled;
456 | this.saveSettings();
457 | $('btn_zoom_actual').setClass('selected', !enabled);
458 | $('btn_zoom_max').setClass('selected', enabled);
459 | }
460 | },
461 |
462 | setSound: function(enabled) {
463 | $('btn_sound_on').setClass('selected', enabled);
464 | $('btn_sound_off').setClass('selected', !enabled);
465 | this.settings.sound = enabled;
466 |
467 | if (this.sceneIdx > -1) {
468 | if (enabled) {
469 | // enable sound
470 | if (this.audioTrack) this.audioTrack.play();
471 | else this.startSceneAudio();
472 | }
473 | else {
474 | // disable sound
475 | if (this.audioTrack) this.audioTrack.pause();
476 | }
477 | }
478 |
479 | this.saveSettings();
480 | },
481 |
482 | setRate: function(rate) {
483 | /* $('btn_rate_30').setClass('selected', rate == 30);
484 | $('btn_rate_60').setClass('selected', rate == 60);
485 | $('btn_rate_90').setClass('selected', rate == 90); */
486 | this.settings.targetFPS = rate;
487 | this.saveSettings();
488 | },
489 |
490 | setSpeed: function(speed) {
491 | $('btn_speed_025').setClass('selected', speed == 0.25);
492 | $('btn_speed_05').setClass('selected', speed == 0.5);
493 | $('btn_speed_1').setClass('selected', speed == 1);
494 | $('btn_speed_2').setClass('selected', speed == 2);
495 | $('btn_speed_4').setClass('selected', speed == 4);
496 | this.settings.speedAdjust = speed;
497 | this.saveSettings();
498 | },
499 |
500 | setBlendShift: function(enabled) {
501 | $('btn_blendshift_on').setClass('selected', enabled);
502 | $('btn_blendshift_off').setClass('selected', !enabled);
503 | this.settings.blendShiftEnabled = enabled;
504 | this.saveSettings();
505 | }
506 |
507 | };
508 |
509 | var CC = CanvasCycle; // shortcut
510 |
--------------------------------------------------------------------------------
/Demos/canvascycle/oop.js:
--------------------------------------------------------------------------------
1 | /**
2 | * JavaScript Object Oriented Programming Framework
3 | * Author: Joseph Huckaby
4 | * Copyright (c) 2008 Joseph Huckaby.
5 | * Released under the LGPL v3.0: http://www.opensource.org/licenses/lgpl-3.0.html
6 | **/
7 |
8 | function _var_exists(name) {
9 | // return true if var exists in "global" context, false otherwise
10 | try {
11 | eval('var foo = ' + name + ';');
12 | }
13 | catch (e) {
14 | return false;
15 | }
16 | return true;
17 | }
18 |
19 | var Namespace = {
20 | // simple namespace support for classes
21 | create: function(path) {
22 | // create namespace for class
23 | var container = null;
24 | while (path.match(/^(\w+)\.?/)) {
25 | var key = RegExp.$1;
26 | path = path.replace(/^(\w+)\.?/, "");
27 |
28 | if (!container) {
29 | if (!_var_exists(key)) eval('window.' + key + ' = {};');
30 | eval('container = ' + key + ';');
31 | }
32 | else {
33 | if (!container[key]) container[key] = {};
34 | container = container[key];
35 | }
36 | }
37 | },
38 | prep: function(name) {
39 | // prep namespace for new class
40 | if (name.match(/^(.+)\.(\w+)$/)) {
41 | var path = RegExp.$1;
42 | name = RegExp.$2;
43 | Namespace.create(path);
44 | }
45 | return { name: name };
46 | }
47 | };
48 |
49 | var Class = {
50 | // simple class factory
51 | create: function(name, members) {
52 | // generate new class with optional namespace
53 |
54 | // support Prototype-style calling convention
55 | if (!name && !members) {
56 | return( function() {
57 | if (this.initialize) this.initialize.apply(this, arguments);
58 | else if (this.__construct) this.__construct.apply(this, arguments);
59 | } );
60 | }
61 |
62 | assert(name, "Must pass name to Class.create");
63 | if (!members) members = {};
64 | members.__parent = null;
65 |
66 | var ns = Namespace.prep(name);
67 | // var container = ns.container;
68 | var full_name = name;
69 | name = ns.name;
70 |
71 | members.__name = name;
72 |
73 | if (!members.__construct) members.__construct = function() {};
74 |
75 | // container[name] = members.__construct;
76 | var obj = null;
77 | eval( full_name + ' = obj = members.__construct;' );
78 |
79 | var static_members = members.__static;
80 | if (static_members) {
81 | for (var key in static_members) {
82 | obj[key] = static_members[key];
83 | }
84 | }
85 |
86 | obj.prototype = members;
87 | obj.extend = obj.subclass = function(name, members) {
88 | Class.subclass( this, name, members );
89 | };
90 | obj.set = obj.add = function(members) {
91 | Class.add( this, members );
92 | };
93 | },
94 | subclass: function(parent, name, members) {
95 | // subclass an existing class
96 | assert(parent, "Must pass parent class to Class.subclass");
97 | assert(name, "Must pass name to Class.subclass");
98 | if (!members) members = {};
99 | members.__name = name;
100 | members.__parent = parent.prototype;
101 |
102 | var ns = Namespace.prep(name);
103 | // var container = ns.container;
104 | var subname = ns.name;
105 |
106 | var obj = null;
107 |
108 | if (members.__construct) {
109 | // explicit subclass constructor
110 | // container[subname] = members.__construct;
111 | eval( name + ' = obj = members.__construct;' );
112 | }
113 | else {
114 | // inherit parent's constructor
115 | var code = parent.toString();
116 | var args = code.substring( code.indexOf("(")+1, code.indexOf(")") );
117 | var inner_code = code.substring( code.indexOf("{")+1, code.lastIndexOf("}") );
118 | eval('members.__construct = ' + name + ' = obj = function ('+args+') {'+inner_code+'};');
119 | }
120 |
121 | // inherit static from parent, if applicable
122 | if (parent.prototype.__static) {
123 | for (var key in parent.prototype.__static) {
124 | obj[key] = parent.prototype.__static[key];
125 | }
126 | }
127 |
128 | var static_members = members.__static;
129 | if (static_members) {
130 | for (var key in static_members) {
131 | obj[key] = static_members[key];
132 | }
133 | }
134 |
135 | obj.prototype = new parent();
136 | // for (var key in parent.prototype) container[subname].prototype[key] = parent.prototype[key];
137 | for (var key in members) obj.prototype[key] = members[key];
138 |
139 | obj.extend = obj.subclass = function(name, members) {
140 | Class.subclass( this, name, members );
141 | };
142 | obj.set = obj.add = function(members) {
143 | Class.add( this, members );
144 | };
145 | },
146 | add: function(obj, members) {
147 | // add members to an existing class
148 | for (var key in members) obj.prototype[key] = members[key];
149 | },
150 | require: function() {
151 | // make sure classes are loaded
152 | for (var idx = 0, len = arguments.length; idx < len; idx++) {
153 | assert( !!eval('window.' + arguments[idx]) );
154 | }
155 | return true;
156 | }
157 | };
158 | Class.extend = Class.subclass;
159 | Class.set = Class.add;
160 |
161 | if (!window.assert) window.assert = function(fact, msg) {
162 | // very simple assert
163 | if (!fact) return alert("ASSERT FAILED! " + msg);
164 | return fact;
165 | };
166 |
--------------------------------------------------------------------------------
/Demos/canvascycle/palette.js:
--------------------------------------------------------------------------------
1 | // 8-bit Palette Classes for use in HTML5 Canvas
2 | // Ported from a C++ library written by Joseph Huckaby
3 | // BlendShift Technology conceived, designed and coded by Joseph Huckaby
4 | // Copyright (c) 2001-2002, 2010 Joseph Huckaby.
5 | // Released under the LGPL v3.0: http://www.opensource.org/licenses/lgpl-3.0.html
6 |
7 | Class.create( 'Color', {
8 | // represents one 24-bit RGB color
9 | red: 0,
10 | green: 0,
11 | blue: 0,
12 |
13 | __construct: function(r, g, b) {
14 | this.set(r, g, b);
15 | },
16 |
17 | set: function(r, g, b) {
18 | this.red = r;
19 | this.green = g;
20 | this.blue = b;
21 | }
22 | } );
23 |
24 | Class.create( 'Cycle', {
25 | // represents one cycle (one range of colors that animate)
26 | rate: 0,
27 | reverse: 0,
28 | low: 0,
29 | high: 0,
30 |
31 | __construct: function(r, rev, l, h) {
32 | this.rate = r;
33 | this.rev = rev;
34 | this.low = l;
35 | this.high = h;
36 | }
37 | } );
38 |
39 | Class.create( 'Palette', {
40 | // represents a single palette, which can have
41 | // multiple "cycles" (animated color ranges) defined.
42 |
43 | colors: null,
44 | baseColors: null,
45 | cycles: null,
46 | numColors: 0,
47 | numCycles: 0,
48 |
49 | __static: {
50 | // static class members
51 | PRECISION: 100,
52 | USE_BLEND_SHIFT: 1,
53 | CYCLE_SPEED: 280,
54 | ENABLE_CYCLING: 1,
55 |
56 | // this utility function allows for variable precision floating point modulus
57 | DFLOAT_MOD: function(a,b) { return (Math.floor(a*Palette.PRECISION) % Math.floor(b*Palette.PRECISION))/Palette.PRECISION; }
58 | },
59 |
60 | __construct: function(clrs, cycls) {
61 | // class constructor
62 | this.colors = [];
63 | this.baseColors = [];
64 | for (var idx = 0, len = clrs.length; idx < len; idx++) {
65 | var clr = clrs[idx];
66 | this.baseColors.push( new Color( clr[0], clr[1], clr[2] ) );
67 | }
68 |
69 | this.cycles = [];
70 | for (var idx = 0, len = cycls.length; idx < len; idx++) {
71 | var cyc = cycls[idx];
72 | this.cycles.push( new Cycle( cyc.rate, cyc.reverse, cyc.low, cyc.high ) );
73 | }
74 |
75 | this.numColors = this.baseColors.length;
76 | this.numCycles = this.cycles.length;
77 | },
78 |
79 | copyColors: function(source, dest) {
80 | // copy one array of colors to another
81 | for (var idx = 0, len = source.length; idx < len; idx++) {
82 | if (!dest[idx]) dest[idx] = new Color();
83 | dest[idx].red = source[idx].red;
84 | dest[idx].green = source[idx].green;
85 | dest[idx].blue = source[idx].blue;
86 | }
87 | },
88 |
89 | swapColors: function(a, b) {
90 | // swap the color values of a with b
91 | var temp;
92 | temp = a.red; a.red = b.red; b.red = temp;
93 | temp = a.green; a.green = b.green; b.green = temp;
94 | temp = a.blue; a.blue = b.blue; b.blue = temp;
95 | },
96 |
97 | reverseColors: function(colors, range) {
98 | // reverse order of colors
99 | var i;
100 | var cycleSize = (range.high - range.low) + 1;
101 |
102 | for (i=0; i max) frame = max;
113 |
114 | tempColor.red = Math.floor( sourceColor.red + (((destColor.red - sourceColor.red) * frame) / max) );
115 | tempColor.green = Math.floor( sourceColor.green + (((destColor.green - sourceColor.green) * frame) / max) );
116 | tempColor.blue = Math.floor( sourceColor.blue + (((destColor.blue - sourceColor.blue) * frame) / max) );
117 |
118 | return(tempColor);
119 | },
120 |
121 | shiftColors: function(colors, range, amount) {
122 | // shift (hard cycle) colors by amount
123 | var i, j, temp;
124 | amount = Math.floor(amount);
125 |
126 | for (i = 0; i < amount; i++) {
127 | temp = colors[range.high];
128 | for (j=range.high-1; j>=range.low; j--)
129 | colors[j+1] = colors[j];
130 | colors[range.low] = temp;
131 | } // i loop
132 | },
133 |
134 | blendShiftColors: function(colors, range, amount) {
135 | // shift colors using BlendShift (fade colors creating a smooth transition)
136 | // BlendShift Technology conceived, designed and coded by Joseph Huckaby
137 | var j, temp;
138 |
139 | this.shiftColors(colors, range, amount);
140 |
141 | var frame = Math.floor( (amount - Math.floor(amount)) * Palette.PRECISION );
142 |
143 | temp = colors[range.high];
144 | for (j=range.high-1; j>=range.low; j--)
145 | colors[j+1] = this.fadeColor(colors[j+1], colors[j], frame, Palette.PRECISION);
146 | colors[range.low] = this.fadeColor(colors[range.low], temp, frame, Palette.PRECISION);
147 | },
148 |
149 | cycle: function(sourceColors, timeNow, speedAdjust, blendShift) {
150 | // cycle all animated color ranges in palette based on timestamp
151 | var i;
152 | var cycleSize, cycleRate;
153 | var cycleAmount;
154 |
155 | this.copyColors( sourceColors, this.colors );
156 |
157 | if (Palette.ENABLE_CYCLING) {
158 | for (i=0; i= cycleSize) cycleAmount = (cycleSize*2) - cycleAmount;
172 | }
173 | else if (cycle.reverse < 6) {
174 | // sine wave
175 | cycleAmount = DFLOAT_MOD((timeNow / (1000 / cycleRate)), cycleSize);
176 | cycleAmount = Math.sin((cycleAmount * 3.1415926 * 2)/cycleSize) + 1;
177 | if (cycle.reverse == 4) cycleAmount *= (cycleSize / 4);
178 | else if (cycle.reverse == 5) cycleAmount *= (cycleSize / 2);
179 | }
180 |
181 | if (cycle.reverse == 2) this.reverseColors(this.colors, cycle);
182 |
183 | if (Palette.USE_BLEND_SHIFT && blendShift) this.blendShiftColors(this.colors, cycle, cycleAmount);
184 | else this.shiftColors(this.colors, cycle, cycleAmount);
185 |
186 | if (cycle.reverse == 2) this.reverseColors(this.colors, cycle);
187 |
188 | cycle.cycleAmount = cycleAmount;
189 | } // active cycle
190 | } // i loop
191 | }
192 | },
193 |
194 | fade: function(destPalette, frame, max) {
195 | // fade entire palette to another, by adjustable amount
196 | var idx;
197 |
198 | for (idx=0; idx span {
254 | padding: 0px 10px 0px 10px;
255 | }
--------------------------------------------------------------------------------
/Demos/canvascycle/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Canvas Cycle -- Color Cycling with Canvas
7 |
8 |
9 |
10 |
36 |
37 |
38 |
39 |
40 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
187 |
188 |
189 |
190 |
211 |
212 |
213 |
--------------------------------------------------------------------------------
/Demos/canvascycle/tools.js:
--------------------------------------------------------------------------------
1 | // Misc Tools
2 | // Copyright (c) 2010 Joseph Huckaby.
3 | // Released under the LGPL v3.0: http://www.opensource.org/licenses/lgpl-3.0.html
4 | // getInnerWindowSize() was grabbed from: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
5 |
6 | function $(thingy) {
7 | // universal DOM lookup function, extends object with hide/show/addClass/removeClass
8 | // can pass in ID or actual DOM object reference
9 | var obj = (typeof(thingy) == 'string') ? document.getElementById(thingy) : thingy;
10 | if (obj && !obj.setOpacity) {
11 | obj.hide = function() { this.style.display = 'none'; return this; };
12 | obj.show = function() { this.style.display = ''; return this; };
13 | obj.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; return this; };
14 |
15 | obj.removeClass = function(name) {
16 | var classes = this.className.split(/\s+/);
17 | var idx = find_idx_in_array( classes, name );
18 | if (idx > -1) {
19 | classes.splice( idx, 1 );
20 | this.className = classes.join(' ');
21 | }
22 | return this;
23 | };
24 |
25 | obj.setClass = function(name, enabled) {
26 | if (enabled) this.addClass(name);
27 | else this.removeClass(name);
28 | };
29 | }
30 | return obj;
31 | }
32 |
33 | function GetTickCount() {
34 | // milliseconds since page load
35 | return Math.floor( (new Date()).getTime() - CanvasCycle.globalTimeStart );
36 | }
37 |
38 | function getInnerWindowSize(dom) {
39 | // get size of inner window
40 | // From: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
41 | if (!dom) dom = window;
42 | var myWidth = 0, myHeight = 0;
43 |
44 | if( typeof( dom.innerWidth ) == 'number' ) {
45 | // Non-IE
46 | myWidth = dom.innerWidth;
47 | myHeight = dom.innerHeight;
48 | }
49 | else if( dom.document.documentElement && ( dom.document.documentElement.clientWidth || dom.document.documentElement.clientHeight ) ) {
50 | // IE 6+ in 'standards compliant mode'
51 | myWidth = dom.document.documentElement.clientWidth;
52 | myHeight = dom.document.documentElement.clientHeight;
53 | }
54 | else if( dom.document.body && ( dom.document.body.clientWidth || dom.document.body.clientHeight ) ) {
55 | // IE 4 compatible
56 | myWidth = dom.document.body.clientWidth;
57 | myHeight = dom.document.body.clientHeight;
58 | }
59 | return { width: myWidth, height: myHeight };
60 | }
61 |
62 | function find_idx_in_array(arr, elem) {
63 | // return idx of elem in arr, or -1 if not found
64 | for (var idx = 0, len = arr.length; idx < len; idx++) {
65 | if (arr[idx] == elem) return idx;
66 | }
67 | return -1;
68 | }
69 |
70 | function isa_hash(arg) {
71 | // determine if arg is a hash
72 | return( !!arg && (typeof(arg) == 'object') && (typeof(arg.length) == 'undefined') );
73 | }
74 |
75 | function isa_array(arg) {
76 | // determine if arg is an array or is array-like
77 | if (typeof(arg) == 'array') return true;
78 | return( !!arg && (typeof(arg) == 'object') && (typeof(arg.length) != 'undefined') );
79 | }
80 |
81 | function merge_objects(a, b) {
82 | // merge keys from a and b into c and return c
83 | // b has precedence over a
84 | if (!a) a = {};
85 | if (!b) b = {};
86 | var c = {};
87 |
88 | // also handle serialized objects for a and b
89 | if (typeof(a) != 'object') eval( "a = " + a );
90 | if (typeof(b) != 'object') eval( "b = " + b );
91 |
92 | for (var key in a) c[key] = a[key];
93 | for (var key in b) c[key] = b[key];
94 |
95 | return c;
96 | }
97 |
98 | function serialize(thingy, glue) {
99 | // serialize anything into json
100 | // or perl object notation (just set glue to '=>')
101 | if (!glue) glue = ':'; // default to json
102 | var stream = '';
103 |
104 | if (typeof(thingy) == 'boolean') {
105 | stream += (thingy ? 'true' : 'false');
106 | }
107 | else if (typeof(thingy) == 'number') {
108 | stream += thingy;
109 | }
110 | else if (typeof(thingy) == 'string') {
111 | stream += '"' + thingy.replace(/([\"\\])/g, '\\$1').replace(/\r/g, "\\r").replace(/\n/g, "\\n") + '"';
112 | }
113 | else if (isa_hash(thingy)) {
114 | var num = 0;
115 | var buffer = [];
116 | for (var key in thingy) {
117 | buffer[num] = (key.match(/^[A-Za-z]\w*$/) ? key : ('"'+key+'"')) + glue + serialize(thingy[key], glue);
118 | num++;
119 | }
120 | stream += '{' + buffer.join(',') + '}';
121 | }
122 | else if (isa_array(thingy)) {
123 | var buffer = [];
124 | for (var idx = 0, len = thingy.length; idx < len; idx++) {
125 | buffer[idx] = serialize(thingy[idx], glue);
126 | }
127 | stream += '[' + buffer.join(',') + ']';
128 | }
129 | else {
130 | // unknown type, just return 0
131 | stream += '0';
132 | }
133 |
134 | return stream;
135 | }
136 |
137 | (function() {
138 | // Browser detection
139 | var u = navigator.userAgent;
140 | var webkit = !!u.match(/webkit/i);
141 | var chrome = !!u.match(/Chrome/);
142 | var safari = !!u.match(/Safari/) && !chrome;
143 | var ie = !!u.match(/MSIE/);
144 | var ie6 = ie && !!u.match(/MSIE\s+6/);
145 | var ie7 = ie && !!u.match(/MSIE\s+7/);
146 | var ie8 = ie && !!u.match(/MSIE\s+8/);
147 | var moz = !safari && !ie;
148 | var op = !!window.opera;
149 | var mac = !!u.match(/Mac/i);
150 | var ff = !!u.match(/Firefox/);
151 | var iphone = !!u.match(/iPhone/);
152 | var ipad = !!u.match(/iPad/);
153 | var snow = !!u.match(/Mac\s+OS\s+X\s+10\D[6789]/);
154 | var titanium = safari && !!u.match(/Titanium/);
155 | var android = !!u.match(/android/i);
156 |
157 | var ver = 0;
158 | if (ff && u.match(/Firefox\D+(\d+(\.\d+)?)/)) {
159 | ver = parseFloat( RegExp.$1 );
160 | }
161 | else if (safari && u.match(/Version\D(\d+(\.\d+)?)/)) {
162 | ver = parseFloat( RegExp.$1 );
163 | }
164 | else if (chrome && u.match(/Chrome\D(\d+(\.\d+)?)/)) {
165 | ver = parseFloat( RegExp.$1 );
166 | }
167 | else if (ie && u.match(/MSIE\D+(\d+(\.\d+)?)/)) {
168 | ver = parseFloat( RegExp.$1 );
169 | }
170 | else if (op && u.match(/Opera\D+(\d+(\.\d+)?)/)) {
171 | ver = parseFloat( RegExp.$1 );
172 | }
173 |
174 | window.ua = {
175 | webkit: webkit,
176 | safari: safari,
177 | ie: ie,
178 | ie8: ie8,
179 | ie7: ie7,
180 | ie6: ie6,
181 | moz: moz,
182 | op: op,
183 | mac: mac,
184 | ff: ff,
185 | chrome: chrome,
186 | iphone: iphone,
187 | ipad: ipad,
188 | snow: snow,
189 | titanium: titanium,
190 | android: android,
191 | mobile: iphone || ipad || android,
192 | ver: ver
193 | };
194 | })();
195 |
196 |
--------------------------------------------------------------------------------
/Demos/canvascycle/tween.js:
--------------------------------------------------------------------------------
1 | ////
2 | // Tween.js
3 | // Provides numerical obj property animation with easing.
4 | // Copyright (c) 2005 - 2008 Joseph Huckaby
5 | ////
6 |
7 | var TweenManager = {
8 |
9 | _tweens: {},
10 | _nextId: 1,
11 |
12 | add: function(_args) {
13 | // add new tween to table
14 | var _tween = new Tween(_args);
15 | this._tweens[ this._nextId ] = _tween;
16 | _tween.id = this._nextId;
17 | this._nextId++;
18 | return _tween;
19 | },
20 |
21 | logic: function(clock) {
22 | // update tweens
23 | for (var _id in this._tweens) {
24 | var _tween = this._tweens[_id];
25 | _tween.logic(clock);
26 | if (_tween.destroyed) delete this._tweens[_id];
27 | }
28 | },
29 |
30 | removeAll: function() {
31 | // remove all tweens
32 | if (arguments.length) {
33 | var criteria = arguments[0];
34 | var num_crit = 0;
35 | for (var key in criteria) num_crit++;
36 |
37 | for (var id in this._tweens) {
38 | var tween = this._tweens[id];
39 | var matched = 0;
40 | for (var key in criteria) {
41 | if (tween[key] == criteria[key]) matched++;
42 | }
43 | if (matched == num_crit) {
44 | tween.destroyed = 1;
45 | delete this._tweens[id];
46 | }
47 | }
48 | }
49 | else {
50 | for (var id in this._tweens) {
51 | var tween = this._tweens[id];
52 | tween.destroyed = 1;
53 | }
54 | this._tweens = {};
55 | }
56 | }
57 |
58 | };
59 | TweenManager.tween = TweenManager.add;
60 |
61 | // Tween Object
62 |
63 | function Tween(_args) {
64 | // create new tween
65 | // args should contain:
66 | // target: target object
67 | // duration: length of animation in logic frames
68 | // mode: EaseIn, EaseOut, EaseInOut (omit or empty string for linear)
69 | // algorithm: Quadtaric, etc.
70 | // properties: { x: {start:0, end:150}, y: {start:0, end:250, filter:Math.floor} }
71 | if (!_args.algorithm && _args.algo) { _args.algorithm = _args.algo; delete _args.algo; }
72 | if (!_args.properties && _args.props) { _args.properties = _args.props; delete _args.props; }
73 |
74 | for (var _key in _args) this[_key] = _args[_key];
75 |
76 | // linear shortcut
77 | if (!this.mode) this.mode = 'EaseIn';
78 | if (!this.algorithm) this.algorithm = 'Linear';
79 |
80 | this.require('target', 'duration', 'properties');
81 | // if (typeof(this.target) != 'object') return alert("Tween: Target is not an object");
82 | if (typeof(this.duration) != 'number') return alert("Tween: Duration is not a number");
83 | if (typeof(this.properties) != 'object') return alert("Tween: Properties is not an object");
84 |
85 | // setup properties
86 | for (var _key in this.properties) {
87 | var _prop = this.properties[_key];
88 | if (typeof(_prop) == 'number') _prop = this.properties[_key] = { end: _prop };
89 | if (typeof(_prop) != 'object') return alert("Tween: Property " + _key + " is not the correct format");
90 | if (typeof(_prop.start) == 'undefined') _prop.start = this.target[_key];
91 |
92 | if (_prop.start.toString().match(/^([\d\.]+)([a-zA-Z]+)$/) && !_prop.suffix) {
93 | _prop.start = RegExp.$1;
94 | _prop.suffix = RegExp.$3;
95 | _prop.end = _prop.end.toString().replace(/[^\d\.]+$/, '');
96 | }
97 | if ((typeof(_prop.start) != 'number') && _prop.start.toString().match(/^\d+\.\d+$/)) {
98 | _prop.start = parseFloat( _prop.start );
99 | }
100 | else if ((typeof(_prop.start) != 'number') && _prop.start.toString().match(/^\d+$/)) {
101 | _prop.start = parseInt( _prop.start, 10 );
102 | }
103 |
104 | if ((typeof(_prop.end) != 'number') && _prop.end.toString().match(/^\d+\.\d+$/)) {
105 | _prop.end = parseFloat( _prop.end );
106 | }
107 | else if ((typeof(_prop.end) != 'number') && _prop.end.toString().match(/^\d+$/)) {
108 | _prop.end = parseInt( _prop.end, 10 );
109 | }
110 |
111 | if (typeof(_prop.start) != 'number') return alert("Tween: Property " + _key + ": start is not a number");
112 | if (typeof(_prop.end) != 'number') return alert("Tween: Property " + _key + ": end is not a number");
113 | if (_prop.filter && (typeof(_prop.filter) != 'function')) return alert("Tween: Property " + _key + ": filter is not a function");
114 | }
115 | }
116 |
117 | Tween.prototype.destroyed = false;
118 | Tween.prototype.delay = 0;
119 |
120 | Tween.prototype.require = function() {
121 | // make sure required class members exist
122 | for (var _idx = 0, _len = arguments.length; _idx < _len; _idx++) {
123 | if (typeof(this[arguments[_idx]]) == 'undefined') {
124 | return alert("Tween: Missing required parameter: " + arguments[_idx]);
125 | }
126 | }
127 | return true;
128 | };
129 |
130 | Tween.prototype.logic = function(clock) {
131 | // abort if our target is destroyed
132 | // (and don't call onTweenComplete)
133 | if (this.target.destroyed) {
134 | this.destroyed = true;
135 | return;
136 | }
137 | if (this.delay > 0) {
138 | this.delay--;
139 | if (this.delay <= 0) this.start = clock;
140 | else return;
141 | }
142 | if (!this.start) this.start = clock;
143 |
144 | // calculate current progress
145 | this.amount = (clock - this.start) / this.duration;
146 | if (this.amount >= 1.0) {
147 | this.amount = 1.0;
148 | this.destroyed = true;
149 | }
150 |
151 | // animate obj properties
152 | for (var _key in this.properties) {
153 | var _prop = this.properties[_key];
154 | var _value = _prop.start + (ease(this.amount, this.mode, this.algorithm) * (_prop.end - _prop.start));
155 | if (_prop.filter) _value = _prop.filter( _value );
156 |
157 | /* console.log( "tweening: " + _key + ": " + serialize({
158 | id: this.id,
159 | amount: this.amount,
160 | start: _prop.start,
161 | end: _prop.end,
162 | mode: this.mode,
163 | algorithm: this.algorithm,
164 | value: _value
165 | }) ); */
166 |
167 | this.target[_key] = _prop.suffix ? ('' + _value + _prop.suffix) : _value;
168 | }
169 |
170 | // notify object that things are happening to it
171 | if (this.onTweenUpdate) this.onTweenUpdate(this);
172 | if (this.target.onTweenUpdate) this.target.onTweenUpdate(this);
173 |
174 | if (this.destroyed) {
175 | if (this.onTweenComplete) this.onTweenComplete(this);
176 | if (this.target.onTweenComplete) this.target.onTweenComplete(this);
177 | }
178 | };
179 |
180 | // Static Utility Function for tweening a single property to a single point in an animation
181 |
182 | function tweenFrame(_start, _end, _amount, _mode, _algo) {
183 | return _start + (ease(_amount, _mode, _algo) * (_end - _start));
184 | }
185 |
186 | //
187 | // Easing functions
188 | //
189 |
190 | var EaseAlgos = {
191 | Linear: function(_amount) { return _amount; },
192 | Quadratic: function(_amount) { return Math.pow(_amount, 2); },
193 | Cubic: function(_amount) { return Math.pow(_amount, 3); },
194 | Quartetic: function(_amount) { return Math.pow(_amount, 4); },
195 | Quintic: function(_amount) { return Math.pow(_amount, 5); },
196 | Sine: function(_amount) { return 1 - Math.sin((1 - _amount) * Math.PI / 2); },
197 | Circular: function(_amount) { return 1 - Math.sin(Math.acos(_amount)); }
198 | };
199 | var EaseModes = {
200 | EaseIn: function(_amount, _algo) { return EaseAlgos[_algo](_amount); },
201 | EaseOut: function(_amount, _algo) { return 1 - EaseAlgos[_algo](1 - _amount); },
202 | EaseInOut: function(_amount, _algo) {
203 | return (_amount <= 0.5) ? EaseAlgos[_algo](2 * _amount) / 2 : (2 - EaseAlgos[_algo](2 * (1 - _amount))) / 2;
204 | }
205 | };
206 | function ease(_amount, _mode, _algo) {
207 | return EaseModes[_mode]( _amount, _algo );
208 | }
209 |
--------------------------------------------------------------------------------
/Demos/clock.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antimatter15/jsgif/b46429c50a53d23b762d6ebb00b375aece3ed843/Demos/clock.gif
--------------------------------------------------------------------------------
/Demos/clock.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
130 |
--------------------------------------------------------------------------------
/Demos/converted_animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antimatter15/jsgif/b46429c50a53d23b762d6ebb00b375aece3ed843/Demos/converted_animation.gif
--------------------------------------------------------------------------------
/Demos/raw_canvas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antimatter15/jsgif/b46429c50a53d23b762d6ebb00b375aece3ed843/Demos/raw_canvas.png
--------------------------------------------------------------------------------
/Demos/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
80 |
--------------------------------------------------------------------------------
/GIFEncoder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This class lets you encode animated GIF files
3 | * Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
5 | * @author Thibault Imbert (AS3 version - bytearray.org)
6 | * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
7 | * @version 0.1 AS3 implementation
8 | */
9 |
10 | GIFEncoder = function() {
11 |
12 | for (var i = 0, chr = {}; i < 256; i++)
13 | chr[i] = String.fromCharCode(i);
14 |
15 | function ByteArray() {
16 | this.bin = [];
17 | }
18 |
19 | ByteArray.prototype.getData = function() {
20 | for (var v = '', l = this.bin.length, i = 0; i < l; i++)
21 | v += chr[this.bin[i]];
22 | return v;
23 | };
24 |
25 | ByteArray.prototype.writeByte = function(val) {
26 | this.bin.push(val);
27 | };
28 |
29 | ByteArray.prototype.writeUTFBytes = function(string) {
30 | for (var l = string.length, i = 0; i < l; i++)
31 | this.writeByte(string.charCodeAt(i));
32 | };
33 |
34 | ByteArray.prototype.writeBytes = function(array, offset, length) {
35 | for (var l = length || array.length, i = offset || 0; i < l; i++)
36 | this.writeByte(array[i]);
37 | };
38 |
39 | var exports = {};
40 | var width; // image size
41 | var height;
42 | var transparent = null; // transparent color if given
43 | var transIndex; // transparent index in color table
44 | var repeat = -1; // no repeat
45 | var delay = 0; // frame delay (hundredths)
46 | var started = false; // ready to output frames
47 | var out;
48 | var image; // current frame
49 | var pixels; // BGR byte array from frame
50 | var indexedPixels; // converted frame indexed to palette
51 | var colorDepth; // number of bit planes
52 | var colorTab; // RGB palette
53 | var usedEntry = []; // active palette entries
54 | var palSize = 7; // color table size (bits-1)
55 | var dispose = -1; // disposal code (-1 = use default)
56 | var closeStream = false; // close stream when finished
57 | var firstFrame = true;
58 | var sizeSet = false; // if false, get size from first frame
59 | var sample = 10; // default sample interval for quantizer
60 | var comment = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif
61 |
62 | /**
63 | * Sets the delay time between each frame, or changes it for subsequent frames
64 | * (applies to last frame added)
65 | * int delay time in milliseconds
66 | * @param ms
67 | */
68 |
69 | var setDelay = exports.setDelay = function setDelay(ms) {
70 | delay = Math.round(ms / 10);
71 | };
72 |
73 | /**
74 | * Sets the GIF frame disposal code for the last added frame and any
75 | *
76 | * subsequent frames. Default is 0 if no transparent color has been set,
77 | * otherwise 2.
78 | * @param code
79 | * int disposal code.
80 | */
81 |
82 | var setDispose = exports.setDispose = function setDispose(code) {
83 | if (code >= 0) dispose = code;
84 | };
85 |
86 | /**
87 | * Sets the number of times the set of GIF frames should be played. Default is
88 | * 1; 0 means play indefinitely. Must be invoked before the first image is
89 | * added.
90 | *
91 | * @param iter
92 | * int number of iterations.
93 | * @return
94 | */
95 |
96 | var setRepeat = exports.setRepeat = function setRepeat(iter) {
97 | if (iter >= 0) repeat = iter;
98 | };
99 |
100 | /**
101 | * Sets the transparent color for the last added frame and any subsequent
102 | * frames. Since all colors are subject to modification in the quantization
103 | * process, the color in the final palette for each frame closest to the given
104 | * color becomes the transparent color for that frame. May be set to null to
105 | * indicate no transparent color.
106 | * @param
107 | * Color to be treated as transparent on display.
108 | */
109 |
110 | var setTransparent = exports.setTransparent = function setTransparent(c) {
111 | transparent = c;
112 | };
113 |
114 |
115 | /**
116 | * Sets the comment for the block comment
117 | * @param
118 | * string to be insterted as comment
119 | */
120 |
121 | var setComment = exports.setComment = function setComment(c) {
122 | comment = c;
123 | };
124 |
125 |
126 |
127 | /**
128 | * The addFrame method takes an incoming BitmapData object to create each frames
129 | * @param
130 | * BitmapData object to be treated as a GIF's frame
131 | */
132 |
133 | var addFrame = exports.addFrame = function addFrame(im, is_imageData) {
134 |
135 | if ((im === null) || !started || out === null) {
136 | throw new Error("Please call start method before calling addFrame");
137 | }
138 |
139 | var ok = true;
140 |
141 | try {
142 | if (!is_imageData) {
143 | image = im.getImageData(0, 0, im.canvas.width, im.canvas.height).data;
144 | if (!sizeSet) setSize(im.canvas.width, im.canvas.height);
145 | } else {
146 | if(im instanceof ImageData) {
147 | image = im.data;
148 | if(!sizeset || width!=im.width || height!=im.height) {
149 | setSize(im.width,im.height);
150 | } else {
151 |
152 | }
153 | } else if(im instanceof Uint8ClampedArray) {
154 | if(im.length==(width*height*4)) {
155 | image=im;
156 | } else {
157 | console.log("Please set the correct size: ImageData length mismatch");
158 | ok=false;
159 | }
160 | } else {
161 | console.log("Please provide correct input");
162 | ok=false;
163 | }
164 | }
165 | getImagePixels(); // convert to correct format if necessary
166 | analyzePixels(); // build color table & map pixels
167 |
168 | if (firstFrame) {
169 | writeLSD(); // logical screen descriptior
170 | writePalette(); // global color table
171 | if (repeat >= 0) {
172 | // use NS app extension to indicate reps
173 | writeNetscapeExt();
174 | }
175 | }
176 |
177 | writeGraphicCtrlExt(); // write graphic control extension
178 | if (comment !== '') {
179 | writeCommentExt(); // write comment extension
180 | }
181 | writeImageDesc(); // image descriptor
182 | if (!firstFrame) writePalette(); // local color table
183 | writePixels(); // encode and write pixel data
184 | firstFrame = false;
185 | } catch (e) {
186 | ok = false;
187 | }
188 |
189 | return ok;
190 | };
191 |
192 | /**
193 | * @description: Downloads the encoded gif with the given name
194 | * No need of any conversion from the stream data (out) to base64
195 | * Solves the issue of large file sizes when there are more frames
196 | * and does not involve in creation of any temporary data in the process
197 | * so no wastage of memory, and speeds up the process of downloading
198 | * to just calling this function.
199 | * @parameter {String} filename filename used for downloading the gif
200 | */
201 |
202 | var download = exports.download = function download(filename) {
203 | if(out===null || closeStream==false) {
204 | console.log("Please call start method and add frames and call finish method before calling download");
205 | } else {
206 | filename= filename !== undefined ? ( filename.endsWith(".gif")? filename: filename+".gif" ): "download.gif";
207 | var templink = document.createElement("a");
208 | templink.download=filename;
209 | templink.href= URL.createObjectURL(new Blob([new Uint8Array(out.bin)], {type : "image/gif" } ));
210 | templink.click();
211 | }
212 | }
213 |
214 | /**
215 | * Adds final trailer to the GIF stream, if you don't call the finish method
216 | * the GIF stream will not be valid.
217 | */
218 |
219 | var finish = exports.finish = function finish() {
220 |
221 | if (!started) return false;
222 |
223 | var ok = true;
224 | started = false;
225 |
226 | try {
227 | out.writeByte(0x3b); // gif trailer
228 | closeStream=true;
229 | } catch (e) {
230 | ok = false;
231 | }
232 |
233 | return ok;
234 | };
235 |
236 | /**
237 | * Resets some members so that a new stream can be started.
238 | * This method is actually called by the start method
239 | */
240 |
241 | var reset = function reset() {
242 |
243 | // reset for subsequent use
244 | transIndex = 0;
245 | image = null;
246 | pixels = null;
247 | indexedPixels = null;
248 | colorTab = null;
249 | closeStream = false;
250 | firstFrame = true;
251 | };
252 |
253 | /**
254 | * * Sets frame rate in frames per second. Equivalent to
255 | * setDelay(1000/fps)
.
256 | * @param fps
257 | * float frame rate (frames per second)
258 | */
259 |
260 | var setFrameRate = exports.setFrameRate = function setFrameRate(fps) {
261 | if (fps != 0xf) delay = Math.round(100 / fps);
262 | };
263 |
264 | /**
265 | * Sets quality of color quantization (conversion of images to the maximum 256
266 | * colors allowed by the GIF specification). Lower values (minimum = 1)
267 | * produce better colors, but slow processing significantly. 10 is the
268 | * default, and produces good color mapping at reasonable speeds. Values
269 | * greater than 20 do not yield significant improvements in speed.
270 | * @param quality
271 | * int greater than 0.
272 | * @return
273 | */
274 |
275 | var setQuality = exports.setQuality = function setQuality(quality) {
276 | if (quality < 1) quality = 1;
277 | sample = quality;
278 | };
279 |
280 | /**
281 | * Sets the GIF frame size. The default size is the size of the first frame
282 | * added if this method is not invoked.
283 | * @param w
284 | * int frame width.
285 | * @param h
286 | * int frame width.
287 | */
288 |
289 | var setSize = exports.setSize = function setSize(w, h) {
290 |
291 | if (started && !firstFrame) return;
292 | width = w;
293 | height = h;
294 | if (width < 1) width = 320;
295 | if (height < 1) height = 240;
296 | sizeSet = true;
297 | };
298 |
299 | /**
300 | * Initiates GIF file creation on the given stream.
301 | * @param os
302 | * OutputStream on which GIF images are written.
303 | * @return false if initial write failed.
304 | */
305 |
306 | var start = exports.start = function start() {
307 |
308 | reset();
309 | var ok = true;
310 | closeStream = false;
311 | out = new ByteArray();
312 | try {
313 | out.writeUTFBytes("GIF89a"); // header
314 | } catch (e) {
315 | ok = false;
316 | }
317 |
318 | return started = ok;
319 | };
320 |
321 | var cont = exports.cont = function cont() {
322 |
323 | reset();
324 | var ok = true;
325 | closeStream = false;
326 | out = new ByteArray();
327 |
328 | return started = ok;
329 | };
330 |
331 | /**
332 | * Analyzes image colors and creates color map.
333 | */
334 |
335 | var analyzePixels = function analyzePixels() {
336 |
337 | var len = pixels.length;
338 | var nPix = len / 3;
339 | indexedPixels = [];
340 | var nq = new NeuQuant(pixels, len, sample);
341 |
342 | // initialize quantizer
343 | colorTab = nq.process(); // create reduced palette
344 |
345 | // map image pixels to new palette
346 | var k = 0;
347 | for (var j = 0; j < nPix; j++) {
348 | var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
349 | usedEntry[index] = true;
350 | indexedPixels[j] = index;
351 | }
352 |
353 | pixels = null;
354 | colorDepth = 8;
355 | palSize = 7;
356 |
357 | // get closest match to transparent color if specified
358 | if (transparent !== null) {
359 | transIndex = findClosest(transparent);
360 | }
361 | };
362 |
363 | /**
364 | * Returns index of palette color closest to c
365 | */
366 |
367 | var findClosest = function findClosest(c) {
368 |
369 | if (colorTab === null) return -1;
370 | var r = (c & 0xFF0000) >> 16;
371 | var g = (c & 0x00FF00) >> 8;
372 | var b = (c & 0x0000FF);
373 | var minpos = 0;
374 | var dmin = 256 * 256 * 256;
375 | var len = colorTab.length;
376 |
377 | for (var i = 0; i < len;) {
378 | var dr = r - (colorTab[i++] & 0xff);
379 | var dg = g - (colorTab[i++] & 0xff);
380 | var db = b - (colorTab[i] & 0xff);
381 | var d = dr * dr + dg * dg + db * db;
382 | var index = i / 3;
383 | if (usedEntry[index] && (d < dmin)) {
384 | dmin = d;
385 | minpos = index;
386 | }
387 | i++;
388 | }
389 | return minpos;
390 | };
391 |
392 | /**
393 | * Extracts image pixels into byte array "pixels
394 | */
395 |
396 | var getImagePixels = function getImagePixels() {
397 | var w = width;
398 | var h = height;
399 | pixels = [];
400 | var data = image;
401 | var count = 0;
402 |
403 | for (var i = 0; i < h; i++) {
404 |
405 | for (var j = 0; j < w; j++) {
406 |
407 | var b = (i * w * 4) + j * 4;
408 | pixels[count++] = data[b];
409 | pixels[count++] = data[b + 1];
410 | pixels[count++] = data[b + 2];
411 |
412 | }
413 |
414 | }
415 | };
416 |
417 | /**
418 | * Writes Graphic Control Extension
419 | */
420 |
421 | var writeGraphicCtrlExt = function writeGraphicCtrlExt() {
422 | out.writeByte(0x21); // extension introducer
423 | out.writeByte(0xf9); // GCE label
424 | out.writeByte(4); // data block size
425 | var transp;
426 | var disp;
427 | if (transparent === null) {
428 | transp = 0;
429 | disp = 0; // dispose = no action
430 | } else {
431 | transp = 1;
432 | disp = 2; // force clear if using transparent color
433 | }
434 | if (dispose >= 0) {
435 | disp = dispose & 7; // user override
436 | }
437 | disp <<= 2;
438 | // packed fields
439 | out.writeByte(0 | // 1:3 reserved
440 | disp | // 4:6 disposal
441 | 0 | // 7 user input - 0 = none
442 | transp); // 8 transparency flag
443 |
444 | WriteShort(delay); // delay x 1/100 sec
445 | out.writeByte(transIndex); // transparent color index
446 | out.writeByte(0); // block terminator
447 | };
448 |
449 | /**
450 | * Writes Comment Extention
451 | */
452 |
453 | var writeCommentExt = function writeCommentExt() {
454 | out.writeByte(0x21); // extension introducer
455 | out.writeByte(0xfe); // comment label
456 | out.writeByte(comment.length); // Block Size (s)
457 | out.writeUTFBytes(comment);
458 | out.writeByte(0); // block terminator
459 | };
460 |
461 |
462 | /**
463 | * Writes Image Descriptor
464 | */
465 |
466 | var writeImageDesc = function writeImageDesc() {
467 |
468 | out.writeByte(0x2c); // image separator
469 | WriteShort(0); // image position x,y = 0,0
470 | WriteShort(0);
471 | WriteShort(width); // image size
472 | WriteShort(height);
473 |
474 | // packed fields
475 | if (firstFrame) {
476 | // no LCT - GCT is used for first (or only) frame
477 | out.writeByte(0);
478 | } else {
479 | // specify normal LCT
480 | out.writeByte(0x80 | // 1 local color table 1=yes
481 | 0 | // 2 interlace - 0=no
482 | 0 | // 3 sorted - 0=no
483 | 0 | // 4-5 reserved
484 | palSize); // 6-8 size of color table
485 | }
486 | };
487 |
488 | /**
489 | * Writes Logical Screen Descriptor
490 | */
491 |
492 | var writeLSD = function writeLSD() {
493 |
494 | // logical screen size
495 | WriteShort(width);
496 | WriteShort(height);
497 | // packed fields
498 | out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used)
499 | 0x70 | // 2-4 : color resolution = 7
500 | 0x00 | // 5 : gct sort flag = 0
501 | palSize)); // 6-8 : gct size
502 |
503 | out.writeByte(0); // background color index
504 | out.writeByte(0); // pixel aspect ratio - assume 1:1
505 | };
506 |
507 | /**
508 | * Writes Netscape application extension to define repeat count.
509 | */
510 |
511 | var writeNetscapeExt = function writeNetscapeExt() {
512 | out.writeByte(0x21); // extension introducer
513 | out.writeByte(0xff); // app extension label
514 | out.writeByte(11); // block size
515 | out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
516 | out.writeByte(3); // sub-block size
517 | out.writeByte(1); // loop sub-block id
518 | WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
519 | out.writeByte(0); // block terminator
520 | };
521 |
522 | /**
523 | * Writes color table
524 | */
525 |
526 | var writePalette = function writePalette() {
527 | out.writeBytes(colorTab);
528 | var n = (3 * 256) - colorTab.length;
529 | for (var i = 0; i < n; i++) out.writeByte(0);
530 | };
531 |
532 | var WriteShort = function WriteShort(pValue) {
533 | out.writeByte(pValue & 0xFF);
534 | out.writeByte((pValue >> 8) & 0xFF);
535 | };
536 |
537 | /**
538 | * Encodes and writes pixel data
539 | */
540 |
541 | var writePixels = function writePixels() {
542 | var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
543 | myencoder.encode(out);
544 | };
545 |
546 | /**
547 | * Retrieves the GIF stream
548 | */
549 |
550 | var stream = exports.stream = function stream() {
551 | return out;
552 | };
553 |
554 | var setProperties = exports.setProperties = function setProperties(has_start, is_first) {
555 | started = has_start;
556 | firstFrame = is_first;
557 | };
558 |
559 | return exports;
560 |
561 | };
562 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2014 Kevin Kwok
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/LZWEncoder.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This class handles LZW encoding
3 | * Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
5 | * @author Thibault Imbert (AS3 version - bytearray.org)
6 | * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
7 | * @version 0.1 AS3 implementation
8 | */
9 |
10 | LZWEncoder = function() {
11 |
12 | var exports = {};
13 | var EOF = -1;
14 | var imgW;
15 | var imgH;
16 | var pixAry;
17 | var initCodeSize;
18 | var remaining;
19 | var curPixel;
20 |
21 | // GIFCOMPR.C - GIF Image compression routines
22 | // Lempel-Ziv compression based on 'compress'. GIF modifications by
23 | // David Rowley (mgardi@watdcsu.waterloo.edu)
24 | // General DEFINEs
25 |
26 | var BITS = 12;
27 | var HSIZE = 5003; // 80% occupancy
28 |
29 | // GIF Image compression - modified 'compress'
30 | // Based on: compress.c - File compression ala IEEE Computer, June 1984.
31 | // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
32 | // Jim McKie (decvax!mcvax!jim)
33 | // Steve Davies (decvax!vax135!petsd!peora!srd)
34 | // Ken Turkowski (decvax!decwrl!turtlevax!ken)
35 | // James A. Woods (decvax!ihnp4!ames!jaw)
36 | // Joe Orost (decvax!vax135!petsd!joe)
37 |
38 | var n_bits; // number of bits/code
39 | var maxbits = BITS; // user settable max # bits/code
40 | var maxcode; // maximum code, given n_bits
41 | var maxmaxcode = 1 << BITS; // should NEVER generate this code
42 | var htab = [];
43 | var codetab = [];
44 | var hsize = HSIZE; // for dynamic table sizing
45 | var free_ent = 0; // first unused entry
46 |
47 | // block compression parameters -- after all codes are used up,
48 | // and compression rate changes, start over.
49 |
50 | var clear_flg = false;
51 |
52 | // Algorithm: use open addressing double hashing (no chaining) on the
53 | // prefix code / next character combination. We do a variant of Knuth's
54 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
55 | // secondary probe. Here, the modular division first probe is gives way
56 | // to a faster exclusive-or manipulation. Also do block compression with
57 | // an adaptive reset, whereby the code table is cleared when the compression
58 | // ratio decreases, but after the table fills. The variable-length output
59 | // codes are re-sized at this point, and a special CLEAR code is generated
60 | // for the decompressor. Late addition: construct the table according to
61 | // file size for noticeable speed improvement on small files. Please direct
62 | // questions about this implementation to ames!jaw.
63 |
64 | var g_init_bits;
65 | var ClearCode;
66 | var EOFCode;
67 |
68 | // output
69 | // Output the given code.
70 | // Inputs:
71 | // code: A n_bits-bit integer. If == -1, then EOF. This assumes
72 | // that n_bits =< wordsize - 1.
73 | // Outputs:
74 | // Outputs code to the file.
75 | // Assumptions:
76 | // Chars are 8 bits long.
77 | // Algorithm:
78 | // Maintain a BITS character long buffer (so that 8 codes will
79 | // fit in it exactly). Use the VAX insv instruction to insert each
80 | // code in turn. When the buffer fills up empty it and start over.
81 |
82 | var cur_accum = 0;
83 | var cur_bits = 0;
84 | var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF];
85 |
86 | // Number of characters so far in this 'packet'
87 | var a_count;
88 |
89 | // Define the storage for the packet accumulator
90 | var accum = [];
91 |
92 | var LZWEncoder = exports.LZWEncoder = function LZWEncoder(width, height, pixels, color_depth) {
93 | imgW = width;
94 | imgH = height;
95 | pixAry = pixels;
96 | initCodeSize = Math.max(2, color_depth);
97 | };
98 |
99 | // Add a character to the end of the current packet, and if it is 254
100 | // characters, flush the packet to disk.
101 | var char_out = function char_out(c, outs) {
102 | accum[a_count++] = c;
103 | if (a_count >= 254) flush_char(outs);
104 | };
105 |
106 | // Clear out the hash table
107 | // table clear for block compress
108 |
109 | var cl_block = function cl_block(outs) {
110 | cl_hash(hsize);
111 | free_ent = ClearCode + 2;
112 | clear_flg = true;
113 | output(ClearCode, outs);
114 | };
115 |
116 | // reset code table
117 | var cl_hash = function cl_hash(hsize) {
118 | for (var i = 0; i < hsize; ++i) htab[i] = -1;
119 | };
120 |
121 | var compress = exports.compress = function compress(init_bits, outs) {
122 |
123 | var fcode;
124 | var i; /* = 0 */
125 | var c;
126 | var ent;
127 | var disp;
128 | var hsize_reg;
129 | var hshift;
130 |
131 | // Set up the globals: g_init_bits - initial number of bits
132 | g_init_bits = init_bits;
133 |
134 | // Set up the necessary values
135 | clear_flg = false;
136 | n_bits = g_init_bits;
137 | maxcode = MAXCODE(n_bits);
138 |
139 | ClearCode = 1 << (init_bits - 1);
140 | EOFCode = ClearCode + 1;
141 | free_ent = ClearCode + 2;
142 |
143 | a_count = 0; // clear packet
144 |
145 | ent = nextPixel();
146 |
147 | hshift = 0;
148 | for (fcode = hsize; fcode < 65536; fcode *= 2)
149 | ++hshift;
150 | hshift = 8 - hshift; // set hash code range bound
151 |
152 | hsize_reg = hsize;
153 | cl_hash(hsize_reg); // clear hash table
154 |
155 | output(ClearCode, outs);
156 |
157 | outer_loop: while ((c = nextPixel()) != EOF) {
158 | fcode = (c << maxbits) + ent;
159 | i = (c << hshift) ^ ent; // xor hashing
160 |
161 | if (htab[i] == fcode) {
162 | ent = codetab[i];
163 | continue;
164 | }
165 |
166 | else if (htab[i] >= 0) { // non-empty slot
167 |
168 | disp = hsize_reg - i; // secondary hash (after G. Knott)
169 | if (i === 0) disp = 1;
170 |
171 | do {
172 | if ((i -= disp) < 0)
173 | i += hsize_reg;
174 |
175 | if (htab[i] == fcode) {
176 | ent = codetab[i];
177 | continue outer_loop;
178 | }
179 | } while (htab[i] >= 0);
180 | }
181 |
182 | output(ent, outs);
183 | ent = c;
184 | if (free_ent < maxmaxcode) {
185 | codetab[i] = free_ent++; // code -> hashtable
186 | htab[i] = fcode;
187 | }
188 | else cl_block(outs);
189 | }
190 |
191 | // Put out the final code.
192 | output(ent, outs);
193 | output(EOFCode, outs);
194 | };
195 |
196 | // ----------------------------------------------------------------------------
197 | var encode = exports.encode = function encode(os) {
198 | os.writeByte(initCodeSize); // write "initial code size" byte
199 | remaining = imgW * imgH; // reset navigation variables
200 | curPixel = 0;
201 | compress(initCodeSize + 1, os); // compress and write the pixel data
202 | os.writeByte(0); // write block terminator
203 | };
204 |
205 | // Flush the packet to disk, and reset the accumulator
206 | var flush_char = function flush_char(outs) {
207 | if (a_count > 0) {
208 | outs.writeByte(a_count);
209 | outs.writeBytes(accum, 0, a_count);
210 | a_count = 0;
211 | }
212 | };
213 |
214 | var MAXCODE = function MAXCODE(n_bits) {
215 | return (1 << n_bits) - 1;
216 | };
217 |
218 | // ----------------------------------------------------------------------------
219 | // Return the next pixel from the image
220 | // ----------------------------------------------------------------------------
221 |
222 | var nextPixel = function nextPixel() {
223 | if (remaining === 0) return EOF;
224 | --remaining;
225 | var pix = pixAry[curPixel++];
226 | return pix & 0xff;
227 | };
228 |
229 | var output = function output(code, outs) {
230 |
231 | cur_accum &= masks[cur_bits];
232 |
233 | if (cur_bits > 0) cur_accum |= (code << cur_bits);
234 | else cur_accum = code;
235 |
236 | cur_bits += n_bits;
237 |
238 | while (cur_bits >= 8) {
239 | char_out((cur_accum & 0xff), outs);
240 | cur_accum >>= 8;
241 | cur_bits -= 8;
242 | }
243 |
244 | // If the next entry is going to be too big for the code size,
245 | // then increase it, if possible.
246 |
247 | if (free_ent > maxcode || clear_flg) {
248 |
249 | if (clear_flg) {
250 |
251 | maxcode = MAXCODE(n_bits = g_init_bits);
252 | clear_flg = false;
253 |
254 | } else {
255 |
256 | ++n_bits;
257 | if (n_bits == maxbits) maxcode = maxmaxcode;
258 | else maxcode = MAXCODE(n_bits);
259 | }
260 | }
261 |
262 | if (code == EOFCode) {
263 |
264 | // At EOF, write the rest of the buffer.
265 | while (cur_bits > 0) {
266 | char_out((cur_accum & 0xff), outs);
267 | cur_accum >>= 8;
268 | cur_bits -= 8;
269 | }
270 |
271 | flush_char(outs);
272 | }
273 | };
274 |
275 | LZWEncoder.apply(this, arguments);
276 | return exports;
277 | };
278 |
--------------------------------------------------------------------------------
/NeuQuant.js:
--------------------------------------------------------------------------------
1 | /*
2 | * NeuQuant Neural-Net Quantization Algorithm
3 | * ------------------------------------------
4 | *
5 | * Copyright (c) 1994 Anthony Dekker
6 | *
7 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
8 | * "Kohonen neural networks for optimal colour quantization" in "Network:
9 | * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
10 | * the algorithm.
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 in
15 | * this software and documentation files (the "Software"), including without
16 | * limitation the rights to use, copy, modify, merge, publish, distribute,
17 | * sublicense, and/or sell copies of the Software, and to permit persons who
18 | * receive copies from any such party to do so, with the only requirement being
19 | * that this copyright notice remain intact.
20 | */
21 |
22 | /*
23 | * This class handles Neural-Net quantization algorithm
24 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
25 | * @author Thibault Imbert (AS3 version - bytearray.org)
26 | * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
27 | * @version 0.1 AS3 implementation
28 | */
29 |
30 | NeuQuant = function() {
31 |
32 | var exports = {};
33 | var netsize = 256; /* number of colours used */
34 |
35 | /* four primes near 500 - assume no image has a length so large */
36 | /* that it is divisible by all four primes */
37 |
38 | var prime1 = 499;
39 | var prime2 = 491;
40 | var prime3 = 487;
41 | var prime4 = 503;
42 | var minpicturebytes = (3 * prime4); /* minimum size for input image */
43 |
44 | /*
45 | * Program Skeleton ---------------- [select samplefac in range 1..30] [read
46 | * image from input file] pic = (unsigned char*) malloc(3*width*height);
47 | * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
48 | * image header, using writecolourmap(f)] inxbuild(); write output image using
49 | * inxsearch(b,g,r)
50 | */
51 |
52 | /*
53 | * Network Definitions -------------------
54 | */
55 |
56 | var maxnetpos = (netsize - 1);
57 | var netbiasshift = 4; /* bias for colour values */
58 | var ncycles = 100; /* no. of learning cycles */
59 |
60 | /* defs for freq and bias */
61 | var intbiasshift = 16; /* bias for fractions */
62 | var intbias = (1 << intbiasshift);
63 | var gammashift = 10; /* gamma = 1024 */
64 | var gamma = (1 << gammashift);
65 | var betashift = 10;
66 | var beta = (intbias >> betashift); /* beta = 1/1024 */
67 | var betagamma = (intbias << (gammashift - betashift));
68 |
69 | /* defs for decreasing radius factor */
70 | var initrad = (netsize >> 3); /* for 256 cols, radius starts */
71 | var radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
72 | var radiusbias = (1 << radiusbiasshift);
73 | var initradius = (initrad * radiusbias); /* and decreases by a */
74 | var radiusdec = 30; /* factor of 1/30 each cycle */
75 |
76 | /* defs for decreasing alpha factor */
77 | var alphabiasshift = 10; /* alpha starts at 1.0 */
78 | var initalpha = (1 << alphabiasshift);
79 | var alphadec; /* biased by 10 bits */
80 |
81 | /* radbias and alpharadbias used for radpower calculation */
82 | var radbiasshift = 8;
83 | var radbias = (1 << radbiasshift);
84 | var alpharadbshift = (alphabiasshift + radbiasshift);
85 | var alpharadbias = (1 << alpharadbshift);
86 |
87 | /*
88 | * Types and Global Variables --------------------------
89 | */
90 |
91 | var thepicture; /* the input image itself */
92 | var lengthcount; /* lengthcount = H*W*3 */
93 | var samplefac; /* sampling factor 1..30 */
94 |
95 | // typedef int pixel[4]; /* BGRc */
96 | var network; /* the network itself - [netsize][4] */
97 | var netindex = [];
98 |
99 | /* for network lookup - really 256 */
100 | var bias = [];
101 |
102 | /* bias and freq arrays for learning */
103 | var freq = [];
104 | var radpower = [];
105 |
106 | var NeuQuant = exports.NeuQuant = function NeuQuant(thepic, len, sample) {
107 |
108 | var i;
109 | var p;
110 |
111 | thepicture = thepic;
112 | lengthcount = len;
113 | samplefac = sample;
114 |
115 | network = new Array(netsize);
116 |
117 | for (i = 0; i < netsize; i++) {
118 |
119 | network[i] = new Array(4);
120 | p = network[i];
121 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
122 | freq[i] = intbias / netsize; /* 1/netsize */
123 | bias[i] = 0;
124 | }
125 | };
126 |
127 | var colorMap = function colorMap() {
128 |
129 | var map = [];
130 | var index = new Array(netsize);
131 |
132 | for (var i = 0; i < netsize; i++)
133 | index[network[i][3]] = i;
134 |
135 | var k = 0;
136 | for (var l = 0; l < netsize; l++) {
137 | var j = index[l];
138 | map[k++] = (network[j][0]);
139 | map[k++] = (network[j][1]);
140 | map[k++] = (network[j][2]);
141 | }
142 |
143 | return map;
144 | };
145 |
146 | /*
147 | * Insertion sort of network and building of netindex[0..255] (to do after
148 | * unbias)
149 | * -------------------------------------------------------------------------------
150 | */
151 |
152 | var inxbuild = function inxbuild() {
153 |
154 | var i;
155 | var j;
156 | var smallpos;
157 | var smallval;
158 | var p;
159 | var q;
160 | var previouscol;
161 | var startpos;
162 |
163 | previouscol = 0;
164 | startpos = 0;
165 | for (i = 0; i < netsize; i++) {
166 |
167 | p = network[i];
168 | smallpos = i;
169 | smallval = p[1]; /* index on g */
170 |
171 | /* find smallest in i..netsize-1 */
172 | for (j = i + 1; j < netsize; j++) {
173 |
174 | q = network[j];
175 | if (q[1] < smallval) { /* index on g */
176 | smallpos = j;
177 | smallval = q[1]; /* index on g */
178 | }
179 | }
180 | q = network[smallpos];
181 |
182 | /* swap p (i) and q (smallpos) entries */
183 | if (i != smallpos) {
184 | j = q[0];
185 | q[0] = p[0];
186 | p[0] = j;
187 | j = q[1];
188 | q[1] = p[1];
189 | p[1] = j;
190 | j = q[2];
191 | q[2] = p[2];
192 | p[2] = j;
193 | j = q[3];
194 | q[3] = p[3];
195 | p[3] = j;
196 | }
197 |
198 | /* smallval entry is now in position i */
199 |
200 | if (smallval != previouscol) {
201 |
202 | netindex[previouscol] = (startpos + i) >> 1;
203 |
204 | for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
205 |
206 | previouscol = smallval;
207 | startpos = i;
208 | }
209 | }
210 |
211 | netindex[previouscol] = (startpos + maxnetpos) >> 1;
212 | for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */
213 | };
214 |
215 | /*
216 | * Main Learning Loop ------------------
217 | */
218 |
219 | var learn = function learn() {
220 |
221 | var i;
222 | var j;
223 | var b;
224 | var g;
225 | var r;
226 | var radius;
227 | var rad;
228 | var alpha;
229 | var step;
230 | var delta;
231 | var samplepixels;
232 | var p;
233 | var pix;
234 | var lim;
235 |
236 | if (lengthcount < minpicturebytes) samplefac = 1;
237 |
238 | alphadec = 30 + ((samplefac - 1) / 3);
239 | p = thepicture;
240 | pix = 0;
241 | lim = lengthcount;
242 | samplepixels = lengthcount / (3 * samplefac);
243 | delta = (samplepixels / ncycles) | 0;
244 | alpha = initalpha;
245 | radius = initradius;
246 |
247 | rad = radius >> radiusbiasshift;
248 | if (rad <= 1) rad = 0;
249 |
250 | for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
251 |
252 | if (lengthcount < minpicturebytes) step = 3;
253 |
254 | else if ((lengthcount % prime1) !== 0) step = 3 * prime1;
255 |
256 | else {
257 |
258 | if ((lengthcount % prime2) !== 0) step = 3 * prime2;
259 | else {
260 | if ((lengthcount % prime3) !== 0) step = 3 * prime3;
261 | else step = 3 * prime4;
262 | }
263 | }
264 |
265 | i = 0;
266 | while (i < samplepixels) {
267 |
268 | b = (p[pix + 0] & 0xff) << netbiasshift;
269 | g = (p[pix + 1] & 0xff) << netbiasshift;
270 | r = (p[pix + 2] & 0xff) << netbiasshift;
271 | j = contest(b, g, r);
272 |
273 | altersingle(alpha, j, b, g, r);
274 | if (rad !== 0) alterneigh(rad, j, b, g, r); /* alter neighbours */
275 |
276 | pix += step;
277 | if (pix >= lim) pix -= lengthcount;
278 |
279 | i++;
280 |
281 | if (delta === 0) delta = 1;
282 |
283 | if (i % delta === 0) {
284 | alpha -= alpha / alphadec;
285 | radius -= radius / radiusdec;
286 | rad = radius >> radiusbiasshift;
287 |
288 | if (rad <= 1) rad = 0;
289 |
290 | for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
291 | }
292 | }
293 | };
294 |
295 | /*
296 | ** Search for BGR values 0..255 (after net is unbiased) and return colour
297 | * index
298 | * ----------------------------------------------------------------------------
299 | */
300 |
301 | var map = exports.map = function map(b, g, r) {
302 |
303 | var i;
304 | var j;
305 | var dist;
306 | var a;
307 | var bestd;
308 | var p;
309 | var best;
310 |
311 | bestd = 1000; /* biggest possible dist is 256*3 */
312 | best = -1;
313 | i = netindex[g]; /* index on g */
314 | j = i - 1; /* start at netindex[g] and work outwards */
315 |
316 | while ((i < netsize) || (j >= 0)) {
317 |
318 | if (i < netsize) {
319 | p = network[i];
320 | dist = p[1] - g; /* inx key */
321 |
322 | if (dist >= bestd) i = netsize; /* stop iter */
323 |
324 | else {
325 |
326 | i++;
327 | if (dist < 0) dist = -dist;
328 | a = p[0] - b;
329 | if (a < 0) a = -a;
330 | dist += a;
331 |
332 | if (dist < bestd) {
333 | a = p[2] - r;
334 | if (a < 0) a = -a;
335 | dist += a;
336 |
337 | if (dist < bestd) {
338 | bestd = dist;
339 | best = p[3];
340 | }
341 | }
342 | }
343 | }
344 |
345 | if (j >= 0) {
346 |
347 | p = network[j];
348 | dist = g - p[1]; /* inx key - reverse dif */
349 |
350 | if (dist >= bestd) j = -1; /* stop iter */
351 |
352 | else {
353 |
354 | j--;
355 | if (dist < 0) dist = -dist;
356 | a = p[0] - b;
357 | if (a < 0) a = -a;
358 | dist += a;
359 |
360 | if (dist < bestd) {
361 | a = p[2] - r;
362 | if (a < 0) a = -a;
363 | dist += a;
364 | if (dist < bestd) {
365 | bestd = dist;
366 | best = p[3];
367 | }
368 | }
369 | }
370 | }
371 | }
372 |
373 | return (best);
374 | };
375 |
376 | var process = exports.process = function process() {
377 | learn();
378 | unbiasnet();
379 | inxbuild();
380 | return colorMap();
381 | };
382 |
383 | /*
384 | * Unbias network to give byte values 0..255 and record position i to prepare
385 | * for sort
386 | * -----------------------------------------------------------------------------------
387 | */
388 |
389 | var unbiasnet = function unbiasnet() {
390 |
391 | var i;
392 | var j;
393 |
394 | for (i = 0; i < netsize; i++) {
395 | network[i][0] >>= netbiasshift;
396 | network[i][1] >>= netbiasshift;
397 | network[i][2] >>= netbiasshift;
398 | network[i][3] = i; /* record colour no */
399 | }
400 | };
401 |
402 | /*
403 | * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
404 | * radpower[|i-j|]
405 | * ---------------------------------------------------------------------------------
406 | */
407 |
408 | var alterneigh = function alterneigh(rad, i, b, g, r) {
409 |
410 | var j;
411 | var k;
412 | var lo;
413 | var hi;
414 | var a;
415 | var m;
416 | var p;
417 |
418 | lo = i - rad;
419 | if (lo < -1) lo = -1;
420 |
421 | hi = i + rad;
422 | if (hi > netsize) hi = netsize;
423 |
424 | j = i + 1;
425 | k = i - 1;
426 | m = 1;
427 |
428 | while ((j < hi) || (k > lo)) {
429 | a = radpower[m++];
430 |
431 | if (j < hi) {
432 | p = network[j++];
433 |
434 | try {
435 | p[0] -= (a * (p[0] - b)) / alpharadbias;
436 | p[1] -= (a * (p[1] - g)) / alpharadbias;
437 | p[2] -= (a * (p[2] - r)) / alpharadbias;
438 | } catch (e) {} // prevents 1.3 miscompilation
439 | }
440 |
441 | if (k > lo) {
442 | p = network[k--];
443 |
444 | try {
445 | p[0] -= (a * (p[0] - b)) / alpharadbias;
446 | p[1] -= (a * (p[1] - g)) / alpharadbias;
447 | p[2] -= (a * (p[2] - r)) / alpharadbias;
448 | } catch (e) {}
449 | }
450 | }
451 | };
452 |
453 | /*
454 | * Move neuron i towards biased (b,g,r) by factor alpha
455 | * ----------------------------------------------------
456 | */
457 |
458 | var altersingle = function altersingle(alpha, i, b, g, r) {
459 |
460 | /* alter hit neuron */
461 | var n = network[i];
462 | n[0] -= (alpha * (n[0] - b)) / initalpha;
463 | n[1] -= (alpha * (n[1] - g)) / initalpha;
464 | n[2] -= (alpha * (n[2] - r)) / initalpha;
465 | };
466 |
467 | /*
468 | * Search for biased BGR values ----------------------------
469 | */
470 |
471 | var contest = function contest(b, g, r) {
472 |
473 | /* finds closest neuron (min dist) and updates freq */
474 | /* finds best neuron (min dist-bias) and returns position */
475 | /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
476 | /* bias[i] = gamma*((1/netsize)-freq[i]) */
477 |
478 | var i;
479 | var dist;
480 | var a;
481 | var biasdist;
482 | var betafreq;
483 | var bestpos;
484 | var bestbiaspos;
485 | var bestd;
486 | var bestbiasd;
487 | var n;
488 |
489 | bestd = ~ (1 << 31);
490 | bestbiasd = bestd;
491 | bestpos = -1;
492 | bestbiaspos = bestpos;
493 |
494 | for (i = 0; i < netsize; i++) {
495 | n = network[i];
496 | dist = n[0] - b;
497 | if (dist < 0) dist = -dist;
498 | a = n[1] - g;
499 | if (a < 0) a = -a;
500 | dist += a;
501 | a = n[2] - r;
502 | if (a < 0) a = -a;
503 | dist += a;
504 |
505 | if (dist < bestd) {
506 | bestd = dist;
507 | bestpos = i;
508 | }
509 |
510 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
511 |
512 | if (biasdist < bestbiasd) {
513 | bestbiasd = biasdist;
514 | bestbiaspos = i;
515 | }
516 |
517 | betafreq = (freq[i] >> betashift);
518 | freq[i] -= betafreq;
519 | bias[i] += (betafreq << gammashift);
520 | }
521 |
522 | freq[bestpos] += beta;
523 | bias[bestpos] -= betagamma;
524 | return (bestbiaspos);
525 | };
526 |
527 | NeuQuant.apply(this, arguments);
528 | return exports;
529 | };
530 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Pure JavaScript HTML5