├── 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 += ''; 51 | html += ''; 52 | html += ''; 53 | html += '
Current FPS:' + this.current + '
Average FPS:' + this.average + '
Total Frames:' + this.totalFrames + '
'; 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 |
28 |
29 |
30 |
< Prev
Next >
31 |
32 |
33 |
Show Options »
34 |
35 |
36 |
37 |
38 | 39 | 40 | 41 |
42 |
SOUND:
43 |
44 |
Off
On
45 |
46 | 47 |
ZOOM:
48 |
49 |
Actual
Max
50 |
51 | 52 |
CYCLE SPEED:
53 |
54 |
¼
½
1
2
4
55 |
56 | 57 |
CYCLE MODE:
58 |
59 |
Standard
Blend
60 |
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 |
183 | 184 | 185 | 186 |
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 to (Animated) GIF Conversion 2 | =========================================================== 3 | 4 | Based on [as3gif](http://code.google.com/p/as3gif/) Ported by [Kevin Kwok](http://antimatter15.com) 5 | 6 | ![This is the raw canvas element saved as a non-animated PNG](Demos/raw_canvas.png) 7 | ![This is the GIF which was generated from the canvas.](Demos/converted_animation.gif) 8 | ![This is the GIF which was generated from the canvas.](Demos/clock.gif) 9 | 10 | > AS3GIF lets you play and encode animated GIF's with ActionScript 3 11 | 12 | Since web pages can usually natively play GIFs fine, it's only a port of the GIFEncoder 13 | portions of the library. 14 | 15 | 16 | Basic Usage 17 | ============ 18 | 19 | Since it pretty much *is* GIFEncoder, you could consult the [as3gif how-to page](http://code.google.com/p/as3gif/wiki/How_to_use#The_GIFEncoder) 20 | 21 | 22 | But there are some differences so I'll cover it here anyway. 23 | 24 | You first need to include the JS files. It's probably best if you include it in this order, but it shouldn't matter too much. 25 | 26 | 27 | 28 | 29 | 30 | If you want to render the gif through an inline `` tag or try to save to disk or send to server or anything that requires 31 | conversion into a non-binary string form, you should probably include `b64.js` too. 32 | 33 | 34 | 35 | Simple enough right? Now to convert stuff to GIF, you need to have a working or at least some imageData-esque array. 36 | 37 | 38 |