diff options
| author | jules <jules@okfoc.us> | 2013-12-13 15:28:31 -0500 |
|---|---|---|
| committer | jules <jules@okfoc.us> | 2013-12-13 15:28:31 -0500 |
| commit | 346f3a9817b1e0812565396b9811a1ce5adc97b8 (patch) | |
| tree | 47944eb9a2762ef2036d140430a69be5afa9f368 /js/vendor | |
| parent | 96fd8423cc0793d47cab9117b0167fe19041cdea (diff) | |
reorganize
Diffstat (limited to 'js/vendor')
| -rw-r--r-- | js/vendor/FileSaver/.bower.json | 15 | ||||
| -rw-r--r-- | js/vendor/FileSaver/FileSaver.js | 232 | ||||
| -rw-r--r-- | js/vendor/FileSaver/LICENSE.md | 30 | ||||
| -rw-r--r-- | js/vendor/FileSaver/README.md | 78 | ||||
| -rw-r--r-- | js/vendor/FileSaver/bower.json | 7 | ||||
| -rw-r--r-- | js/vendor/FileSaver/demo/demo.css | 55 | ||||
| -rw-r--r-- | js/vendor/FileSaver/demo/demo.js | 213 | ||||
| -rw-r--r-- | js/vendor/FileSaver/demo/demo.min.js | 2 | ||||
| -rw-r--r-- | js/vendor/FileSaver/demo/index.xhtml | 57 | ||||
| -rw-r--r-- | js/vendor/FileSaver/package.json | 23 | ||||
| -rw-r--r-- | js/vendor/canvasquery.js | 1767 | ||||
| -rw-r--r-- | js/vendor/dataUriToBlob.js | 47 | ||||
| -rwxr-xr-x | js/vendor/gif-encode/GIFEncoder.js | 513 | ||||
| -rw-r--r-- | js/vendor/gif-encode/LZWEncoder.js | 328 | ||||
| -rw-r--r-- | js/vendor/gif-encode/NeuQuant.js | 538 | ||||
| -rw-r--r-- | js/vendor/gif-encode/client.js | 260 | ||||
| -rw-r--r-- | js/vendor/gif-encode/tube.js | 323 | ||||
| -rw-r--r-- | js/vendor/gif-encode/util.js | 15 | ||||
| -rw-r--r-- | js/vendor/gif-encode/worker.js | 88 | ||||
| -rw-r--r-- | js/vendor/gif.js | 1690 |
20 files changed, 6281 insertions, 0 deletions
diff --git a/js/vendor/FileSaver/.bower.json b/js/vendor/FileSaver/.bower.json new file mode 100644 index 0000000..586a935 --- /dev/null +++ b/js/vendor/FileSaver/.bower.json @@ -0,0 +1,15 @@ +{ + "name": "FileSaver", + "main": "./FileSaver.js", + "dependencies": {}, + "homepage": "https://github.com/eligrey/FileSaver.js", + "_release": "1f4b2f1724", + "_resolution": { + "type": "branch", + "branch": "master", + "commit": "1f4b2f1724b19ddaf5eae3d1523f7a8ae9cbf812" + }, + "_source": "git://github.com/eligrey/FileSaver.js.git", + "_target": "*", + "_originalSource": "FileSaver" +}
\ No newline at end of file diff --git a/js/vendor/FileSaver/FileSaver.js b/js/vendor/FileSaver/FileSaver.js new file mode 100644 index 0000000..378a9dc --- /dev/null +++ b/js/vendor/FileSaver/FileSaver.js @@ -0,0 +1,232 @@ +/* FileSaver.js + * A saveAs() FileSaver implementation. + * 2013-10-21 + * + * By Eli Grey, http://eligrey.com + * License: X11/MIT + * See LICENSE.md + */ + +/*global self */ +/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ + +var saveAs = saveAs + || (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator)) + || (function(view) { + "use strict"; + var + doc = view.document + // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet + , get_URL = function() { + return view.URL || view.webkitURL || view; + } + , URL = view.URL || view.webkitURL || view + , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") + , can_use_save_link = !view.externalHost && "download" in save_link + , click = function(node) { + var event = doc.createEvent("MouseEvents"); + event.initMouseEvent( + "click", true, false, view, 0, 0, 0, 0, 0 + , false, false, false, false, 0, null + ); + node.dispatchEvent(event); + } + , webkit_req_fs = view.webkitRequestFileSystem + , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem + , throw_outside = function (ex) { + (view.setImmediate || view.setTimeout)(function() { + throw ex; + }, 0); + } + , force_saveable_type = "application/octet-stream" + , fs_min_size = 0 + , deletion_queue = [] + , process_deletion_queue = function() { + var i = deletion_queue.length; + while (i--) { + var file = deletion_queue[i]; + if (typeof file === "string") { // file is an object URL + URL.revokeObjectURL(file); + } else { // file is a File + file.remove(); + } + } + deletion_queue.length = 0; // clear queue + } + , dispatch = function(filesaver, event_types, event) { + event_types = [].concat(event_types); + var i = event_types.length; + while (i--) { + var listener = filesaver["on" + event_types[i]]; + if (typeof listener === "function") { + try { + listener.call(filesaver, event || filesaver); + } catch (ex) { + throw_outside(ex); + } + } + } + } + , FileSaver = function(blob, name) { + // First try a.download, then web filesystem, then object URLs + var + filesaver = this + , type = blob.type + , blob_changed = false + , object_url + , target_view + , get_object_url = function() { + var object_url = get_URL().createObjectURL(blob); + deletion_queue.push(object_url); + return object_url; + } + , dispatch_all = function() { + dispatch(filesaver, "writestart progress write writeend".split(" ")); + } + // on any filesys errors revert to saving with object URLs + , fs_error = function() { + // don't create more object URLs than needed + if (blob_changed || !object_url) { + object_url = get_object_url(blob); + } + if (target_view) { + target_view.location.href = object_url; + } else { + window.open(object_url, "_blank"); + } + filesaver.readyState = filesaver.DONE; + dispatch_all(); + } + , abortable = function(func) { + return function() { + if (filesaver.readyState !== filesaver.DONE) { + return func.apply(this, arguments); + } + }; + } + , create_if_not_found = {create: true, exclusive: false} + , slice + ; + filesaver.readyState = filesaver.INIT; + if (!name) { + name = "download"; + } + if (can_use_save_link) { + object_url = get_object_url(blob); + // FF for Android has a nasty garbage collection mechanism + // that turns all objects that are not pure javascript into 'deadObject' + // this means `doc` and `save_link` are unusable and need to be recreated + // `view` is usable though: + doc = view.document; + save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"); + save_link.href = object_url; + save_link.download = name; + var event = doc.createEvent("MouseEvents"); + event.initMouseEvent( + "click", true, false, view, 0, 0, 0, 0, 0 + , false, false, false, false, 0, null + ); + save_link.dispatchEvent(event); + filesaver.readyState = filesaver.DONE; + dispatch_all(); + return; + } + // Object and web filesystem URLs have a problem saving in Google Chrome when + // viewed in a tab, so I force save with application/octet-stream + // http://code.google.com/p/chromium/issues/detail?id=91158 + if (view.chrome && type && type !== force_saveable_type) { + slice = blob.slice || blob.webkitSlice; + blob = slice.call(blob, 0, blob.size, force_saveable_type); + blob_changed = true; + } + // Since I can't be sure that the guessed media type will trigger a download + // in WebKit, I append .download to the filename. + // https://bugs.webkit.org/show_bug.cgi?id=65440 + if (webkit_req_fs && name !== "download") { + name += ".download"; + } + if (type === force_saveable_type || webkit_req_fs) { + target_view = view; + } + if (!req_fs) { + fs_error(); + return; + } + fs_min_size += blob.size; + req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { + fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { + var save = function() { + dir.getFile(name, create_if_not_found, abortable(function(file) { + file.createWriter(abortable(function(writer) { + writer.onwriteend = function(event) { + target_view.location.href = file.toURL(); + deletion_queue.push(file); + filesaver.readyState = filesaver.DONE; + dispatch(filesaver, "writeend", event); + }; + writer.onerror = function() { + var error = writer.error; + if (error.code !== error.ABORT_ERR) { + fs_error(); + } + }; + "writestart progress write abort".split(" ").forEach(function(event) { + writer["on" + event] = filesaver["on" + event]; + }); + writer.write(blob); + filesaver.abort = function() { + writer.abort(); + filesaver.readyState = filesaver.DONE; + }; + filesaver.readyState = filesaver.WRITING; + }), fs_error); + }), fs_error); + }; + dir.getFile(name, {create: false}, abortable(function(file) { + // delete file if it already exists + file.remove(); + save(); + }), abortable(function(ex) { + if (ex.code === ex.NOT_FOUND_ERR) { + save(); + } else { + fs_error(); + } + })); + }), fs_error); + }), fs_error); + } + , FS_proto = FileSaver.prototype + , saveAs = function(blob, name) { + return new FileSaver(blob, name); + } + ; + FS_proto.abort = function() { + var filesaver = this; + filesaver.readyState = filesaver.DONE; + dispatch(filesaver, "abort"); + }; + FS_proto.readyState = FS_proto.INIT = 0; + FS_proto.WRITING = 1; + FS_proto.DONE = 2; + + FS_proto.error = + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; + + view.addEventListener("unload", process_deletion_queue, false); + return saveAs; +}(this.self || this.window || this.content)); +// `self` is undefined in Firefox for Android content script context +// while `this` is nsIContentFrameMessageManager +// with an attribute `content` that corresponds to the window + +if (typeof module !== 'undefined') module.exports = saveAs; diff --git a/js/vendor/FileSaver/LICENSE.md b/js/vendor/FileSaver/LICENSE.md new file mode 100644 index 0000000..7eb56b9 --- /dev/null +++ b/js/vendor/FileSaver/LICENSE.md @@ -0,0 +1,30 @@ +This software is licensed under the MIT/X11 license. + +MIT/X11 license +--------------- + +Copyright © 2011 [Eli Grey][1]. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + + + [1]: http://eligrey.com
\ No newline at end of file diff --git a/js/vendor/FileSaver/README.md b/js/vendor/FileSaver/README.md new file mode 100644 index 0000000..49aff62 --- /dev/null +++ b/js/vendor/FileSaver/README.md @@ -0,0 +1,78 @@ +FileSaver.js +============ + +FileSaver.js implements the HTML5 W3C `saveAs()` [FileSaver][1] interface in browsers that do +not natively support it. There is a [FileSaver.js demo][2] that demonstrates saving +various media types. + +FileSaver.js is the solution to saving files on the client-side, and is perfect for +webapps that need to generate files, or for saving sensitive information that shouldn't be +sent to an external server. + +Looking for `canvas.toBlob()` for saving canvases? Check out +[canvas-toBlob.js](https://github.com/eligrey/canvas-toBlob.js) for a cross-browser implementation. + +Supported Browsers +------------------ + +| Browser | Constructs as | Filenames | Max Blob Size | Dependencies | +| -------------- | ------------- | ------------ | ------------- | ------------ | +| Firefox 20+ | Blob | Yes | 800MiB | None | +| Firefox ≤ 19 | data: URI | No | n/a | [Blob.js](https://github.com/eligrey/Blob.js) | +| Chrome | Blob | Yes | 345MiB | None | +| Chrome for Android | Blob | Yes | ? | None | +| IE 10+ | Blob | Yes | 600MiB | None | +| Opera Next | Blob | Yes | ? | None | +| Opera < 15 | data: URI | No | n/a | [Blob.js](https://github.com/eligrey/Blob.js) | +| Safari 6.1+ | Blob | No | ? | None | +| Safari < 6 | data: URI | No | n/a | [Blob.js](https://github.com/eligrey/Blob.js) | + +Feature detection is possible: + + try { var isFileSaverSupported = !!new Blob(); } catch(e){} + +Syntax +------ + + FileSaver saveAs(in Blob data, in DOMString filename) + +Examples +-------- + +### Saving text + + var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"}); + saveAs(blob, "hello world.txt"); + +The standard W3C File API [`Blob`][3] interface is not available in all browsers. +[Blob.js][4] is a cross-browser `Blob` implementation that solves this. + +### Saving a canvas + + var canvas = document.getElementById("my-canvas"), ctx = canvas.getContext("2d"); + // draw to canvas... + canvas.toBlob(function(blob) { + saveAs(blob, "pretty image.png"); + }); + +Note: The standard HTML5 `canvas.toBlob()` method is not available in all browsers. +[canvas-toBlob.js][5] is a cross-browser `canvas.toBlob()` that polyfills this. + +### Aborting a save + + var filesaver = saveAs(blob, "whatever"); + cancel_button.addEventListener("click", function() { + if (filesaver.abort) { + filesaver.abort(); + } + }, false); + +This isn't that useful unless you're saving very large files (e.g. generated video). + + + + [1]: http://www.w3.org/TR/file-writer-api/#the-filesaver-interface + [2]: http://eligrey.com/demos/FileSaver.js/ + [3]: https://developer.mozilla.org/en-US/docs/DOM/Blob + [4]: https://github.com/eligrey/Blob.js + [5]: https://github.com/eligrey/canvas-toBlob.js diff --git a/js/vendor/FileSaver/bower.json b/js/vendor/FileSaver/bower.json new file mode 100644 index 0000000..0e0d7e0 --- /dev/null +++ b/js/vendor/FileSaver/bower.json @@ -0,0 +1,7 @@ +{ + "name": "FileSaver", + "version": "1.0.0", + "main": "./FileSaver.js", + "dependencies": { + } +} diff --git a/js/vendor/FileSaver/demo/demo.css b/js/vendor/FileSaver/demo/demo.css new file mode 100644 index 0000000..fe03ca5 --- /dev/null +++ b/js/vendor/FileSaver/demo/demo.css @@ -0,0 +1,55 @@ +html {
+ background-color: #DDD;
+}
+body {
+ width: 900px;
+ margin: 0 auto;
+ font-family: Verdana, Helvetica, Arial, sans-serif;
+ box-shadow: 0 0 5px #000;
+ box-shadow: 0 0 10px 2px rgba(0, 0, 0, .5);
+ padding: 7px 25px 70px;
+ background-color: #FFF;
+}
+h1, h2, h3, h4, h5, h6 {
+ font-family: Georgia, "Times New Roman", serif;
+}
+h2, form {
+ text-align: center;
+}
+form {
+ margin-top: 5px;
+}
+.input {
+ width: 500px;
+ height: 300px;
+ margin: 0 auto;
+ display: block;
+}
+section {
+ margin-top: 40px;
+}
+dt {
+ font-weight: bold;
+ font-size: larger;
+}
+#canvas {
+ cursor: crosshair;
+}
+#canvas, #html {
+ border: 1px solid black;
+}
+.filename {
+ text-align: right;
+}
+#html {
+ box-sizing: border-box;
+ ms-box-sizing: border-box;
+ webkit-box-sizing: border-box;
+ moz-box-sizing: border-box;
+ overflow: auto;
+ padding: 1em;
+}
+dt:target {
+ background-color: Highlight;
+ color: HighlightText;
+}
diff --git a/js/vendor/FileSaver/demo/demo.js b/js/vendor/FileSaver/demo/demo.js new file mode 100644 index 0000000..e08497b --- /dev/null +++ b/js/vendor/FileSaver/demo/demo.js @@ -0,0 +1,213 @@ +/* FileSaver.js demo script + * 2012-01-23 + * + * By Eli Grey, http://eligrey.com + * License: X11/MIT + * See LICENSE.md + */ + +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/demo/demo.js */ + +(function(view) { +"use strict"; +// The canvas drawing portion of the demo is based off the demo at +// http://www.williammalone.com/articles/create-html5-canvas-javascript-drawing-app/ +var + document = view.document + , $ = function(id) { + return document.getElementById(id); + } + , session = view.sessionStorage + // only get URL when necessary in case Blob.js hasn't defined it yet + , get_blob = function() { + return view.Blob; + } + + , canvas = $("canvas") + , canvas_options_form = $("canvas-options") + , canvas_filename = $("canvas-filename") + , canvas_clear_button = $("canvas-clear") + + , text = $("text") + , text_options_form = $("text-options") + , text_filename = $("text-filename") + + , html = $("html") + , html_options_form = $("html-options") + , html_filename = $("html-filename") + + , ctx = canvas.getContext("2d") + , drawing = false + , x_points = session.x_points || [] + , y_points = session.y_points || [] + , drag_points = session.drag_points || [] + , add_point = function(x, y, dragging) { + x_points.push(x); + y_points.push(y); + drag_points.push(dragging); + } + , draw = function(){ + canvas.width = canvas.width; + ctx.lineWidth = 6; + ctx.lineJoin = "round"; + ctx.strokeStyle = "#000000"; + var + i = 0 + , len = x_points.length + ; + for(; i < len; i++) { + ctx.beginPath(); + if (i && drag_points[i]) { + ctx.moveTo(x_points[i-1], y_points[i-1]); + } else { + ctx.moveTo(x_points[i]-1, y_points[i]); + } + ctx.lineTo(x_points[i], y_points[i]); + ctx.closePath(); + ctx.stroke(); + } + } + , stop_drawing = function() { + drawing = false; + } + + // Title guesser and document creator available at https://gist.github.com/1059648 + , guess_title = function(doc) { + var + h = "h6 h5 h4 h3 h2 h1".split(" ") + , i = h.length + , headers + , header_text + ; + while (i--) { + headers = doc.getElementsByTagName(h[i]); + for (var j = 0, len = headers.length; j < len; j++) { + header_text = headers[j].textContent.trim(); + if (header_text) { + return header_text; + } + } + } + } + , doc_impl = document.implementation + , create_html_doc = function(html) { + var + dt = doc_impl.createDocumentType('html', null, null) + , doc = doc_impl.createDocument("http://www.w3.org/1999/xhtml", "html", dt) + , doc_el = doc.documentElement + , head = doc_el.appendChild(doc.createElement("head")) + , charset_meta = head.appendChild(doc.createElement("meta")) + , title = head.appendChild(doc.createElement("title")) + , body = doc_el.appendChild(doc.createElement("body")) + , i = 0 + , len = html.childNodes.length + ; + charset_meta.setAttribute("charset", html.ownerDocument.characterSet); + for (; i < len; i++) { + body.appendChild(doc.importNode(html.childNodes.item(i), true)); + } + var title_text = guess_title(doc); + if (title_text) { + title.appendChild(doc.createTextNode(title_text)); + } + return doc; + } +; +canvas.width = 500; +canvas.height = 300; + + if (typeof x_points === "string") { + x_points = JSON.parse(x_points); +} if (typeof y_points === "string") { + y_points = JSON.parse(y_points); +} if (typeof drag_points === "string") { + drag_points = JSON.parse(drag_points); +} if (session.canvas_filename) { + canvas_filename.value = session.canvas_filename; +} if (session.text) { + text.value = session.text; +} if (session.text_filename) { + text_filename.value = session.text_filename; +} if (session.html) { + html.innerHTML = session.html; +} if (session.html_filename) { + html_filename.value = session.html_filename; +} + +drawing = true; +draw(); +drawing = false; + +canvas_clear_button.addEventListener("click", function() { + canvas.width = canvas.width; + x_points.length = + y_points.length = + drag_points.length = + 0; +}, false); +canvas.addEventListener("mousedown", function(event) { + event.preventDefault(); + drawing = true; + add_point(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop, false); + draw(); +}, false); +canvas.addEventListener("mousemove", function(event) { + if (drawing) { + add_point(event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop, true); + draw(); + } +}, false); +canvas.addEventListener("mouseup", stop_drawing, false); +canvas.addEventListener("mouseout", stop_drawing, false); + +canvas_options_form.addEventListener("submit", function(event) { + event.preventDefault(); + canvas.toBlob(function(blob) { + saveAs( + blob + , (canvas_filename.value || canvas_filename.placeholder) + ".png" + ); + }, "image/png"); +}, false); + +text_options_form.addEventListener("submit", function(event) { + event.preventDefault(); + var BB = get_blob(); + saveAs( + new BB( + [text.value || text.placeholder] + , {type: "text/plain;charset=" + document.characterSet} + ) + , (text_filename.value || text_filename.placeholder) + ".txt" + ); +}, false); + +html_options_form.addEventListener("submit", function(event) { + event.preventDefault(); + var + BB = get_blob() + , xml_serializer = new XMLSerializer + , doc = create_html_doc(html) + ; + saveAs( + new BB( + [xml_serializer.serializeToString(doc)] + , {type: "application/xhtml+xml;charset=" + document.characterSet} + ) + , (html_filename.value || html_filename.placeholder) + ".xhtml" + ); +}, false); + +view.addEventListener("unload", function() { + session.x_points = JSON.stringify(x_points); + session.y_points = JSON.stringify(y_points); + session.drag_points = JSON.stringify(drag_points); + session.canvas_filename = canvas_filename.value; + + session.text = text.value; + session.text_filename = text_filename.value; + + session.html = html.innerHTML; + session.html_filename = html_filename.value; +}, false); +}(self)); diff --git a/js/vendor/FileSaver/demo/demo.min.js b/js/vendor/FileSaver/demo/demo.min.js new file mode 100644 index 0000000..77f9ed1 --- /dev/null +++ b/js/vendor/FileSaver/demo/demo.min.js @@ -0,0 +1,2 @@ +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/demo/demo.js */ +(function(n){"use strict";var s=n.document,g=function(A){return s.getElementById(A)},b=n.sessionStorage,x=function(){return n.Blob},f=g("canvas"),r=g("canvas-options"),y=g("canvas-filename"),p=g("canvas-clear"),q=g("text"),t=g("text-options"),h=g("text-filename"),m=g("html"),e=g("html-options"),i=g("html-filename"),u=f.getContext("2d"),z=false,a=b.x_points||[],o=b.y_points||[],d=b.drag_points||[],j=function(A,C,B){a.push(A);o.push(C);d.push(B)},l=function(){f.width=f.width;u.lineWidth=6;u.lineJoin="round";u.strokeStyle="#000000";var B=0,A=a.length;for(;B<A;B++){u.beginPath();if(B&&d[B]){u.moveTo(a[B-1],o[B-1])}else{u.moveTo(a[B]-1,o[B])}u.lineTo(a[B],o[B]);u.closePath();u.stroke()}},c=function(){z=false},w=function(E){var D="h6 h5 h4 h3 h2 h1".split(" "),C=D.length,F,G;while(C--){F=E.getElementsByTagName(D[C]);for(var B=0,A=F.length;B<A;B++){G=F[B].textContent.trim();if(G){return G}}}},v=s.implementation,k=function(D){var B=v.createDocumentType("html",null,null),J=v.createDocument("http://www.w3.org/1999/xhtml","html",B),A=J.documentElement,H=A.appendChild(J.createElement("head")),K=H.appendChild(J.createElement("meta")),I=H.appendChild(J.createElement("title")),E=A.appendChild(J.createElement("body")),C=0,G=D.childNodes.length;K.setAttribute("charset",D.ownerDocument.characterSet);for(;C<G;C++){E.appendChild(J.importNode(D.childNodes.item(C),true))}var F=w(J);if(F){I.appendChild(J.createTextNode(F))}return J};f.width=500;f.height=300;if(typeof a==="string"){a=JSON.parse(a)}if(typeof o==="string"){o=JSON.parse(o)}if(typeof d==="string"){d=JSON.parse(d)}if(b.canvas_filename){y.value=b.canvas_filename}if(b.text){q.value=b.text}if(b.text_filename){h.value=b.text_filename}if(b.html){m.innerHTML=b.html}if(b.html_filename){i.value=b.html_filename}z=true;l();z=false;p.addEventListener("click",function(){f.width=f.width;a.length=o.length=d.length=0},false);f.addEventListener("mousedown",function(A){A.preventDefault();z=true;j(A.pageX-f.offsetLeft,A.pageY-f.offsetTop,false);l()},false);f.addEventListener("mousemove",function(A){if(z){j(A.pageX-f.offsetLeft,A.pageY-f.offsetTop,true);l()}},false);f.addEventListener("mouseup",c,false);f.addEventListener("mouseout",c,false);r.addEventListener("submit",function(A){A.preventDefault();f.toBlob(function(B){saveAs(B,(y.value||y.placeholder)+".png")},"image/png")},false);t.addEventListener("submit",function(A){A.preventDefault();var B=x();saveAs(new B([q.value||q.placeholder],{type:"text/plain;charset="+s.characterSet}),(h.value||h.placeholder)+".txt")},false);e.addEventListener("submit",function(B){B.preventDefault();var D=x(),A=new XMLSerializer,C=k(m);saveAs(new D([A.serializeToString(C)],{type:"application/xhtml+xml;charset="+s.characterSet}),(i.value||i.placeholder)+".xhtml")},false);n.addEventListener("unload",function(){b.x_points=JSON.stringify(a);b.y_points=JSON.stringify(o);b.drag_points=JSON.stringify(d);b.canvas_filename=y.value;b.text=q.value;b.text_filename=h.value;b.html=m.innerHTML;b.html_filename=i.value},false)}(self));
\ No newline at end of file diff --git a/js/vendor/FileSaver/demo/index.xhtml b/js/vendor/FileSaver/demo/index.xhtml new file mode 100644 index 0000000..abc8813 --- /dev/null +++ b/js/vendor/FileSaver/demo/index.xhtml @@ -0,0 +1,57 @@ +<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US-x-Hixie">
+<head>
+<meta charset="utf-8"/>
+<title>FileSaver.js demo</title>
+<link rel="stylesheet" type="text/css" href="demo.css"/>
+</head>
+<body>
+<h1><a href="https://github.com/eligrey/FileSaver.js">FileSaver.js</a> demo</h1>
+<p>
+The following examples demonstrate how it is possible to generate and save any type of data right in the browser using the W3C <code>saveAs()</code> <a href="http://www.w3.org/TR/file-writer-api/#the-filesaver-interface">FileSaver</a> interface, without contacting any servers.
+</p>
+<section id="image-demo">
+ <h2>Saving an image</h2>
+ <canvas class="input" id="canvas" width="500" height="300"/>
+ <form id="canvas-options">
+ <label>Filename: <input type="text" class="filename" id="canvas-filename" placeholder="doodle"/>.png</label>
+ <input type="submit" value="Save"/>
+ <input type="button" id="canvas-clear" value="Clear"/>
+ </form>
+</section>
+<section id="text-demo">
+ <h2>Saving text</h2>
+ <textarea class="input" id="text" placeholder="Once upon a time..."/>
+ <form id="text-options">
+ <label>Filename: <input type="text" class="filename" id="text-filename" placeholder="a plain document"/>.txt</label>
+ <input type="submit" value="Save"/>
+ </form>
+</section>
+<section id="html-demo">
+ <h2>Saving rich text</h2>
+ <div class="input" id="html" contenteditable="">
+ <h3>Some example rich text</h3>
+ <ul>
+ <li><del>Plain</del> <ins>Boring</ins> text.</li>
+ <li><em>Emphasized text!</em></li>
+ <li><strong>Strong text!</strong></li>
+ <li>
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="70" height="70">
+ <circle cx="35" cy="35" r="35" fill="red"/>
+ <text x="10" y="40">image</text>
+ </svg>
+ </li>
+ <li><a href="https://github.com/eligrey/FileSaver.js">A link.</a></li>
+ </ul>
+ </div>
+ <form id="html-options">
+ <label>Filename: <input type="text" class="filename" id="html-filename" placeholder="a rich document"/>.xhtml</label>
+ <input type="submit" value="Save"/>
+ </form>
+</section>
+<script type="application/ecmascript" async="" src="https://raw.github.com/eligrey/Blob.js/master/Blob.min.js"/>
+<script type="application/ecmascript" async="" src="https://raw.github.com/eligrey/canvas-toBlob.js/master/canvas-toBlob.js"/>
+<script type="application/ecmascript" async="" src="https://raw.github.com/eligrey/FileSaver.js/master/FileSaver.js"/>
+<script type="application/ecmascript" async="" src="https://raw.github.com/eligrey/FileSaver.js/master/demo/demo.js"/>
+</body>
+</html>
diff --git a/js/vendor/FileSaver/package.json b/js/vendor/FileSaver/package.json new file mode 100644 index 0000000..3ddf184 --- /dev/null +++ b/js/vendor/FileSaver/package.json @@ -0,0 +1,23 @@ +{ + "name": "filesaver.js", + "version": "2013.1.23", + "description": "A saveAs() FileSaver implementation", + "main": "FileSaver.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/eligrey/FileSaver.js.git" + }, + "keywords": [ + "filesaver", + "saveas", + "blob" + ], + "author": "Eli Grey", + "license": "MIT/X11", + "bugs": { + "url": "https://github.com/eligrey/FileSaver.js/issues" + } +} diff --git a/js/vendor/canvasquery.js b/js/vendor/canvasquery.js new file mode 100644 index 0000000..a5b36f3 --- /dev/null +++ b/js/vendor/canvasquery.js @@ -0,0 +1,1767 @@ +/* + Canvas Query 0.8.4 + http://canvasquery.org + (c) 2012-2013 http://rezoner.net + Canvas Query may be freely distributed under the MIT license. +*/ + +(function(window, undefined) { + + var MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); + + + window.requestAnimationFrame = (function() { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + })(); + + + var $ = function(selector) { + if (arguments.length === 0) { + var canvas = $.createCanvas(window.innerWidth, window.innerHeight); + window.addEventListener("resize", function() { + // canvas.width = window.innerWidth; + // canvas.height = window.innerHeight; + }); + } else if (typeof selector === "string") { + var canvas = document.querySelector(selector); + } else if (typeof selector === "number") { + var canvas = $.createCanvas(arguments[0], arguments[1]); + } else if (selector instanceof Image || selector instanceof HTMLImageElement) { + var canvas = $.createCanvas(selector); + } else if (selector instanceof $.Wrapper) { + return selector; + } else { + var canvas = selector; + } + + return new $.Wrapper(canvas); + } + + $.extend = function() { + for (var i = 1; i < arguments.length; i++) { + for (var j in arguments[i]) { + arguments[0][j] = arguments[i][j]; + } + } + + return arguments[0]; + }; + + $.augment = function() { + for (var i = 1; i < arguments.length; i++) { + _.extend(arguments[0], arguments[i]); + arguments[i](arguments[0]); + } + }; + + $.distance = function(x1, y1, x2, y2) { + if (arguments.length > 2) { + var dx = x1 - x2; + var dy = y1 - y2; + + return Math.sqrt(dx * dx + dy * dy); + } else { + return Math.abs(x1 - y1); + } + }; + + $.extend($, { + + keycodes: { + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 45: "insert", + 46: "delete", + 8: "backspace", + 9: "tab", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause", + 20: "capslock", + 27: "escape", + 32: "space", + 33: "pageup", + 34: "pagedown", + 35: "end", + 112: "f1", + 113: "f2", + 114: "f3", + 115: "f4", + 116: "f5", + 117: "f6", + 118: "f7", + 119: "f8", + 120: "f9", + 121: "f10", + 122: "f11", + 123: "f12", + 144: "numlock", + 145: "scrolllock", + 186: "semicolon", + 187: "equal", + 188: "comma", + 189: "dash", + 190: "period", + 191: "slash", + 192: "graveaccent", + 219: "openbracket", + 220: "backslash", + 221: "closebraket", + 222: "singlequote" + }, + + cleanArray: function(array, property) { + + var lastArgument = arguments[arguments.length - 1]; + var isLastArgumentFunction = typeof lastArgument === "function"; + + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === null || (property && array[i][property])) { + if (isLastArgumentFunction) { + lastArgument(array[i]); + } + array.splice(i--, 1); + len--; + } + } + }, + + specialBlendFunctions: ["color", "value", "hue", "saturation"], + + blendFunctions: { + normal: function(a, b) { + return b; + }, + + overlay: function(a, b) { + a /= 255; + b /= 255; + var result = 0; + + if (a < 0.5) result = 2 * a * b; + else result = 1 - 2 * (1 - a) * (1 - b); + + return Math.min(255, Math.max(0, result * 255 | 0)); + }, + + hardLight: function(a, b) { + return $.blendFunctions.overlay(b, a); + }, + + softLight: function(a, b) { + a /= 255; + b /= 255; + + var v = (1 - 2 * b) * (a * a) + 2 * b * a; + return $.limitValue(v * 255, 0, 255); + }, + + dodge: function(a, b) { + return Math.min(256 * a / (255 - b + 1), 255); + }, + + burn: function(a, b) { + return 255 - Math.min(256 * (255 - a) / (b + 1), 255); + }, + + multiply: function(a, b) { + return b * a / 255; + }, + + divide: function(a, b) { + return Math.min(256 * a / (b + 1), 255); + }, + + screen: function(a, b) { + return 255 - (255 - b) * (255 - a) / 255; + }, + + grainExtract: function(a, b) { + return $.limitValue(a - b + 128, 0, 255); + }, + + grainMerge: function(a, b) { + return $.limitValue(a + b - 128, 0, 255); + }, + + difference: function(a, b) { + return Math.abs(a - b); + }, + + addition: function(a, b) { + return Math.min(a + b, 255); + }, + + substract: function(a, b) { + return Math.max(a - b, 0); + }, + + darkenOnly: function(a, b) { + return Math.min(a, b); + }, + + lightenOnly: function(a, b) { + return Math.max(a, b); + }, + + color: function(a, b) { + var aHSL = $.rgbToHsl(a); + var bHSL = $.rgbToHsl(b); + + return $.hslToRgb(bHSL[0], bHSL[1], aHSL[2]); + }, + + hue: function(a, b) { + var aHSV = $.rgbToHsv(a); + var bHSV = $.rgbToHsv(b); + + if (!bHSV[1]) return $.hsvToRgb(aHSV[0], aHSV[1], aHSV[2]); + else return $.hsvToRgb(bHSV[0], aHSV[1], aHSV[2]); + }, + + value: function(a, b) { + var aHSV = $.rgbToHsv(a); + var bHSV = $.rgbToHsv(b); + + return $.hsvToRgb(aHSV[0], aHSV[1], bHSV[2]); + }, + + saturation: function(a, b) { + var aHSV = $.rgbToHsv(a); + var bHSV = $.rgbToHsv(b); + + return $.hsvToRgb(aHSV[0], bHSV[1], aHSV[2]); + } + }, + + blend: function(below, above, mode, mix) { + if (typeof mix === "undefined") mix = 1; + + var below = $(below); + var above = $(above); + + var belowCtx = below.context; + var aboveCtx = above.context; + + var belowData = belowCtx.getImageData(0, 0, below.canvas.width, below.canvas.height); + var aboveData = aboveCtx.getImageData(0, 0, above.canvas.width, above.canvas.height); + + var belowPixels = belowData.data; + var abovePixels = aboveData.data; + + var imageData = this.createImageData(below.canvas.width, below.canvas.height); + var pixels = imageData.data; + + var blendingFunction = $.blendFunctions[mode]; + + if ($.specialBlendFunctions.indexOf(mode) !== -1) { + for (var i = 0, len = belowPixels.length; i < len; i += 4) { + var rgb = blendingFunction([belowPixels[i + 0], belowPixels[i + 1], belowPixels[i + 2]], [abovePixels[i + 0], abovePixels[i + 1], abovePixels[i + 2]]); + + pixels[i + 0] = belowPixels[i + 0] + (rgb[0] - belowPixels[i + 0]) * mix; + pixels[i + 1] = belowPixels[i + 1] + (rgb[1] - belowPixels[i + 1]) * mix; + pixels[i + 2] = belowPixels[i + 2] + (rgb[2] - belowPixels[i + 2]) * mix; + + pixels[i + 3] = belowPixels[i + 3]; + } + } else { + + for (var i = 0, len = belowPixels.length; i < len; i += 4) { + var r = blendingFunction(belowPixels[i + 0], abovePixels[i + 0]); + var g = blendingFunction(belowPixels[i + 1], abovePixels[i + 1]); + var b = blendingFunction(belowPixels[i + 2], abovePixels[i + 2]); + + pixels[i + 0] = belowPixels[i + 0] + (r - belowPixels[i + 0]) * mix; + pixels[i + 1] = belowPixels[i + 1] + (g - belowPixels[i + 1]) * mix; + pixels[i + 2] = belowPixels[i + 2] + (b - belowPixels[i + 2]) * mix; + + pixels[i + 3] = belowPixels[i + 3]; + } + } + + below.context.putImageData(imageData, 0, 0); + + return below; + }, + + wrapValue: function(value, min, max) { + var d = Math.abs(max - min); + return min + (value - min) % d; + }, + + limitValue: function(value, min, max) { + return value < min ? min : value > max ? max : value; + }, + + mix: function(a, b, ammount) { + return a + (b - a) * ammount; + }, + + hexToRgb: function(hex) { + if (hex.length === 7) return ['0x' + hex[1] + hex[2] | 0, '0x' + hex[3] + hex[4] | 0, '0x' + hex[5] + hex[6] | 0]; + else return ['0x' + hex[1] | 0, '0x' + hex[2], '0x' + hex[3] | 0]; + }, + + rgbToHex: function(r, g, b) { + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1, 7); + }, + + /* author: http://mjijackson.com/ */ + + rgbToHsl: function(r, g, b) { + + if (r instanceof Array) { + b = r[2]; + g = r[1]; + r = r[0]; + } + + r /= 255, g /= 255, b /= 255; + var max = Math.max(r, g, b), + min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + + if (max == min) { + h = s = 0; // achromatic + } else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return [h, s, l]; + }, + + /* author: http://mjijackson.com/ */ + + hslToRgb: function(h, s, l) { + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [r * 255 | 0, g * 255 | 0, b * 255 | 0]; + }, + + rgbToHsv: function(r, g, b) { + if (r instanceof Array) { + b = r[2]; + g = r[1]; + r = r[0]; + } + + r = r / 255, g = g / 255, b = b / 255; + var max = Math.max(r, g, b), + min = Math.min(r, g, b); + var h, s, v = max; + + var d = max - min; + s = max == 0 ? 0 : d / max; + + if (max == min) { + h = 0; // achromatic + } else { + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return [h, s, v]; + }, + + hsvToRgb: function(h, s, v) { + var r, g, b; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: + r = v, g = t, b = p; + break; + case 1: + r = q, g = v, b = p; + break; + case 2: + r = p, g = v, b = t; + break; + case 3: + r = p, g = q, b = v; + break; + case 4: + r = t, g = p, b = v; + break; + case 5: + r = v, g = p, b = q; + break; + } + + return [r * 255 | 0, g * 255 | 0, b * 255 | 0]; + }, + + color: function() { + var result = new $.Color(); + result.parse(arguments[0], arguments[1]); + return result; + }, + + createCanvas: function(width, height) { + var result = document.createElement("canvas"); + + if (arguments[0] instanceof Image || arguments[0] instanceof HTMLImageElement) { + var image = arguments[0]; + result.width = image.width; + result.height = image.height; + result.getContext("2d").drawImage(image, 0, 0); + } else { + result.width = width; + result.height = height; + } + + return result; + }, + + createImageData: function(width, height) { + return document.createElement("Canvas").getContext("2d").createImageData(width, height); + }, + + + /* https://gist.github.com/3781251 */ + + mousePosition: function(event) { + var totalOffsetX = 0, + totalOffsetY = 0, + coordX = 0, + coordY = 0, + currentElement = event.target || event.srcElement, + mouseX = 0, + mouseY = 0; + + // Traversing the parents to get the total offset + do { + totalOffsetX += currentElement.offsetLeft; + totalOffsetY += currentElement.offsetTop; + } + while ((currentElement = currentElement.offsetParent)); + // Set the event to first touch if using touch-input + if (event.changedTouches && event.changedTouches[0] !== undefined) { + event = event.changedTouches[0]; + } + // Use pageX to get the mouse coordinates + if (event.pageX || event.pageY) { + mouseX = event.pageX; + mouseY = event.pageY; + } + // IE8 and below doesn't support event.pageX + else if (event.clientX || event.clientY) { + mouseX = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + mouseY = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + // Subtract the offset from the mouse coordinates + coordX = mouseX - totalOffsetX; + coordY = mouseY - totalOffsetY; + + return { + x: coordX, + y: coordY + }; + } + }); + + $.Wrapper = function(canvas) { + this.context = canvas.getContext("2d"); + this.canvas = canvas; + } + + $.Wrapper.prototype = { + appendTo: function(selector) { + if (typeof selector === "object") { + var element = selector; + } else { + var element = document.querySelector(selector); + } + + element.appendChild(this.canvas); + + return this; + }, + + blendOn: function(what, mode, mix) { + $.blend(what, this, mode, mix); + + return this; + }, + + blend: function(what, mode, mix) { + if (typeof what === "string") { + var color = what; + what = $($.createCanvas(this.canvas.width, this.canvas.height)); + what.fillStyle(color).fillRect(0, 0, this.canvas.width, this.canvas.height); + } + + $.blend(this, what, mode, mix); + + return this; + }, + + circle: function(x, y, r) { + this.context.arc(x, y, r, 0, Math.PI * 2); + return this; + }, + + crop: function(x, y, w, h) { + + var canvas = $.createCanvas(w, h); + var context = canvas.getContext("2d"); + + context.drawImage(this.canvas, x, y, w, h, 0, 0, w, h); + this.canvas.width = w; + this.canvas.height = h; + this.clear(); + this.context.drawImage(canvas, 0, 0); + + return this; + }, + + set: function(properties) { + $.extend(this.context, properties); + }, + + resize: function(width, height) { + var w = width, + h = height; + + if (arguments.length === 1) { + w = arguments[0] * this.canvas.width | 0; + h = arguments[0] * this.canvas.height | 0; + } else { + + if (height === null) { + if (this.canvas.width > width) { + h = this.canvas.height * (width / this.canvas.width) | 0; + w = width; + } else { + w = this.canvas.width; + h = this.canvas.height; + } + } else if (width === null) { + if (this.canvas.width > width) { + w = this.canvas.width * (height / this.canvas.height) | 0; + h = height; + } else { + w = this.canvas.width; + h = this.canvas.height; + } + } + } + + var $resized = $(w, h).drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, w, h); + this.canvas = $resized.canvas; + this.context = $resized.context; + + return this; + }, + + + trim: function(color, changes) { + var transparent; + + if (color) { + color = $.color(color).toArray(); + transparent = !color[3]; + } else transparent = true; + + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + + var bound = [this.canvas.width, this.canvas.height, 0, 0]; + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + if (transparent) { + if (!sourcePixels[i + 3]) continue; + } else if (sourcePixels[i + 0] === color[0] && sourcePixels[i + 1] === color[1] && sourcePixels[i + 2] === color[2]) continue; + var x = (i / 4 | 0) % this.canvas.width | 0; + var y = (i / 4 | 0) / this.canvas.width | 0; + + if (x < bound[0]) bound[0] = x; + if (x > bound[2]) bound[2] = x; + + if (y < bound[1]) bound[1] = y; + if (y > bound[3]) bound[3] = y; + } + + if (bound[2] === 0 || bound[3] === 0) { + + } else { + if (changes) { + changes.left = bound[0]; + changes.top = bound[1]; + changes.width = bound[2] - bound[0]; + changes.height = bound[3] - bound[1]; + } + + this.crop(bound[0], bound[1], bound[2] - bound[0] + 1, bound[3] - bound[1] + 1); + } + + return this; + }, + + resizePixel: function(pixelSize) { + + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + var canvas = document.createElement("canvas"); + var context = canvas.context = canvas.getContext("2d"); + + canvas.width = this.canvas.width * pixelSize | 0; + canvas.height = this.canvas.height * pixelSize | 0; + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + if (!sourcePixels[i + 3]) continue; + context.fillStyle = $.rgbToHex(sourcePixels[i + 0], sourcePixels[i + 1], sourcePixels[i + 2]); + + var x = (i / 4) % this.canvas.width; + var y = (i / 4) / this.canvas.width | 0; + + context.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); + } + + this.canvas.width = canvas.width; + this.canvas.height = canvas.height; + this.clear().drawImage(canvas, 0, 0); + + return this; + + /* this very clever method is working only under Chrome */ + + var x = 0, + y = 0; + + var canvas = document.createElement("canvas"); + var context = canvas.context = canvas.getContext("2d"); + + canvas.width = this.canvas.width * pixelSize | 0; + canvas.height = this.canvas.height * pixelSize | 0; + + while (x < this.canvas.width) { + y = 0; + while (y < this.canvas.height) { + context.drawImage(this.canvas, x, y, 1, 1, x * pixelSize, y * pixelSize, pixelSize, pixelSize); + y++; + } + x++; + } + + this.canvas = canvas; + this.context = context; + + return this; + }, + + + matchPalette: function(palette) { + var imgData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + + var rgbPalette = []; + for (var i = 0; i < palette.length; i++) { + rgbPalette.push($.color(palette[i])); + } + + + for (var i = 0; i < imgData.data.length; i += 4) { + var difList = []; + for (var j = 0; j < rgbPalette.length; j++) { + var rgbVal = rgbPalette[j]; + var rDif = Math.abs(imgData.data[i] - rgbVal[0]), + gDif = Math.abs(imgData.data[i + 1] - rgbVal[1]), + bDif = Math.abs(imgData.data[i + 2] - rgbVal[2]); + difList.push(rDif + gDif + bDif); + } + + var closestMatch = 0; + for (var j = 0; j < palette.length; j++) { + if (difList[j] < difList[closestMatch]) { + closestMatch = j; + } + } + + var paletteRgb = cq.hexToRgb(palette[closestMatch]); + imgData.data[i] = paletteRgb[0]; + imgData.data[i + 1] = paletteRgb[1]; + imgData.data[i + 2] = paletteRgb[2]; + } + + this.context.putImageData(imgData, 0, 0); + + return this; + }, + + getPalette: function() { + var palette = []; + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + if (sourcePixels[i + 3]) { + var hex = $.rgbToHex(sourcePixels[i + 0], sourcePixels[i + 1], sourcePixels[i + 2]); + if (palette.indexOf(hex) === -1) palette.push(hex); + } + } + + return palette; + }, + + pixelize: function(size) { + if (!size) return this; + size = size || 4; + + var mozImageSmoothingEnabled = this.context.mozImageSmoothingEnabled; + var webkitImageSmoothingEnabled = this.context.webkitImageSmoothingEnabled; + + this.context.mozImageSmoothingEnabled = false; + this.context.webkitImageSmoothingEnabled = false; + + var scale = (this.canvas.width / size) / this.canvas.width; + + var temp = cq(this.canvas.width, this.canvas.height); + + temp.drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, this.canvas.width * scale | 0, this.canvas.height * scale | 0); + this.clear().drawImage(temp.canvas, 0, 0, this.canvas.width * scale | 0, this.canvas.height * scale | 0, 0, 0, this.canvas.width, this.canvas.height); + + this.context.mozImageSmoothingEnabled = mozImageSmoothingEnabled; + this.context.webkitImageSmoothingEnabled = webkitImageSmoothingEnabled; + + return this; + }, + + colorToMask: function(color, inverted) { + color = $.color(color).toArray(); + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + + var mask = []; + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + if (sourcePixels[i + 0] == color[0] && sourcePixels[i + 1] == color[1] && sourcePixels[i + 2] == color[2]) mask.push(inverted || false); + else mask.push(!inverted); + } + + return mask; + }, + + grayscaleToMask: function(color) { + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + + var mask = []; + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + mask.push((sourcePixels[i + 0] + sourcePixels[i + 1] + sourcePixels[i + 2]) / 3 | 0); + } + + return mask; + }, + + grayscaleToAlpha: function() { + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + + var mask = []; + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + sourcePixels[i + 3] = (sourcePixels[i + 0] + sourcePixels[i + 1] + sourcePixels[i + 2]) / 3 | 0; + + sourcePixels[i + 0] = sourcePixels[i + 1] = sourcePixels[i + 2] = 255; + } + + this.context.putImageData(sourceData, 0, 0); + + return this; + }, + + applyMask: function(mask) { + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + + var mode = typeof mask[0] === "boolean" ? "bool" : "byte"; + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + var value = mask[i / 4]; + + if (mode === "bool") sourcePixels[i + 3] = 255 * value | 0; + else { + sourcePixels[i + 3] = value | 0; + } + } + + + this.context.putImageData(sourceData, 0, 0); + return this; + }, + + fillMask: function(mask) { + + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var sourcePixels = sourceData.data; + + var maskType = typeof mask[0] === "boolean" ? "bool" : "byte"; + var colorMode = arguments.length === 2 ? "normal" : "gradient"; + + var color = $.color(arguments[1]); + if (colorMode === "gradient") colorB = $.color(arguments[2]); + + for (var i = 0, len = sourcePixels.length; i < len; i += 4) { + var value = mask[i / 4]; + + if (maskType === "byte") value /= 255; + + if (colorMode === "normal") { + if (value) { + sourcePixels[i + 0] = color[0] | 0; + sourcePixels[i + 1] = color[1] | 0; + sourcePixels[i + 2] = color[2] | 0; + sourcePixels[i + 3] = value * 255 | 0; + } + } else { + sourcePixels[i + 0] = color[0] + (colorB[0] - color[0]) * value | 0; + sourcePixels[i + 1] = color[1] + (colorB[1] - color[1]) * value | 0; + sourcePixels[i + 2] = color[2] + (colorB[2] - color[2]) * value | 0; + sourcePixels[i + 3] = 255; + } + } + + this.context.putImageData(sourceData, 0, 0); + return this; + }, + + clear: function(color) { + if (color) { + this.context.fillStyle = color; + this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + } else { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + + return this; + }, + + clone: function() { + var result = $.createCanvas(this.canvas.width, this.canvas.height); + result.getContext("2d").drawImage(this.canvas, 0, 0); + return $(result); + }, + + fillStyle: function(fillStyle) { + this.context.fillStyle = fillStyle; + return this; + }, + + strokeStyle: function(strokeStyle) { + this.context.strokeStyle = strokeStyle; + return this; + }, + + gradientText: function(text, x, y, maxWidth, gradient) { + + var words = text.split(" "); + + var h = this.font().match(/\d+/g)[0] * 2; + + var ox = 0; + var oy = 0; + + if (maxWidth) { + var line = 0; + var lines = [""]; + + for (var i = 0; i < words.length; i++) { + var word = words[i] + " "; + var wordWidth = this.context.measureText(word).width; + + if (ox + wordWidth > maxWidth) { + lines[++line] = ""; + ox = 0; + } + + lines[line] += word; + + ox += wordWidth; + } + } else var lines = [text]; + + for (var i = 0; i < lines.length; i++) { + var oy = y + i * h * 0.6 | 0; + var lingrad = this.context.createLinearGradient(0, oy, 0, oy + h * 0.6 | 0); + + for (var j = 0; j < gradient.length; j += 2) { + lingrad.addColorStop(gradient[j], gradient[j + 1]); + } + + var text = lines[i]; + + this.fillStyle(lingrad).fillText(text, x, oy); + } + + return this; + }, + + setHsl: function() { + + if (arguments.length === 1) { + var args = arguments[0]; + } else { + var args = arguments; + } + + var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var pixels = data.data; + var r, g, b, a, h, s, l, hsl = [], + newPixel = []; + + for (var i = 0, len = pixels.length; i < len; i += 4) { + hsl = $.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); + + h = args[0] === null ? hsl[0] : $.limitValue(args[0], 0, 1); + s = args[1] === null ? hsl[1] : $.limitValue(args[1], 0, 1); + l = args[2] === null ? hsl[2] : $.limitValue(args[2], 0, 1); + + newPixel = $.hslToRgb(h, s, l); + + pixels[i + 0] = newPixel[0]; + pixels[i + 1] = newPixel[1]; + pixels[i + 2] = newPixel[2]; + } + + this.context.putImageData(data, 0, 0); + + return this; + }, + + shiftHsl: function() { + + if (arguments.length === 1) { + var args = arguments[0]; + } else { + var args = arguments; + } + + var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var pixels = data.data; + var r, g, b, a, h, s, l, hsl = [], + newPixel = []; + + for (var i = 0, len = pixels.length; i < len; i += 4) { + hsl = $.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); + + h = args[0] === null ? hsl[0] : $.wrapValue(hsl[0] + args[0], 0, 1); + s = args[1] === null ? hsl[1] : $.limitValue(hsl[1] + args[1], 0, 1); + l = args[2] === null ? hsl[2] : $.limitValue(hsl[2] + args[2], 0, 1); + + newPixel = $.hslToRgb(h, s, l); + + pixels[i + 0] = newPixel[0]; + pixels[i + 1] = newPixel[1]; + pixels[i + 2] = newPixel[2]; + } + + + this.context.putImageData(data, 0, 0); + + return this; + }, + + replaceHue: function(src, dst) { + + var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var pixels = data.data; + var r, g, b, a, h, s, l, hsl = [], + newPixel = []; + + for (var i = 0, len = pixels.length; i < len; i += 4) { + hsl = $.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); + + if (Math.abs(hsl[0] - src) < 0.05) h = $.wrapValue(dst, 0, 1); + else h = hsl[0]; + + newPixel = $.hslToRgb(h, hsl[1], hsl[2]); + + pixels[i + 0] = newPixel[0]; + pixels[i + 1] = newPixel[1]; + pixels[i + 2] = newPixel[2]; + } + + this.context.putImageData(data, 0, 0); + + return this; + }, + + invert: function(src, dst) { + + var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var pixels = data.data; + var r, g, b, a, h, s, l, hsl = [], + newPixel = []; + + for (var i = 0, len = pixels.length; i < len; i += 4) { + pixels[i + 0] = 255 - pixels[i + 0]; + pixels[i + 1] = 255 - pixels[i + 1]; + pixels[i + 2] = 255 - pixels[i + 2]; + } + + this.context.putImageData(data, 0, 0); + + return this; + }, + + roundRect: function(x, y, width, height, radius) { + + this.beginPath(); + this.moveTo(x + radius, y); + this.lineTo(x + width - radius, y); + this.quadraticCurveTo(x + width, y, x + width, y + radius); + this.lineTo(x + width, y + height - radius); + this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + this.lineTo(x + radius, y + height); + this.quadraticCurveTo(x, y + height, x, y + height - radius); + this.lineTo(x, y + radius); + this.quadraticCurveTo(x, y, x + radius, y); + this.closePath(); + + return this; + }, + + wrappedText: function(text, x, y, maxWidth, newlineCallback) { + + var lines = text instanceof Array ? text : this.flowText(text, maxWidth); + var h = this.font().match(/\d+/g)[0] * 2; + + var ox = 0; + var oy = 0; + + if (maxWidth) { + var line = 0; + var lines = [""]; + + for (var i = 0; i < words.length; i++) { + var word = words[i] + " "; + var wordWidth = this.context.measureText(word).width; + + if (ox + wordWidth > maxWidth) { + lines[++line] = ""; + ox = 0; + } + + lines[line] += word; + + ox += wordWidth; + } + } else { + var lines = [text]; + } + + for (var i = 0; i < lines.length; i++) { + var oy = y + i * h * 0.6 | 0; + + var text = lines[i]; + + if (newlineCallback) newlineCallback.call(this, x, y + oy); + + this.fillText(text, x, oy); + } + + return this; + + }, + + flowText: function(text, maxWidth) { + var words = text.split(/(\s)/); + + var ox = 0; + var oy = 0; + + if (maxWidth) { + var line = 0; + var lines = [""]; + + var spaceWidth = this.context.measureText(" ").width; + + for(var i = 0; i < words.length; i++) { + + var word = words[i]; + + if (word == "\n") { + lines[line] = lines[line].replace(/\s+$/,""); + lines[++line] = ""; + ox = 0; + continue; + } + + else if (word == " ") { + ox += spaceWidth; + if (ox > maxWidth) { + lines[line] = lines[line].replace(/\s+$/,""); + lines[++line] = ""; + ox = 0; + } + else { + lines[line] += " "; + } + } + + else { + var wordWidth = this.context.measureText(word).width; + + ox += wordWidth; + + if(ox > maxWidth && lines[line].length) { + lines[line] = lines[line].replace(/\s+$/,""); + lines[++line] = ""; + ox = wordWidth; + } + + lines[line] += word; + } + } + } else { + var lines = [text]; + } + + return lines; + + }, + + textBoundaries: function(text, maxWidth) { + + var lines = this.flowText(text, maxWidth || Infinity); + + var h = this.font().match(/\d+/g)[0] * 2; + + var w = 0; + for (var i = 0; i < lines.length; i++) { + w = Math.max( w, this.measureText(lines[i]).width ); + } + + return { + height: lines.length * h * 0.6 | 0, + width: w, + lines: lines + } + }, + + paperBag: function(x, y, width, height, blowX, blowY) { + var lx, ly; + this.beginPath(); + this.moveTo(x, y); + this.quadraticCurveTo(x + width / 2 | 0, y + height * blowY | 0, x + width, y); + this.quadraticCurveTo(x + width - width * blowX | 0, y + height / 2 | 0, x + width, y + height); + this.quadraticCurveTo(x + width / 2 | 0, y + height - height * blowY | 0, x, y + height); + this.quadraticCurveTo(x + width * blowX | 0, y + height / 2 | 0, x, y); + }, + + borderImage: function(image, x, y, w, h, t, r, b, l, fill) { + + /* top */ + this.drawImage(image, l, 0, image.width - l - r, t, x + l, y, w - l - r, t); + + /* bottom */ + this.drawImage(image, l, image.height - b, image.width - l - r, b, x + l, y + h - b, w - l - r, b); + + /* left */ + this.drawImage(image, 0, t, l, image.height - b - t, x, y + t, l, h - b - t); + + /* right */ + this.drawImage(image, image.width - r, t, r, image.height - b - t, x + w - r, y + t, r, h - b - t); + + /* top-left */ + this.drawImage(image, 0, 0, l, t, x, y, l, t); + + /* top-right */ + this.drawImage(image, image.width - r, 0, r, t, x + w - r, y, r, t); + + /* bottom-right */ + this.drawImage(image, image.width - r, image.height - b, r, b, x + w - r, y + h - b, r, b); + + /* bottom-left */ + this.drawImage(image, 0, image.height - b, l, b, x, y + h - b, l, b); + + if (fill) { + if (typeof fill === "string") { + this.fillStyle(fill).fillRect(x + l, y + t, w - l - r, h - t - b); + } else { + this.drawImage(image, l, t, image.width - r - l, image.height - b - t, x + l, y + t, w - l - r, h - t - b); + } + } + }, + + /* www.html5rocks.com/en/tutorials/canvas/imagefilters/ */ + + convolve: function(matrix, mix, divide) { + + if (typeof divide === "undefined") divide = 1; + if (typeof mix === "undefined") mix = 1; + + var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var matrixSize = Math.sqrt(matrix.length) + 0.5 | 0; + var halfMatrixSize = matrixSize / 2 | 0; + var src = sourceData.data; + var sw = sourceData.width; + var sh = sourceData.height; + var w = sw; + var h = sh; + var output = $.createImageData(this.canvas.width, this.canvas.height); + var dst = output.data; + + for (var y = 1; y < h - 1; y++) { + for (var x = 1; x < w - 1; x++) { + + var dstOff = (y * w + x) * 4; + var r = 0, + g = 0, + b = 0, + a = 0; + + for (var cy = 0; cy < matrixSize; cy++) { + for (var cx = 0; cx < matrixSize; cx++) { + var scy = y + cy - halfMatrixSize; + var scx = x + cx - halfMatrixSize; + if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) { + var srcOff = (scy * sw + scx) * 4; + + var wt = matrix[cy * matrixSize + cx] / divide; + + r += src[srcOff + 0] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; + } + } + } + + dst[dstOff + 0] = $.mix(src[dstOff + 0], r, mix); + dst[dstOff + 1] = $.mix(src[dstOff + 1], g, mix); + dst[dstOff + 2] = $.mix(src[dstOff + 2], b, mix); + dst[dstOff + 3] = a; + // src[dstOff + 3]; + } + } + }, + + blur: function(mix) { + return this.convolve([1, 1, 1, 1, 1, 1, 1, 1, 1], mix, 9); + }, + + gaussianBlur: function(mix) { + return this.convolve([0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067, 0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292, 0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117, 0.00038771, 0.01330373, 0.11098164, 0.22508352, 0.11098164, 0.01330373, 0.00038771, 0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117, 0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292, 0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067], mix, 1); + }, + + sharpen: function(mix) { + return this.convolve([0, -1, 0, -1, 5, -1, 0, -1, 0], mix); + }, + + threshold: function(threshold) { + var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var pixels = data.data; + var r, g, b; + + for (var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i + 1]; + var b = pixels[i + 2]; + var v = (0.2126 * r + 0.7152 * g + 0.0722 * b >= threshold) ? 255 : 0; + pixels[i] = pixels[i + 1] = pixels[i + 2] = v + } + + this.context.putImageData(data, 0, 0); + + return this; + }, + + sepia: function() { + var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); + var pixels = data.data; + var r, g, b; + + for (var i = 0; i < pixels.length; i += 4) { + pixels[i + 0] = $.limitValue((pixels[i + 0] * .393) + (pixels[i + 1] * .769) + (pixels[i + 2] * .189), 0, 255); + pixels[i + 1] = $.limitValue((pixels[i + 0] * .349) + (pixels[i + 1] * .686) + (pixels[i + 2] * .168), 0, 255); + pixels[i + 2] = $.limitValue((pixels[i + 0] * .272) + (pixels[i + 1] * .534) + (pixels[i + 2] * .131), 0, 255); + } + + this.context.putImageData(data, 0, 0); + + return this; + }, + + measureText: function() { + return this.context.measureText.apply(this.context, arguments); + }, + + createRadialGradient: function() { + return this.context.createRadialGradient.apply(this.context, arguments); + }, + + createLinearGradient: function() { + return this.context.createLinearGradient.apply(this.context, arguments); + }, + + getImageData: function() { + return this.context.getImageData.apply(this.context, arguments); + }, + + /* framework */ + + framework: function(args, context) { + if (context) { + this.tempContext = context === true ? args : context; + } + + for (var name in args) { + if (this[name]) this[name](args[name], undefined, undefined); + } + + this.tempContext = null; + + return this; + }, + + onStep: function(callback, interval) { + var self = this.tempContext || this; + var lastTick = Date.now(); + interval = interval || 25; + + this.timer = setInterval(function() { + var delta = Date.now() - lastTick; + lastTick = Date.now(); + callback.call(self, delta, lastTick); + }, interval); + + return this; + }, + + onRender: function(callback) { + var self = this.tempContext || this; + + var lastTick = Date.now(); + + function step() { + var delta = Date.now() - lastTick; + lastTick = Date.now(); + requestAnimationFrame(step) + callback.call(self, delta, lastTick); + }; + + requestAnimationFrame(step); + + return this; + }, + + onMouseMove: function(callback) { + var self = this.tempContext || this; + + if (!MOBILE) this.canvas.addEventListener("mousemove", function(e) { + var pos = $.mousePosition(e); + callback.call(self, pos.x, pos.y); + }); + + else this.canvas.addEventListener("touchmove", function(e) { + e.preventDefault(); + var pos = $.mousePosition(e); + callback.call(self, pos.x, pos.y); + }); + + return this; + }, + + onMouseDown: function(callback) { + var self = this.tempContext || this; + + if (!MOBILE) { + this.canvas.addEventListener("mousedown", function(e) { + var pos = $.mousePosition(e); + callback.call(self, pos.x, pos.y, e.button); + }); + } else { + this.canvas.addEventListener("touchstart", function(e) { + var pos = $.mousePosition(e); + callback.call(self, pos.x, pos.y, e.button); + }); + } + + return this; + }, + + onMouseUp: function(callback) { + var self = this.tempContext || this; + + if (!MOBILE) { + this.canvas.addEventListener("mouseup", function(e) { + var pos = $.mousePosition(e); + callback.call(self, pos.x, pos.y, e.button); + }); + } else { + this.canvas.addEventListener("touchend", function(e) { + var pos = $.mousePosition(e); + callback.call(self, pos.x, pos.y, e.button); + }); + } + + return this; + }, + + + onSwipe: function(callback, threshold, timeout) { + var self = this.tempContext || this; + + var swipeThr = threshold || 35; + var swipeTim = timeout || 350; + + var swipeSP = 0; + var swipeST = 0; + var swipeEP = 0; + var swipeET = 0; + + function swipeStart(e) { + e.preventDefault(); + swipeSP = $.mousePosition(e); + swipeST = Date.now(); + } + + function swipeUpdate(e) { + e.preventDefault(); + swipeEP = $.mousePosition(e); + swipeET = Date.now(); + } + + function swipeEnd(e) { + e.preventDefault(); + + var xDif = (swipeSP.x - swipeEP.x); + var yDif = (swipeSP.y - swipeEP.y); + var x = (xDif * xDif); + var y = (yDif * yDif); + var swipeDist = Math.sqrt(x + y); + var swipeTime = (swipeET - swipeST); + var swipeDir = undefined; + + if (swipeDist > swipeThr && swipeTime < swipeTim) { + if (Math.abs(xDif) > Math.abs(yDif)) { + if (xDif > 0) { + swipeDir = "left"; + } else { + swipeDir = "right"; + } + } else { + if (yDif > 0) { + swipeDir = "up"; + } else { + swipeDir = "down"; + } + } + callback.call(self, swipeDir); + } + } + + this.canvas.addEventListener("touchstart", function(e) { + swipeStart(e); + }); + this.canvas.addEventListener("touchmove", function(e) { + swipeUpdate(e); + }); + this.canvas.addEventListener("touchend", function(e) { + swipeEnd(e); + }); + this.canvas.addEventListener("mousedown", function(e) { + swipeStart(e); + }); + this.canvas.addEventListener("mousemove", function(e) { + swipeUpdate(e); + }); + this.canvas.addEventListener("mouseup", function(e) { + swipeEnd(e); + }); + + return this; + }, + + onKeyDown: function(callback) { + var self = this.tempContext || this; + + document.addEventListener("keydown", function(e) { + if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); + else var keyName = $.keycodes[e.which]; + callback.call(self, keyName); + }); + return this; + }, + + onKeyUp: function(callback) { + var self = this.tempContext || this; + + document.addEventListener("keyup", function(e) { + if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); + else var keyName = $.keycodes[e.which]; + callback.call(self, keyName); + }); + return this; + }, + + + onResize: function(callback) { + var self = this.tempContext || this; + + window.addEventListener("resize", function() { + callback.call(self, window.innerWidth, window.innerHeight); + }); + + callback.call(self, window.innerWidth, window.innerHeight); + + return this; + }, + + onDropImage: function(callback) { + var self = this.tempContext || this; + + document.addEventListener('drop', function(e) { + e.stopPropagation(); + e.preventDefault(); + + var file = e.dataTransfer.files[0]; + + if (!(/image/i).test(file.type)) return false; + var reader = new FileReader(); + + reader.onload = function(e) { + var image = new Image; + + image.onload = function() { + callback.call(self, this); + }; + + image.src = e.target.result; + }; + + reader.readAsDataURL(file); + + }); + + document.addEventListener("dragover", function(e) { + e.preventDefault(); + }); + + return this; + } + + }; + + /* extend wrapper with drawing context methods */ + + var methods = ["arc", "arcTo", "beginPath", "bezierCurveTo", "clearRect", "clip", "closePath", "createImageData", "createLinearGradient", "createRadialGradient", "createPattern", "drawFocusRing", "drawImage", "fill", "fillRect", "fillText", "getImageData", "isPointInPath", "lineTo", "measureText", "moveTo", "putImageData", "quadraticCurveTo", "rect", "restore", "rotate", "save", "scale", "setTransform", "stroke", "strokeRect", "strokeText", "transform", "translate"]; + for (var i = 0; i < methods.length; i++) { + var name = methods[i]; + if (!$.Wrapper.prototype[name]) $.Wrapper.prototype[name] = Function("this.context." + name + ".apply(this.context, arguments); return this;"); + }; + + /* create setters and getters */ + + var properties = ["canvas", "fillStyle", "font", "globalAlpha", "globalCompositeOperation", "lineCap", "lineJoin", "lineWidth", "miterLimit", "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", "strokeStyle", "textAlign", "textBaseline"]; + for (var i = 0; i < properties.length; i++) { + var name = properties[i]; + if (!$.Wrapper.prototype[name]) $.Wrapper.prototype[name] = Function("if(arguments.length) { this.context." + name + " = arguments[0]; return this; } else { return this.context." + name + "; }"); + }; + + /* color */ + + $.Color = function(data, type) { + if (arguments.length) this.parse(data); + } + + $.Color.prototype = { + parse: function(args, type) { + if (args[0] instanceof $.Color) { + this[0] = args[0][0]; + this[1] = args[0][1]; + this[2] = args[0][2]; + this[3] = args[0][3]; + return; + } + + if (typeof args === "string") { + var match = null; + + if (args[0] === "#") { + var rgb = $.hexToRgb(args); + this[0] = rgb[0]; + this[1] = rgb[1]; + this[2] = rgb[2]; + this[3] = 1.0; + } else if (match = args.match(/rgb\((.*),(.*),(.*)\)/)) { + this[0] = match[1] | 0; + this[1] = match[2] | 0; + this[2] = match[3] | 0; + this[3] = 1.0; + } else if (match = args.match(/rgba\((.*),(.*),(.*)\)/)) { + this[0] = match[1] | 0; + this[1] = match[2] | 0; + this[2] = match[3] | 0; + this[3] = match[4] | 0; + } else if (match = args.match(/hsl\((.*),(.*),(.*)\)/)) { + this.fromHsl(match[1], match[2], match[3]); + } else if (match = args.match(/hsv\((.*),(.*),(.*)\)/)) { + this.fromHsv(match[1], match[2], match[3]); + } + } else { + switch (type) { + case "hsl": + case "hsla": + + this.fromHsl(args[0], args[1], args[2], args[3]); + break; + + case "hsv": + case "hsva": + + this.fromHsv(args[0], args[1], args[2], args[3]); + break; + + default: + this[0] = args[0]; + this[1] = args[1]; + this[2] = args[2]; + this[3] = typeof args[3] === "undefined" ? 1.0 : args[3]; + break; + } + } + }, + + fromHsl: function() { + var components = arguments[0] instanceof Array ? arguments[0] : arguments; + var color = $.hslToRgb(components[0], components[1], components[2]); + + this[0] = color[0]; + this[1] = color[1]; + this[2] = color[2]; + this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; + }, + + fromHsv: function() { + var components = arguments[0] instanceof Array ? arguments[0] : arguments; + var color = $.hsvToRgb(components[0], components[1], components[2]); + + this[0] = color[0]; + this[1] = color[1]; + this[2] = color[2]; + this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; + }, + + toArray: function() { + return [this[0], this[1], this[2], this[3]]; + }, + + toRgb: function() { + return "rgb(" + this[0] + ", " + this[1] + ", " + this[2] + ")"; + }, + + toRgba: function() { + return "rgba(" + this[0] + ", " + this[1] + ", " + this[2] + ", " + this[3] + ")"; + }, + + toHex: function() { + return $.rgbToHex(this[0], this[1], this[2]); + }, + + toHsl: function() { + var c = $.rgbToHsl(this[0], this[1], this[2]); + c[3] = this[3]; + return c; + }, + + toHsv: function() { + var c = $.rgbToHsv(this[0], this[1], this[2]); + c[3] = this[3]; + return c; + }, + + gradient: function(target, steps) { + var targetColor = $.color(target); + }, + + shiftHsl: function() { + var hsl = this.toHsl(); + + var h = arguments[0] === null ? hsl[0] : $.wrapValue(hsl[0] + arguments[0], 0, 1); + var s = arguments[1] === null ? hsl[1] : $.limitValue(hsl[1] + arguments[1], 0, 1); + var l = arguments[2] === null ? hsl[2] : $.limitValue(hsl[2] + arguments[2], 0, 1); + + this.fromHsl(h, s, l); + + return this; + }, + + setHsl: function() { + var hsl = this.toHsl(); + + var h = arguments[0] === null ? hsl[0] : $.limitValue(arguments[0], 0, 1); + var s = arguments[1] === null ? hsl[1] : $.limitValue(arguments[1], 0, 1); + var l = arguments[2] === null ? hsl[2] : $.limitValue(arguments[2], 0, 1); + + this.fromHsl(h, s, l); + + return this; + } + + }; + + window["cq"] = window["CanvasQuery"] = $; + + if (typeof define === "function" && define.amd) { + define([], function() { + return $; + }); + } + +})(window); diff --git a/js/vendor/dataUriToBlob.js b/js/vendor/dataUriToBlob.js new file mode 100644 index 0000000..582aecb --- /dev/null +++ b/js/vendor/dataUriToBlob.js @@ -0,0 +1,47 @@ +window.dataUriToBlob = (function(){ +/** + * Blob constructor. + */ + +var Blob = window.Blob; + +/** + * ArrayBufferView support. + */ + +var hasArrayBufferView = new Blob([new Uint8Array(100)]).size == 100; + +/** + * Return a `Blob` for the given data `uri`. + * + * @param {String} uri + * @return {Blob} + * @api public + */ + +var dataUriToBlob = function(uri){ + var data = uri.split(',')[1]; + var bytes = atob(data); + var buf = new ArrayBuffer(bytes.length); + var arr = new Uint8Array(buf); + for (var i = 0; i < bytes.length; i++) { + arr[i] = bytes.charCodeAt(i); + } + + if (!hasArrayBufferView) arr = buf; + var blob = new Blob([arr], { type: mime(uri) }); + blob.slice = blob.slice || blob.webkitSlice; + return blob; +}; + +/** + * Return data uri mime type. + */ + +function mime(uri) { + return uri.split(';')[0].slice(5); +} + +return dataUriToBlob; + +})() diff --git a/js/vendor/gif-encode/GIFEncoder.js b/js/vendor/gif-encode/GIFEncoder.js new file mode 100755 index 0000000..01d3618 --- /dev/null +++ b/js/vendor/gif-encode/GIFEncoder.js @@ -0,0 +1,513 @@ +/**
+* This class lets you encode animated GIF files
+* Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
+* @author Kevin Weiner (original Java version - kweiner@fmsware.com)
+* @author Thibault Imbert (AS3 version - bytearray.org)
+* @version 0.1 AS3 implementation
+*/
+
+//import flash.utils.ByteArray;
+//import flash.display.BitmapData;
+//import flash.display.Bitmap;
+//import org.bytearray.gif.encoder.NeuQuant
+//import flash.net.URLRequestHeader;
+//import flash.net.URLRequestMethod;
+//import flash.net.URLRequest;
+//import flash.net.navigateToURL;
+
+GIFEncoder = function() {
+ for(var i = 0, chr = {}; i < 256; i++) {
+ chr[i] = String.fromCharCode(i);
+ }
+
+ function ByteArray(){
+ this.bin = [];
+ }
+
+ ByteArray.prototype.getData = function(){
+ for(var v = '', l = this.bin.length, i = 0; i < l; i++) {
+ v += chr[this.bin[i]];
+ }
+ return v;
+ }
+ ByteArray.prototype.writeByte = function(val){
+ this.bin.push(val);
+ }
+ ByteArray.prototype.writeUTFBytes = function(string) {
+ for(var l = string.length, i = 0; i < l; i++) {
+ this.writeByte(string.charCodeAt(i));
+ }
+ }
+ ByteArray.prototype.writeBytes = function(array, offset, length) {
+ for(var l = length || array.length, i = offset||0; i < l; i++) {
+ this.writeByte(array[i]);
+ }
+ }
+
+ var exports = {};
+ var width/*int*/ // image size
+ var height/*int*/;
+ var transparent/***/ = null; // transparent color if given
+ var transIndex/*int*/; // transparent index in color table
+ var repeat/*int*/ = -1; // no repeat
+ var delay/*int*/ = 0; // frame delay (hundredths)
+ var started/*Boolean*/ = false; // ready to output frames
+ var out/*ByteArray*/;
+ var image/*Bitmap*/; // current frame
+ var pixels/*ByteArray*/; // BGR byte array from frame
+ var indexedPixels/*ByteArray*/ // converted frame indexed to palette
+ var colorDepth/*int*/; // number of bit planes
+ var colorTab/*ByteArray*/; // RGB palette
+ var usedEntry/*Array*/ = new Array; // active palette entries
+ var palSize/*int*/ = 7; // color table size (bits-1)
+ var dispose/*int*/ = -1; // disposal code (-1 = use default)
+ var closeStream/*Boolean*/ = false; // close stream when finished
+ var firstFrame/*Boolean*/ = true;
+ var sizeSet/*Boolean*/ = false; // if false, get size from first frame
+ var sample/*int*/ = 1; // default sample interval for quantizer
+ var neuquantBrain = null; // allow loading in a prefab neural net
+
+ /**
+ * Sets the delay time between each frame, or changes it for subsequent frames
+ * (applies to last frame added)
+ * int delay time in milliseconds
+ * @param ms
+ */
+
+ var setDelay = exports.setDelay = function setDelay(ms/*int*/) {
+ delay = Math.round(ms / 10);
+ }
+
+ /**
+ * Sets the GIF frame disposal code for the last added frame and any
+ *
+ * subsequent frames. Default is 0 if no transparent color has been set,
+ * otherwise 2.
+ * @param code
+ * int disposal code.
+ */
+
+ var setDispose = exports.setDispose = function setDispose(code/*int*/) {
+ if (code >= 0) dispose = code;
+ }
+
+ /**
+ * Sets the number of times the set of GIF frames should be played. Default is
+ * 1; 0 means play indefinitely. Must be invoked before the first image is
+ * added.
+ *
+ * @param iter
+ * int number of iterations.
+ * @return
+ */
+
+ var setRepeat = exports.setRepeat = function setRepeat(iter/*int*/) {
+ if (iter >= 0) repeat = iter;
+ }
+
+ /**
+ * Sets the transparent color for the last added frame and any subsequent
+ * frames. Since all colors are subject to modification in the quantization
+ * process, the color in the final palette for each frame closest to the given
+ * color becomes the transparent color for that frame. May be set to null to
+ * indicate no transparent color.
+ * @param
+ * Color to be treated as transparent on display.
+ */
+
+ var setTransparent = exports.setTransparent = function setTransparent(c/*Number*/) {
+ transparent = c;
+ }
+
+ /**
+ * The addFrame method takes an incoming BitmapData object to create each frames
+ * @param
+ * BitmapData object to be treated as a GIF's frame
+ */
+
+ /*Boolean*/
+ var addFrame = exports.addFrame = function addFrame(im/*BitmapData*/, is_imageData) {
+ if ((im == null) || ! started || out == null) {
+ throw new Error ("Please call start method before calling addFrame");
+ return false;
+ }
+
+ var ok/*Boolean*/ = true;
+
+ try {
+ if ( ! is_imageData) {
+ image = im.getImageData(0,0, im.canvas.width, im.canvas.height).data;
+ if ( ! sizeSet) {
+ setSize(im.canvas.width, im.canvas.height);
+ }
+ }
+ else {
+ image = im;
+ }
+ getImagePixels(); // convert to correct format if necessary
+ analyzePixels(); // build color table & map pixels
+
+ if (firstFrame) {
+ writeLSD(); // logical screen descriptior
+ writePalette(); // global color table
+ if (repeat >= 0) {
+ // use NS app extension to indicate reps
+ writeNetscapeExt();
+ }
+ }
+
+ writeGraphicCtrlExt(); // write graphic control extension
+ writeImageDesc(); // image descriptor
+ if (!firstFrame) {
+ writePalette(); // local color table
+ }
+ writePixels(); // encode and write pixel data
+ firstFrame = false;
+ }
+ catch (e/*Error*/) {
+ ok = false;
+ }
+ return ok;
+ }
+
+ /**
+ * Adds final trailer to the GIF stream, if you don't call the finish method
+ * the GIF stream will not be valid.
+ */
+
+ /*Boolean*/
+ var finish = exports.finish = function finish() {
+ if ( ! started) {
+ return false;
+ }
+
+ var ok/*Boolean*/ = true;
+ started = false;
+ try {
+ out.writeByte(0x3b); // gif trailer
+ }
+ catch (e/*Error*/) {
+ ok = false;
+ }
+ return ok;
+ }
+
+ /**
+ * Resets some members so that a new stream can be started.
+ * This method is actually called by the start method
+ */
+
+ var reset = function reset () {
+ // reset for subsequent use
+ transIndex = 0;
+ image = null;
+ pixels = null;
+ indexedPixels = null;
+ colorTab = null;
+ closeStream = false;
+ firstFrame = true;
+ }
+
+ /**
+ * * Sets frame rate in frames per second. Equivalent to
+ * <code>setDelay(1000/fps)</code>.
+ * @param fps
+ * float frame rate (frames per second)
+ */
+
+ var setFrameRate = exports.setFrameRate = function setFrameRate(fps/*Number*/) {
+ if (fps != 0xf) {
+ delay = Math.round(100/fps);
+ }
+ }
+
+ /**
+ * Sets quality of color quantization (conversion of images to the maximum 256
+ * colors allowed by the GIF specification). Lower values (minimum = 1)
+ * produce better colors, but slow processing significantly. 10 is the
+ * default, and produces good color mapping at reasonable speeds. Values
+ * greater than 20 do not yield significant improvements in speed.
+ * @param quality
+ * int greater than 0.
+ * @return
+ */
+
+ var setQuality = exports.setQuality = function setQuality(quality/*int*/) {
+ sample = Math.max(1, quality);
+ }
+
+ /**
+ * Sets the GIF frame size. The default size is the size of the first frame
+ * added if this method is not invoked.
+ * @param w
+ * int frame width.
+ * @param h
+ * int frame width.
+ */
+
+ var setSize = exports.setSize = function setSize(w/*int*/, h/*int*/) {
+ if (started && !firstFrame) {
+ return;
+ }
+ width = w;
+ height = h;
+ if (width < 1) width = 320;
+ if (height < 1) height = 240;
+ sizeSet = true;
+ }
+
+ /**
+ * After running the Neuquant on some test frames, it can be exported and then loaded
+ * into an uninitialized NQ instance on another worker and used accordingly.
+ */
+ var setNeuquant = exports.setNeuquant = function setNeuquant(neuquant, colors){
+ neuquantBrain = neuquant;
+ colorTab = colors;
+ }
+
+ /**
+ * Initiates GIF file creation on the given stream.
+ * @param os
+ * OutputStream on which GIF images are written.
+ * @return false if initial write failed.
+ */
+
+ var start = exports.start = function start() {
+ reset();
+ var ok/*Boolean*/ = true;
+ closeStream = false;
+ out = new ByteArray;
+ try {
+ out.writeUTFBytes("GIF89a"); // header
+ } catch (e/*Error*/) {
+ ok = false;
+ }
+
+ return started = ok;
+ }
+
+ var cont = exports.cont = function cont() {
+ reset();
+ var ok = true;
+ closeStream = false;
+ out = new ByteArray ();
+ return started = ok;
+ }
+
+ /**
+ * Analyzes image colors and creates color map.
+ */
+
+ var analyzePixels = function analyzePixels() {
+ var len = pixels.length;
+ var nPix = len / 3;
+ indexedPixels = [];
+ // initialize quantizer
+
+ var nq;
+ if (neuquantBrain && colorTab) {
+ nq = new NeuQuant();
+ nq.load(neuquantBrain);
+ }
+ else {
+ nq = new NeuQuant (pixels, len, sample);
+ colorTab = nq.process(); // create reduced palette
+ }
+
+ // map image pixels to new palette
+ var k = 0;
+ for (var j = 0; j < nPix; j++) {
+ var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
+ usedEntry[index] = true;
+ indexedPixels[j] = index;
+ }
+ pixels = null;
+ colorDepth = 8;
+ palSize = 7;
+
+ // get closest match to transparent color if specified
+ if (transparent != null) {
+ transIndex = findClosest(transparent);
+ }
+ }
+
+ /**
+ * Returns index of palette color closest to c
+ */
+
+ var findClosest = function findClosest(c/*Number*/) {
+ if (colorTab == null) return -1;
+ var r = (c & 0xFF0000) >> 16;
+ var g = (c & 0x00FF00) >> 8;
+ var b = (c & 0x0000FF);
+ var minpos = 0;
+ var dmin = 256 * 256 * 256;
+ var len = colorTab.length;
+
+ for (var i = 0; i < len;) {
+ var dr = r - (colorTab[i++] & 0xff);
+ var dg = g - (colorTab[i++] & 0xff);
+ var db = b - (colorTab[i] & 0xff);
+ var d = dr * dr + dg * dg + db * db;
+ var index = i / 3;
+ if (usedEntry[index] && (d < dmin)) {
+ dmin = d;
+ minpos = index;
+ }
+ i++;
+ }
+ return minpos;
+ }
+
+ /**
+ * Extracts image pixels into byte array "pixels
+ */
+
+ var getImagePixels = function getImagePixels() {
+ var w = width;
+ var h = height;
+ pixels = [];
+ var data = image;
+ var count/*int*/ = 0;
+
+ for ( var i/*int*/ = 0; i < h; i++ ) {
+ for (var j/*int*/ = 0; j < w; j++ ) {
+ var b = (i*w*4)+j*4;
+ pixels[count++] = data[b];
+ pixels[count++] = data[b+1];
+ pixels[count++] = data[b+2];
+ }
+ }
+ }
+
+ /**
+ * Writes Graphic Control Extension
+ */
+
+ var writeGraphicCtrlExt = function writeGraphicCtrlExt() {
+ out.writeByte(0x21); // extension introducer
+ out.writeByte(0xf9); // GCE label
+ out.writeByte(4); // data block size
+ var transp/*int*/
+ var disp/*int*/;
+ if (transparent == null) {
+ transp = 0;
+ disp = 0; // dispose = no action
+ }
+ else {
+ transp = 1;
+ disp = 2; // force clear if using transparent color
+ }
+ if (dispose >= 0) {
+ disp = dispose & 7; // user override
+ }
+ disp <<= 2;
+ // packed fields
+ out.writeByte(0 | // 1:3 reserved
+ disp | // 4:6 disposal
+ 0 | // 7 user input - 0 = none
+ transp); // 8 transparency flag
+
+ WriteShort(delay); // delay x 1/100 sec
+ out.writeByte(transIndex); // transparent color index
+ out.writeByte(0); // block terminator
+ }
+
+ /**
+ * Writes Image Descriptor
+ */
+
+ var writeImageDesc = function writeImageDesc() {
+ out.writeByte(0x2c); // image separator
+ WriteShort(0); // image position x,y = 0,0
+ WriteShort(0);
+ WriteShort(width); // image size
+ WriteShort(height);
+
+ // packed fields
+ if (firstFrame) {
+ // no LCT - GCT is used for first (or only) frame
+ out.writeByte(0);
+ }
+ else {
+ // specify normal LCT
+ out.writeByte(0x80 | // 1 local color table 1=yes
+ 0 | // 2 interlace - 0=no
+ 0 | // 3 sorted - 0=no
+ 0 | // 4-5 reserved
+ palSize); // 6-8 size of color table
+ }
+ }
+
+ /**
+ * Writes Logical Screen Descriptor
+ */
+
+ var writeLSD = function writeLSD() {
+ // logical screen size
+ WriteShort(width);
+ WriteShort(height);
+ // packed fields
+ out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used)
+ 0x70 | // 2-4 : color resolution = 7
+ 0x00 | // 5 : gct sort flag = 0
+ palSize)); // 6-8 : gct size
+
+ out.writeByte(0); // background color index
+ out.writeByte(0); // pixel aspect ratio - assume 1:1
+ }
+
+ /**
+ * Writes Netscape application extension to define repeat count.
+ */
+
+ var writeNetscapeExt = function writeNetscapeExt() {
+ out.writeByte(0x21); // extension introducer
+ out.writeByte(0xff); // app extension label
+ out.writeByte(11); // block size
+ out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
+ out.writeByte(3); // sub-block size
+ out.writeByte(1); // loop sub-block id
+ WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
+ out.writeByte(0); // block terminator
+ }
+
+ /**
+ * Writes color table
+ */
+ var writePalette = function writePalette() {
+ out.writeBytes(colorTab);
+ var n/*int*/ = (3 * 256) - colorTab.length;
+ for (var i/*int*/ = 0; i < n; i++) {
+ out.writeByte(0);
+ }
+ }
+
+ var WriteShort = function WriteShort (pValue/*int*/) {
+ out.writeByte( pValue & 0xFF );
+ out.writeByte( (pValue >> 8) & 0xFF);
+ }
+
+ /**
+ * Encodes and writes pixel data
+ */
+ var writePixels = function writePixels() {
+ var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
+ myencoder.encode(out);
+ }
+
+ /**
+ * retrieves the GIF stream
+ */
+ var stream = exports.stream = function stream () {
+ return out;
+ }
+
+ var setProperties = exports.setProperties = function setProperties(has_start, is_first) {
+ started = has_start;
+ firstFrame = is_first;
+ }
+
+ return exports;
+}
+
diff --git a/js/vendor/gif-encode/LZWEncoder.js b/js/vendor/gif-encode/LZWEncoder.js new file mode 100644 index 0000000..e3c512a --- /dev/null +++ b/js/vendor/gif-encode/LZWEncoder.js @@ -0,0 +1,328 @@ +/**
+* This class handles LZW encoding
+* Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
+* @author Kevin Weiner (original Java version - kweiner@fmsware.com)
+* @author Thibault Imbert (AS3 version - bytearray.org)
+* @version 0.1 AS3 implementation
+*/
+
+//import flash.utils.ByteArray;
+
+LZWEncoder = function()
+{
+ var exports = {};
+ /*private_static*/ var EOF/*int*/ = -1;
+ /*private*/ var imgW/*int*/;
+ /*private*/ var imgH/*int*/
+ /*private*/ var pixAry/*ByteArray*/;
+ /*private*/ var initCodeSize/*int*/;
+ /*private*/ var remaining/*int*/;
+ /*private*/ var curPixel/*int*/;
+
+ // GIFCOMPR.C - GIF Image compression routines
+ // Lempel-Ziv compression based on 'compress'. GIF modifications by
+ // David Rowley (mgardi@watdcsu.waterloo.edu)
+ // General DEFINEs
+
+ /*private_static*/ var BITS/*int*/ = 12;
+ /*private_static*/ var HSIZE/*int*/ = 5003; // 80% occupancy
+
+ // GIF Image compression - modified 'compress'
+ // Based on: compress.c - File compression ala IEEE Computer, June 1984.
+ // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
+ // Jim McKie (decvax!mcvax!jim)
+ // Steve Davies (decvax!vax135!petsd!peora!srd)
+ // Ken Turkowski (decvax!decwrl!turtlevax!ken)
+ // James A. Woods (decvax!ihnp4!ames!jaw)
+ // Joe Orost (decvax!vax135!petsd!joe)
+
+ /*private*/ var n_bits/*int*/ // number of bits/code
+ /*private*/ var maxbits/*int*/ = BITS; // user settable max # bits/code
+ /*private*/ var maxcode/*int*/ // maximum code, given n_bits
+ /*private*/ var maxmaxcode/*int*/ = 1 << BITS; // should NEVER generate this code
+ /*private*/ var htab/*Array*/ = new Array;
+ /*private*/ var codetab/*Array*/ = new Array;
+ /*private*/ var hsize/*int*/ = HSIZE; // for dynamic table sizing
+ /*private*/ var free_ent/*int*/ = 0; // first unused entry
+
+ // block compression parameters -- after all codes are used up,
+ // and compression rate changes, start over.
+
+ /*private*/ var clear_flg/*Boolean*/ = false;
+
+ // Algorithm: use open addressing double hashing (no chaining) on the
+ // prefix code / next character combination. We do a variant of Knuth's
+ // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ // secondary probe. Here, the modular division first probe is gives way
+ // to a faster exclusive-or manipulation. Also do block compression with
+ // an adaptive reset, whereby the code table is cleared when the compression
+ // ratio decreases, but after the table fills. The variable-length output
+ // codes are re-sized at this point, and a special CLEAR code is generated
+ // for the decompressor. Late addition: construct the table according to
+ // file size for noticeable speed improvement on small files. Please direct
+ // questions about this implementation to ames!jaw.
+
+ /*private*/ var g_init_bits/*int*/;
+ /*private*/ var ClearCode/*int*/;
+ /*private*/ var EOFCode/*int*/;
+
+ // output
+ // Output the given code.
+ // Inputs:
+ // code: A n_bits-bit integer. If == -1, then EOF. This assumes
+ // that n_bits =< wordsize - 1.
+ // Outputs:
+ // Outputs code to the file.
+ // Assumptions:
+ // Chars are 8 bits long.
+ // Algorithm:
+ // Maintain a BITS character long buffer (so that 8 codes will
+ // fit in it exactly). Use the VAX insv instruction to insert each
+ // code in turn. When the buffer fills up empty it and start over.
+
+ /*private*/ var cur_accum/*int*/ = 0;
+ /*private*/ var cur_bits/*int*/ = 0;
+ /*private*/ var masks/*Array*/ = [ 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF ];
+
+ // Number of characters so far in this 'packet'
+ /*private*/ var a_count/*int*/;
+
+ // Define the storage for the packet accumulator
+ /*private*/ var accum/*ByteArray*/ = [];
+
+ var LZWEncoder = exports.LZWEncoder = function LZWEncoder (width/*int*/, height/*int*/, pixels/*ByteArray*/, color_depth/*int*/)
+ {
+
+ imgW = width;
+ imgH = height;
+ pixAry = pixels;
+ initCodeSize = Math.max(2, color_depth);
+
+ }
+
+ // Add a character to the end of the current packet, and if it is 254
+ // characters, flush the packet to disk.
+ var char_out = function char_out(c/*Number*/, outs/*ByteArray*/)/*void*/
+ {
+ accum[a_count++] = c;
+ if (a_count >= 254) flush_char(outs);
+
+ }
+
+ // Clear out the hash table
+ // table clear for block compress
+
+ var cl_block = function cl_block(outs/*ByteArray*/)/*void*/
+ {
+
+ cl_hash(hsize);
+ free_ent = ClearCode + 2;
+ clear_flg = true;
+ output(ClearCode, outs);
+
+ }
+
+ // reset code table
+ var cl_hash = function cl_hash(hsize/*int*/)/*void*/
+ {
+
+ for (var i/*int*/ = 0; i < hsize; ++i) htab[i] = -1;
+
+ }
+
+ var compress = exports.compress = function compress(init_bits/*int*/, outs/*ByteArray*/)/*void*/
+
+ {
+ var fcode/*int*/;
+ var i/*int*/ /* = 0 */;
+ var c/*int*/;
+ var ent/*int*/;
+ var disp/*int*/;
+ var hsize_reg/*int*/;
+ var hshift/*int*/;
+
+ // Set up the globals: g_init_bits - initial number of bits
+ g_init_bits = init_bits;
+
+ // Set up the necessary values
+ clear_flg = false;
+ n_bits = g_init_bits;
+ maxcode = MAXCODE(n_bits);
+
+ ClearCode = 1 << (init_bits - 1);
+ EOFCode = ClearCode + 1;
+ free_ent = ClearCode + 2;
+
+ a_count = 0; // clear packet
+
+ ent = nextPixel();
+
+ hshift = 0;
+ for (fcode = hsize; fcode < 65536; fcode *= 2)
+ ++hshift;
+ hshift = 8 - hshift; // set hash code range bound
+
+ hsize_reg = hsize;
+ cl_hash(hsize_reg); // clear hash table
+
+ output(ClearCode, outs);
+
+ outer_loop: while ((c = nextPixel()) != EOF)
+
+ {
+
+ fcode = (c << maxbits) + ent;
+ i = (c << hshift) ^ ent; // xor hashing
+
+ if (htab[i] == fcode)
+ {
+ ent = codetab[i];
+ continue;
+ } else if (htab[i] >= 0) // non-empty slot
+ {
+ disp = hsize_reg - i; // secondary hash (after G. Knott)
+ if (i == 0)
+ disp = 1;
+ do
+ {
+
+ if ((i -= disp) < 0) i += hsize_reg;
+
+ if (htab[i] == fcode)
+ {
+ ent = codetab[i];
+ continue outer_loop;
+ }
+ } while (htab[i] >= 0);
+ }
+
+ output(ent, outs);
+ ent = c;
+ if (free_ent < maxmaxcode)
+ {
+ codetab[i] = free_ent++; // code -> hashtable
+ htab[i] = fcode;
+ } else cl_block(outs);
+ }
+
+ // Put out the final code.
+ output(ent, outs);
+ output(EOFCode, outs);
+
+ }
+
+ // ----------------------------------------------------------------------------
+ var encode = exports.encode = function encode(os/*ByteArray*/)/*void*/
+ {
+ os.writeByte(initCodeSize); // write "initial code size" byte
+ remaining = imgW * imgH; // reset navigation variables
+ curPixel = 0;
+ compress(initCodeSize + 1, os); // compress and write the pixel data
+ os.writeByte(0); // write block terminator
+
+ }
+
+ // Flush the packet to disk, and reset the accumulator
+ var flush_char = function flush_char(outs/*ByteArray*/)/*void*/
+ {
+
+ if (a_count > 0)
+ {
+ outs.writeByte(a_count);
+ outs.writeBytes(accum, 0, a_count);
+ a_count = 0;
+ }
+
+ }
+
+ var MAXCODE = function MAXCODE(n_bits/*int*/)/*int*/
+ {
+
+ return (1 << n_bits) - 1;
+
+ }
+
+ // ----------------------------------------------------------------------------
+ // Return the next pixel from the image
+ // ----------------------------------------------------------------------------
+
+ var nextPixel = function nextPixel()/*int*/
+ {
+
+ if (remaining == 0) return EOF;
+
+ --remaining;
+
+ var pix/*Number*/ = pixAry[curPixel++];
+
+ return pix & 0xff;
+
+ }
+
+ var output = function output(code/*int*/, outs/*ByteArray*/)/*void*/
+
+ {
+ cur_accum &= masks[cur_bits];
+
+ if (cur_bits > 0) cur_accum |= (code << cur_bits);
+ else cur_accum = code;
+
+ cur_bits += n_bits;
+
+ while (cur_bits >= 8)
+
+ {
+
+ char_out((cur_accum & 0xff), outs);
+ cur_accum >>= 8;
+ cur_bits -= 8;
+
+ }
+
+ // If the next entry is going to be too big for the code size,
+ // then increase it, if possible.
+
+ if (free_ent > maxcode || clear_flg)
+ {
+
+ if (clear_flg)
+ {
+
+ maxcode = MAXCODE(n_bits = g_init_bits);
+ clear_flg = false;
+
+ } else
+ {
+
+ ++n_bits;
+
+ if (n_bits == maxbits) maxcode = maxmaxcode;
+
+ else maxcode = MAXCODE(n_bits);
+
+ }
+
+ }
+
+ if (code == EOFCode)
+ {
+
+ // At EOF, write the rest of the buffer.
+ while (cur_bits > 0)
+ {
+
+ char_out((cur_accum & 0xff), outs);
+ cur_accum >>= 8;
+ cur_bits -= 8;
+ }
+
+
+ flush_char(outs);
+
+ }
+
+ }
+ LZWEncoder.apply(this, arguments);
+ return exports;
+}
+
diff --git a/js/vendor/gif-encode/NeuQuant.js b/js/vendor/gif-encode/NeuQuant.js new file mode 100644 index 0000000..91424ba --- /dev/null +++ b/js/vendor/gif-encode/NeuQuant.js @@ -0,0 +1,538 @@ +/*
+* NeuQuant Neural-Net Quantization Algorithm
+* ------------------------------------------
+*
+* Copyright (c) 1994 Anthony Dekker
+*
+* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
+* "Kohonen neural networks for optimal colour quantization" in "Network:
+* Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
+* the algorithm.
+*
+* Any party obtaining a copy of these files from the author, directly or
+* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
+* world-wide, paid up, royalty-free, nonexclusive right and license to deal in
+* this software and documentation files (the "Software"), including without
+* limitation the rights to use, copy, modify, merge, publish, distribute,
+* sublicense, and/or sell copies of the Software, and to permit persons who
+* receive copies from any such party to do so, with the only requirement being
+* that this copyright notice remain intact.
+*/
+
+/*
+* This class handles Neural-Net quantization algorithm
+* @author Kevin Weiner (original Java version - kweiner@fmsware.com)
+* @author Thibault Imbert (AS3 version - bytearray.org)
+* @version 0.1 AS3 implementation
+*/
+
+//import flash.utils.ByteArray;
+
+NeuQuant = function() {
+ var exports = {};
+ var netsize = 128; /* number of colours used */
+
+ /* four primes near 500 - assume no image has a length so large */
+ /* that it is divisible by all four primes */
+
+ var prime1 = 499;
+ var prime2 = 491;
+ var prime3 = 487;
+ var prime4 = 503;
+ var minpicturebytes = (3 * prime4);
+
+ /* minimum size for input image */
+ /*
+ * Program Skeleton ---------------- [select samplefac in range 1..30] [read
+ * image from input file] pic = (unsigned char*) malloc(3*width*height);
+ * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
+ * image header, using writecolourmap(f)] inxbuild(); write output image using
+ * inxsearch(b,g,r)
+ */
+
+ /*
+ * Network Definitions -------------------
+ */
+
+ var maxnetpos = (netsize - 1);
+ var netbiasshift = 4; /* bias for colour values */
+ var ncycles = 100; /* no. of learning cycles */
+
+ /* defs for freq and bias */
+ var intbiasshift = 16; /* bias for fractions */
+ var intbias = (1 << intbiasshift);
+ var gammashift = 10; /* gamma = 1024 */
+ var gamma = (1 << gammashift);
+ var betashift = 10;
+ var beta = (intbias >> betashift); /* beta = 1/1024 */
+ var betagamma = (intbias << (gammashift - betashift));
+
+ /* defs for decreasing radius factor */
+ var initrad = (netsize >> 3); /* for 256 cols, radius starts */
+ var radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
+ var radiusbias = (1 << radiusbiasshift);
+ var initradius = (initrad * radiusbias); /* and decreases by a */
+ var radiusdec = 30; /* factor of 1/30 each cycle */
+
+ /* defs for decreasing alpha factor */
+ var alphabiasshift = 10; /* alpha starts at 1.0 */
+ var initalpha = (1 << alphabiasshift);
+ var alphadec /* biased by 10 bits */
+
+ /* radbias and alpharadbias used for radpower calculation */
+ var radbiasshift = 8;
+ var radbias = (1 << radbiasshift);
+ var alpharadbshift = (alphabiasshift + radbiasshift);
+
+ var alpharadbias = (1 << alpharadbshift);
+
+ /*
+ * Types and Global Variables --------------------------
+ */
+
+ var thepicture/*ByteArray*//* the input image itself */
+ var lengthcount; /* lengthcount = H*W*3 */
+ var samplefac; /* sampling factor 1..30 */
+
+ // typedef int pixel[4]; /* BGRc */
+ var network; /* the network itself - [netsize][4] */
+ var netindex = new Array();
+
+ /* for network lookup - really 256 */
+ var bias = new Array();
+
+ /* bias and freq arrays for learning */
+ var freq = new Array();
+ var radpower = new Array();
+
+ var NeuQuant = exports.NeuQuant = function NeuQuant(thepic, len, sample) {
+
+ // with no input, assume we'll load in a lobotomized neuquant later.
+ // otherwise, initialize the neural net stuff
+
+ if (thepic && len && sample) {
+ var i;
+ var p;
+
+ thepicture = thepic;
+ lengthcount = len;
+ samplefac = sample;
+
+ network = new Array(netsize);
+
+ for (i = 0; i < netsize; i++) {
+ network[i] = new Array(4);
+ p = network[i];
+ p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
+ freq[i] = intbias / netsize; /* 1/netsize */
+ bias[i] = 0;
+ }
+ }
+ }
+
+ var colorMap = function colorMap() {
+ var map/*ByteArray*/ = [];
+ var index = new Array(netsize);
+ for (var i = 0; i < netsize; i++) {
+ index[network[i][3]] = i;
+ }
+ var k = 0;
+ for (var l = 0; l < netsize; l++) {
+ var j = index[l];
+ map[k++] = (network[j][0]);
+ map[k++] = (network[j][1]);
+ map[k++] = (network[j][2]);
+ }
+ return map;
+ }
+
+ /*
+ * Insertion sort of network and building of netindex[0..255] (to do after
+ * unbias)
+ * -------------------------------------------------------------------------------
+ */
+
+ var inxbuild = function inxbuild() {
+ var i;
+ var j;
+ var smallpos;
+ var smallval;
+ var p;
+ var q;
+ var previouscol
+ var startpos
+
+ previouscol = 0;
+ startpos = 0;
+ for (i = 0; i < netsize; i++) {
+ p = network[i];
+ smallpos = i;
+ smallval = p[1]; /* index on g */
+ /* find smallest in i..netsize-1 */
+ for (j = i + 1; j < netsize; j++) {
+ q = network[j];
+ if (q[1] < smallval) { /* index on g */
+ smallpos = j;
+ smallval = q[1]; /* index on g */
+ }
+ }
+
+ q = network[smallpos];
+ /* swap p (i) and q (smallpos) entries */
+
+ if (i != smallpos) {
+ j = q[0];
+ q[0] = p[0];
+ p[0] = j;
+ j = q[1];
+ q[1] = p[1];
+ p[1] = j;
+ j = q[2];
+ q[2] = p[2];
+ p[2] = j;
+ j = q[3];
+ q[3] = p[3];
+ p[3] = j;
+ }
+
+ /* smallval entry is now in position i */
+
+ if (smallval != previouscol) {
+ netindex[previouscol] = (startpos + i) >> 1;
+
+ for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
+
+ previouscol = smallval;
+ startpos = i;
+ }
+ }
+
+ netindex[previouscol] = (startpos + maxnetpos) >> 1;
+ for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */
+ }
+
+ /*
+ * Main Learning Loop ------------------
+ */
+
+ var learn = function learn() {
+ var i;
+ var j;
+ var b;
+ var g
+ var r;
+ var radius;
+ var rad;
+ var alpha;
+ var step;
+ var delta;
+ var samplepixels;
+ var p/*ByteArray*/;
+ var pix;
+ var lim;
+
+ if (lengthcount < minpicturebytes) samplefac = 1;
+
+ alphadec = 30 + ((samplefac - 1) / 3);
+ p = thepicture;
+ pix = 0;
+ lim = lengthcount;
+ samplepixels = lengthcount / (3 * samplefac);
+ delta = samplepixels / ncycles;
+ alpha = initalpha;
+ radius = initradius;
+
+ rad = radius >> radiusbiasshift;
+ if (rad <= 1) rad = 0;
+
+ for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
+
+ if (lengthcount < minpicturebytes) step = 3;
+ else if ((lengthcount % prime1) != 0) step = 3 * prime1;
+ else if ((lengthcount % prime2) != 0) step = 3 * prime2;
+ else if ((lengthcount % prime3) != 0) step = 3 * prime3;
+ else step = 3 * prime4;
+
+ i = 0;
+
+ while (i < samplepixels) {
+ b = (p[pix + 0] & 0xff) << netbiasshift;
+ g = (p[pix + 1] & 0xff) << netbiasshift;
+ r = (p[pix + 2] & 0xff) << netbiasshift;
+ j = contest(b, g, r);
+
+ altersingle(alpha, j, b, g, r);
+
+ if (rad != 0) alterneigh(rad, j, b, g, r); /* alter neighbours */
+
+ pix += step;
+
+ if (pix >= lim) pix -= lengthcount;
+
+ i++;
+
+ if (delta == 0) delta = 1;
+
+ if (i % delta == 0) {
+ alpha -= alpha / alphadec;
+ radius -= radius / radiusdec;
+ rad = radius >> radiusbiasshift;
+
+ if (rad <= 1) rad = 0;
+
+ for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
+ }
+ }
+ }
+
+
+ /* Save the neural network so we can load it back in on another worker.
+ */
+ var save = exports.save = function(){
+ var data = {
+ netindex: netindex,
+ netsize: netsize,
+ network: network
+ };
+ return data;
+ }
+ var load = exports.load = function(data){
+ netindex = data.netindex;
+ netsize = data.netsize;
+ network = data.network;
+ }
+
+
+ /*
+ ** Search for BGR values 0..255 (after net is unbiased) and return colour
+ * index
+ * ----------------------------------------------------------------------------
+ */
+
+ var map = exports.map = function map(b, g, r) {
+ var i;
+ var j;
+ var dist
+ var a;
+ var bestd;
+ var p;
+ var best;
+
+ bestd = 1000; /* biggest possible dist is 256*3 */
+ best = -1;
+ i = netindex[g]; /* index on g */
+ j = i - 1; /* start at netindex[g] and work outwards */
+
+ while ((i < netsize) || (j >= 0)) {
+ if (i < netsize) {
+ p = network[i];
+ dist = p[1] - g; /* inx key */
+ if (dist >= bestd) i = netsize; /* stop iter */
+ else {
+ i++;
+
+ if (dist < 0) dist = -dist;
+
+ a = p[0] - b;
+
+ if (a < 0) a = -a;
+
+ dist += a;
+
+ if (dist < bestd) {
+ a = p[2] - r;
+
+ if (a < 0) a = -a;
+
+ dist += a;
+
+ if (dist < bestd) {
+ bestd = dist;
+ best = p[3];
+ }
+ }
+ }
+ }
+ if (j >= 0) {
+ p = network[j];
+
+ dist = g - p[1]; /* inx key - reverse dif */
+
+ if (dist >= bestd) j = -1; /* stop iter */
+ else {
+ j--;
+ if (dist < 0) dist = -dist;
+ a = p[0] - b;
+ if (a < 0) a = -a;
+ dist += a;
+
+ if (dist < bestd) {
+ a = p[2] - r;
+ if (a < 0)a = -a;
+ dist += a;
+ if (dist < bestd) {
+ bestd = dist;
+ best = p[3];
+ }
+ }
+ }
+ }
+ }
+ return best;
+ }
+
+ var process = exports.process = function process() {
+ learn();
+ unbiasnet();
+ inxbuild();
+ return colorMap();
+ }
+
+ /*
+ * Unbias network to give byte values 0..255 and record position i to prepare
+ * for sort
+ * -----------------------------------------------------------------------------------
+ */
+
+ var unbiasnet = function unbiasnet() {
+ var i;
+ var j;
+ for (i = 0; i < netsize; i++) {
+ network[i][0] >>= netbiasshift;
+ network[i][1] >>= netbiasshift;
+ network[i][2] >>= netbiasshift;
+ network[i][3] = i; /* record colour no */
+ }
+ }
+
+ /*
+ * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
+ * radpower[|i-j|]
+ * ---------------------------------------------------------------------------------
+ */
+
+ var alterneigh = function alterneigh(rad, i, b, g, r) {
+ var j;
+ var k;
+ var lo;
+ var hi;
+ var a;
+ var m;
+ var p;
+
+ lo = i - rad;
+ if (lo < -1) lo = -1;
+
+ hi = i + rad;
+
+ if (hi > netsize) hi = netsize;
+
+ j = i + 1;
+ k = i - 1;
+ m = 1;
+
+ while ((j < hi) || (k > lo)) {
+ a = radpower[m++];
+ if (j < hi) {
+ p = network[j++];
+
+ try {
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
+ } catch (e/*Error*/) {} // prevents 1.3 miscompilation
+ }
+
+ if (k > lo) {
+ p = network[k--];
+ try {
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
+ } catch (e/*Error*/) {}
+ }
+ }
+ }
+
+ /*
+ * Move neuron i towards biased (b,g,r) by factor alpha
+ * ----------------------------------------------------
+ */
+
+ var altersingle = function altersingle(alpha, i, b, g, r) {
+ /* alter hit neuron */
+ var n = network[i];
+ n[0] -= (alpha * (n[0] - b)) / initalpha;
+ n[1] -= (alpha * (n[1] - g)) / initalpha;
+ n[2] -= (alpha * (n[2] - r)) / initalpha;
+ }
+
+ /*
+ * Search for biased BGR values ----------------------------
+ */
+
+ var contest = function contest(b, g, r) {
+ /* finds closest neuron (min dist) and updates freq */
+ /* finds best neuron (min dist-bias) and returns position */
+ /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
+ /* bias[i] = gamma*((1/netsize)-freq[i]) */
+
+ var i;
+ var dist;
+ var a;
+ var biasdist;
+ var betafreq;
+ var bestpos;
+ var bestbiaspos;
+ var bestd;
+ var bestbiasd;
+ var n;
+
+ bestd = ~(1 << 31);
+ bestbiasd = bestd;
+ bestpos = -1;
+ bestbiaspos = bestpos;
+
+ for (i = 0; i < netsize; i++) {
+ n = network[i];
+ dist = n[0] - b;
+
+ if (dist < 0) dist = -dist;
+
+ a = n[1] - g;
+
+ if (a < 0) a = -a;
+
+ dist += a;
+
+ a = n[2] - r;
+
+ if (a < 0) a = -a;
+
+ dist += a;
+
+ if (dist < bestd) {
+ bestd = dist;
+ bestpos = i;
+ }
+
+ biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
+
+ if (biasdist < bestbiasd) {
+ bestbiasd = biasdist;
+ bestbiaspos = i;
+ }
+
+ betafreq = (freq[i] >> betashift);
+ freq[i] -= betafreq;
+ bias[i] += (betafreq << gammashift);
+ }
+
+ freq[bestpos] += beta;
+ bias[bestpos] -= betagamma;
+ return (bestbiaspos);
+ }
+
+ NeuQuant.apply(this, arguments);
+ return exports;
+}
diff --git a/js/vendor/gif-encode/client.js b/js/vendor/gif-encode/client.js new file mode 100644 index 0000000..a6c09ec --- /dev/null +++ b/js/vendor/gif-encode/client.js @@ -0,0 +1,260 @@ +// Total frames to record +var FRAMES_PER_GIF = 36; + +// Frames per second to read from the video +var FPS = 12; + +// Per-frame delay in milliseconds +var DELAY = Math.floor( 1000 / FPS ); + +// Number of WebWorkers to create +var WORKERS = 4; + +// Number of frames to use to build the gif palette (takes longest) +var FRAMES_TO_QUANTIZE = 4; + +// Upload these gifs when finished?? +var DO_UPLOAD = true; + +function GifEncoder(){ + var base = this; + this.working = false; + var canvases = []; + var contexts = []; + var frames = []; + var delays = []; + + var initted = Date.now(); + var started = Date.now(); + var tube = base.tube = new Tube () + + var workers = new Factory (); + + var width, height; + var neuquant, colortab; + + workers.hire("message", receiveMessage); + workers.hire("quantize", receiveQuantize); + workers.hire("encode", receiveEncode); + + var reset = this.reset = function(){ + canvases = []; + contexts = []; + frames = []; + delays = []; + width = 0; + height = 0; + neuquant = null; + colortab = null; + base.quantized = false + } + var resetFrames = this.resetFrames = function(){ + canvases = []; + contexts = []; + frames = []; + delays = []; + width = 0; + height = 0; + } + + this.on = function(){ + base.tube.on.apply(base.tube, arguments) + }; + + this.off = function(){ + base.tube.off.apply(base.tube, arguments) + }; + + var addFrame = this.addFrame = function(canvas, delay) { + var ctx = canvas.getContext('2d'); + canvases.push(canvas); + contexts.push(ctx); + delays.push(delay); + + if (canvases.length == 1) { + width = canvas.width; + height = canvas.height; + } + } + + var copyFrame = this.copyFrame = function(canvas, delay) { + var newCanvas = document.createElement("canvas"); + var ctx = newCanvas.getContext('2d'); + + ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height); + + canvases.push(newCanvas); + contexts.push(ctx); + delays.push(delay); + + if (canvases.length == 1) { + width = canvas.width; + height = canvas.height; + } + } + + function Factory () { + var base = this; + var w = 0; // which worker to work next + var ww = []; + base.init = function(){ + for (var i = 0; i < WORKERS; i++) { + var worker = new Worker('gif-encode/worker.js'); + worker.onmessage = base.receiveWork; + ww.push(worker); + } + } + var tasks = {}; + base.hire = function(task, cb){ + tasks[task] = cb; + } + base.work = function(job){ + ww[++w % ww.length].postMessage(job); + } + base.receiveWork = function(e){ + e.data.task in tasks && tasks[e.data.task](e); + } + base.init(); + } + + function receiveMessage(e){ + console.log("[WORKER]", e.data.message); + } + + var neuquant, colortab; + var quantize = this.quantize = function () { + initted = Date.now(); + started = Date.now(); + var spritedata = spriteSheet(FRAMES_TO_QUANTIZE); + + workers.work({ + task: 'quantize', + imageData: spritedata + }); + } + + function receiveQuantize(e) { + console.log(Date.now() - started, "quantization done"); + neuquant = e.data.neuquant; + colortab = e.data.colortab; + base.quantized = true + base.tube("quantized") + } + + var encode = this.encode = function (nq, ct) { + nq = nq || neuquant + ct = ct || colortab + + started = Date.now(); + + console.log('working .... '); + var i = 0; + + function sendWork () { + if (i == canvases.length) return doneSending(); + + var ctx = contexts[i]; + var imdata = ctx.getImageData(0, 0, width, height).data; + var delay = delays[i]; + + workers.work({ + task: 'encode', + frame_index: i, + frame_length: contexts.length-1, + height: height, + width: width, + delay: delay, + imageData: imdata, + neuquant: neuquant, + colortab: colortab + }); + + i++; + setTimeout(sendWork, 16); + } + function doneSending(){ + base.tube("doneSending") + // ui.doneEncodingPicture(); + } + sendWork(); + } + + function receiveEncode(e){ + var frame_index = e.data["frame_index"]; + var frame_data = e.data["frame_data"]; + + frames[frame_index] = frame_data; + for (var j = 0; j < canvases.length; j++) { + if (frames[j] == null) { + return; + } + } + console.log("FINISHED " + canvases.length); + var binary_gif = frames.join(''); + var base64_gif = window.btoa(binary_gif); + var data_url = 'data:image/gif;base64,'+base64_gif; + + base.working = false; + + // photo.setAttribute('src', data_url); + // ui.doneEncodingPicture(); + base.tube("rendered", binary_gif) + base.tube("rendered-url", data_url) +// if (DO_UPLOAD) upload( base64_gif ); + + console.log((Date.now() - started), "processed frames"); + console.log((Date.now() - initted), "done"); + } + +// function upload (base64_gif) { +// $("#working").html("UPLOADING") +// +// console.log("starting upload") +// var params = { +// url: base64_gif +// } +// $.ajax({ +// 'url': "/photos.json", +// 'type': 'post', +// 'data': csrf(params), +// 'success': function(data){ +// +// // $("#share").data("href", "/photos/" + data.hash) +// // $("#share, #make-another").fadeIn(400); +// console.log(data); +// console.log((Date.now() - started), "uploaded"); +// // $("#photo").attr("src", data.url); +// // window.location.href = "/photos/" + data.hash +// localStorage.setItem('hash', data.hash) +// window.location.href = "/" +// // data.hash +// } +// }); +// console.log("ok"); +// } + + function spriteSheet (frameCount) { + var start = Date.now(); + frameCount = Math.min(contexts.length, frameCount); + var sprites = document.createElement("canvas"); + var spriteContext = sprites.getContext('2d'); + sprites.width = width; + sprites.height = height * frameCount; + var spritedata = spriteContext.getImageData(0, 0, sprites.width, sprites.height) + var spritedatadata = spritedata.data + var j = 0; + var ctxz = sample(contexts, 4); + while (frameCount--) { + var ctx = ctxz[frameCount]; + var imdata = ctx.getImageData(0, 0, width, height).data; + for (var n = 0; n < imdata.length; j++, n++) { + spritedatadata[j] = imdata[n]; + } + } + // spriteContext.putImageData(spritedata, 0, 0, 0, 0, sprites.width, sprites.height); + // upload( sprites.toDataURL("image/png").split(",")[1] + console.log(Date.now() - start, "built spritesheet"); + return spritedata; + } + +} diff --git a/js/vendor/gif-encode/tube.js b/js/vendor/gif-encode/tube.js new file mode 100644 index 0000000..17d3bfd --- /dev/null +++ b/js/vendor/gif-encode/tube.js @@ -0,0 +1,323 @@ +var nextTick = (function(){ + // postMessage behaves badly on IE8 + if (window.ActiveXObject || !window.postMessage) { + var nextTick = function(fn) { + setTimeout(fn, 0); + } + } else { + // based on setZeroTimeout by David Baron + // - http://dbaron.org/log/20100309-faster-timeouts + var timeouts = [] + , name = 'next-tick-zero-timeout' + + window.addEventListener('message', function(e){ + if (e.source == window && e.data == name) { + if (e.stopPropagation) e.stopPropagation(); + if (timeouts.length) timeouts.shift()(); + } + }, true); + + var nextTick = function(fn){ + timeouts.push(fn); + window.postMessage(name, '*'); + } + } + + return nextTick; +})() + +var Uid = (function(){ + var id = 0 + return function(){ return id++ + "" } +})() + + +var tokenize = (function(){ + var tokenize = function(str, splitOn){ + return str + .trim() + .split(splitOn || tokenize.default); + }; + + tokenize.default = /\s+/g; + + return tokenize; +})() + +// globber("*".split(":"), "a:b:c".split(":")) => true +// globber("*:c".split(":"), "a:b:c".split(":")) => true +// globber("a:*".split(":"), "a:b:c".split(":")) => true +// globber("a:*:c".split(":"), "a:b:c".split(":")) => true + +// based on codegolf.stackexchange.com/questions/467/implement-glob-matcher +var globber = function(patterns, strings) { + // console.log("globber called with: " + patterns.join(":"), strings.join(":")) + var first = patterns[0], + rest = patterns.slice(1), + len = strings.length, + matchFound; + + if(first === '*') { + for(var i = 0; i <= len; ++i) { + // console.log("* " + i + " trying " + rest.join(":") + " with " + strings.slice(i).join(":")) + if(globber(rest, strings.slice(i))) return true; + } + return false; + } else { + matchFound = (first === strings[0]); + // console.log ("literal matching " + first + " " + strings[0] + " " + !!matched) + } + + return matchFound && ((!rest.length && !len) || globber(rest, strings.slice(1))); +}; + +var setproto = function(obj, proto){ + if (obj.__proto__) + obj.__proto__ = proto; + else + for (var key in proto) + obj[key] = proto[key]; +}; + + +var Tube = (function(){ + var globcache = {}; + var Tube = function(opts){ + opts = opts || {}; + if (opts.queue){ + var c = function(){ + var args = arguments; + // queueOrNextTick (function(){ c.send.apply(c, args) }); + nextTick (function(){ c.send.apply(c, args) }); + return c; + }; + } else { + var c = function(){ + c.send.apply(c, arguments); + return c; + }; + } + + setproto(c, Tube.proto); + c.listeners = {}; + c.globListeners = {}; + + return c; + }; + + Tube.total = {}; + Tube.proto = {}; + + /* + adds fns as listeners to a channel + + on("msg", fn, {opts}) + on("msg", [fn, fn2], {opts}) + on("msg msg2 msg3", fn, {opts}) + on({"msg": fn, "msg2": fn2}, {opts}) + */ + + Tube.proto.on = function(){ + var chan = this; + if (typeof arguments[0] === "string") { + //if (arguments.length > 1) { // on("msg", f) + var msgMap = {}; + msgMap[arguments[0]] = arguments[1]; + var opts = arguments[2] || {}; + } else { // on({"msg": f, ...}) + var msgMap = arguments[0]; + var opts = arguments[1] || {}; + } + + for (var string in msgMap){ + var msgs = string.split(" "); + var fs = msgMap[string]; + if (!Array.isArray(fs)) fs = [fs]; + + for(var i=0, f; f=fs[i]; i++){ + if (!f.uid) f.uid = Uid(); + } + + for(var i=0, msg; msg=msgs[i]; i++){ + var listeners = (msg.indexOf("*") === -1) ? + chan.listeners : + chan.globListeners; + + // todo: this probably wastes a lot of memory? + // make a copy of the listener, add to it, and replace the listener + // why not just push directly? + // send might be iterating over it... and that will fuck up the iteration + + listeners[msg] = (msg in listeners) ? + listeners[msg].concat(fs) : + fs.concat(); + } + } + + return chan; + }; + + /* + off() + off("a:b:c") + off(f) + off("a:b:c", f) + off("a:b:c d:e:f") + off([f, f2]) + off({"a": f, "b": f2}) + */ + + Tube.proto.off = function(){ var chan = this; + + var listeners, i, msgs, msg; + + // off() : delete all listeners. but replace, instead of delete + if (arguments.length === 0) { + chan.listeners = {}; + chan.globListeners = {}; + return chan; + } + + // off("a:b:c d:e:f") + // remove all matching listeners + if (arguments.length === 1 && typeof arguments[0] === "string"){ + // question... will this fuck up send if we delete in the middle of it dispatching? + msgs = arguments[0].split(" "); + + for (i=0; msg=msgs[i]; i++){ + delete chan.listeners[msg]; + delete chan.globListeners[msg]; + } + return chan; + } + + // off(f) or off([f, f2]) + // remove all matching functions + if (typeof arguments[0] === "function" || Array.isArray(arguments[0])) { + var fs = (typeof arguments[0] === "function") ? + [arguments[0]] : + arguments[0]; + // TODO + return chan; + } + + // off("a:b:c", f) or off({"a": f, "b": f2}) + if (arguments.length > 1) { // off("msg", f) + var msgMap = {}; + msgMap[arguments[0]] = arguments[1]; + } else { // off({"msg": f, ...}) + var msgMap = arguments[0]; + } + + for (var string in msgMap){ + msgs = string.split(" "); + + var fs = msgMap[string]; + if (typeof fs === "function") fs = [fs]; + + for(var i=0; msg=msgs[i]; i++){ + if (msg in chan.listeners) + listeners = chan.listeners; + else if (msg in chan.globListeners) + listeners = chan.globListeners; + else + continue; + + // gotta do this carefully in case we are still iterating through the listener in send + // build a new array and assign it to the property, instead of mutating it. + + // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length) + // console.log(listeners[msg].join(",")); + // console.log(fs.join(",")); + + listeners[msg] = listeners[msg].filter( + function(f){ return fs.indexOf(f) === -1 } + ); + + // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length) + + } + } + + return chan; + + }; + + /* + c = Tube() + c.on("foo", fn) + c("foo", "bar", []) + + will call fn("bar", [], "foo") + */ + + Tube.proto.send = function(msgString /*, data... */){ + // todo: don't do this? + if (!Tube.total[msgString]) Tube.total[msgString] = 0 + Tube.total[msgString]+=1; + + var listener, + listeners = this.listeners, + globListeners = this.globListeners, + //args = Array.prototype.splice.call(arguments, 1), + msgs = tokenize(msgString), + msg, f; + + if (arguments.length) { + var args = Array.prototype.splice.call(arguments, 1); + args.push(msgString); + + } else { + var args = []; + } + + for (var m=0; msg=msgs[m]; m++){ + + var fsToRun = []; + var uidKeyFnValue = {}; + var uidKeyMsgStringValue = {}; + + // note this will die on errors + // todo: implement http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/ + // exact matches + if (listener = listeners[msg]) { + for (var i=0; f=listener[i]; i++){ + // fsToRun.push([f, msg]); + uidKeyFnValue[f.uid] = f; + uidKeyMsgStringValue[f.uid] = msg; + } + } + + // glob matches + var msgSplit = msg.split(":"); + + for (var pattern in globListeners){ + + if (pattern !== "*") { // * always matches + var patternSplit = globcache[pattern] || (globcache[pattern] = pattern.split(":")); + if (!globber(patternSplit, msgSplit)) continue; + } + + listener = globListeners[pattern]; + + for (var i=0; f=listener[i]; i++){ + //f.apply(window, args); // hm possibly pass the actual message to the func + // fsToRun.push([f, msg]); + uidKeyFnValue[f.uid] = f; + uidKeyMsgStringValue[f.uid] = msg; + } + } + + var fns = []; + for (var f in uidKeyFnValue) fns.push(uidKeyFnValue[f]); + + for (var i=0, f; f=fns[i]; i++) + f.apply(f, args); + + } + return this; + }; + + return Tube; +})() + diff --git a/js/vendor/gif-encode/util.js b/js/vendor/gif-encode/util.js new file mode 100644 index 0000000..92d8129 --- /dev/null +++ b/js/vendor/gif-encode/util.js @@ -0,0 +1,15 @@ +function shuffle(a){ + var aa = new Array(a.length); + aa[0] = a[0]; + + for (var i = 1; i < a.length; i++) { + var j = Math.floor( Math.random() * i ); + aa[i] = aa[j]; + aa[j] = a[i]; + } + return aa; +} +function sample(a, n) { + var aa = shuffle(a); + return aa.slice(0,n); +} diff --git a/js/vendor/gif-encode/worker.js b/js/vendor/gif-encode/worker.js new file mode 100644 index 0000000..b2f32d4 --- /dev/null +++ b/js/vendor/gif-encode/worker.js @@ -0,0 +1,88 @@ +importScripts('LZWEncoder.js', 'NeuQuant.js', 'GIFEncoder.js'); + +self.onmessage = function(event) { + var data = event.data; + var task = data['task']; + switch (task) { + case 'encode': + encode (data); + break; + case 'quantize': + quantize(data); + break; + } +} + +function log(msg) { + self.postMessage({ + task: 'message', + message: msg + }); +} + +function quantize (data) { + var imageData = data["imageData"]; + var pixels = discardAlphaChannel( imageData.data ); + + var nq = new NeuQuant (pixels, pixels.length, 1); + var colortab = nq.process(); + + self.postMessage({ + task: 'quantize', + neuquant: nq.save(), + colortab: colortab + }); +} + +function discardAlphaChannel( imageData ) { + var pixels = []; + + for ( var i = 0, b = 0, _len = imageData.length; i < _len; b += 4 ) { + pixels[i++] = imageData[b]; + pixels[i++] = imageData[b+1]; + pixels[i++] = imageData[b+2]; + } + return pixels; +} + +function encode (data) { + var frame_index = data["frame_index"]; + var frame_length = data["frame_length"]; + var height = data["height"]; + var width = data["width"]; + var imageData = data["imageData"]; + var delay = data["delay"]; + var neuquant = data["neuquant"]; + var colortab = data["colortab"]; + + // Create a new GIFEncoder for every new worker + var encoder = new GIFEncoder(); + encoder.setRepeat(0); // loop forever + encoder.setQuality(1); + encoder.setSize(width, height); + encoder.setDelay(delay); + + if (frame_index == 0) { + encoder.start(); + } + else { + encoder.cont(); + encoder.setProperties(true, false); //started, firstFrame + } + + // Load the neural net here because the color table gets clobbered by encoder.start(); + encoder.setNeuquant(neuquant, colortab); + encoder.addFrame(imageData, true); + + if(frame_length == frame_index) { + encoder.finish(); + } + + self.postMessage({ + task: 'encode', + frame_index: frame_index, + frame_data: encoder.stream().getData() + }); + // on the page, search for the GIF89a to see the frame_index +}; + diff --git a/js/vendor/gif.js b/js/vendor/gif.js new file mode 100644 index 0000000..a2c22fa --- /dev/null +++ b/js/vendor/gif.js @@ -0,0 +1,1690 @@ +// gif.js by @timb :) +;(function(e,t,n,r){function i(r){if(!n[r]){if(!t[r]){if(e)return e(r);throw new Error("Cannot find module '"+r+"'")}var s=n[r]={exports:{}};t[r][0](function(e){var n=t[r][1][e];return i(n?n:e)},s,s.exports)}return n[r].exports}for(var s=0;s<r.length;s++)i(r[s]);return i})(typeof require!=="undefined"&&require,{1:[function(require,module,exports){var Benchmark = require('../../benchmark');
+var Tube = require('../../tube');
+var BufferLoader = require('../../bufferloader');
+
+var decode = require('./decode');
+var render = require('./render-canvas');
+
+var GIF = function(src, opts){
+
+ var gif = Object.create(GIF.proto);
+
+ gif.buf = {}; // buffers used to store binary data
+ gif.tube = Tube();
+ gif.benchmark = Benchmark();
+
+ if (src instanceof File){
+ gif.src = src;
+ }
+ else if (typeof src === 'string'){ // url
+ gif.src = src;
+ }
+
+ return gif;
+
+};
+
+
+/*
+states:
+empty
+buffering: fetching a resource (File, url) into an ArrayBuffer
+decoding: decode binary data into different data chunks
+rendering: render data into frames as canvas contexts, or into a texture for webgl, etc
+*/
+
+GIF.proto = {};
+
+GIF.setupBuffers = function(gif, abuf){
+ gif.buf.cursor = 0;
+ gif.buf.abuf = abuf;
+ gif.buf.u8 = new Uint8Array(abuf);
+ gif.buf.dv = new DataView(abuf);
+};
+
+GIF.proto.on = function(){ var gif = this;
+ gif.tube.on.apply(gif.tube, arguments)
+};
+GIF.proto.off = function(){ var gif = this;
+ gif.tube.off.apply(gif.tube, arguments)
+};
+
+GIF.proto.load = function(){ var gif = this;
+ // if (!gif.src) throw new Error('gif needs a src');
+
+ gif.loader = BufferLoader(gif.src, {benchmark: gif.benchmark});
+ gif.loader.on("load", function(abuf){
+ GIF.setupBuffers(gif, abuf)
+ gif.loaded = true;
+ gif.tube("loaded")
+ });
+ gif.loader.on("error", function(e, xhr){ gif.tube("error", e, xhr)} );
+ gif.loader.load();
+
+};
+
+GIF.proto.decode = function(){ var gif = this;
+
+ if (!gif.loaded) {
+ gif.on("loaded", gif.decode.bind(gif)) // ugh
+ gif.load();
+ return gif
+ }
+
+ // TODO: deal with
+ gif.extensions = [];
+ gif.frames = [];
+ //gif.cursor = 0;
+
+ decode(gif);
+
+};
+
+GIF.proto.render = function(){ var gif = this;
+ if (!gif.decoded){
+ gif.on("decoded", gif.render.bind(gif)) // ugh
+ gif.decode();
+ return gif
+ }
+
+ render(gif);
+
+};
+
+
+if (typeof modules !== "undefined") modules.exports = GIF;
+if (typeof window !== "undefined") window.GIF = GIF; +},{"../../benchmark":2,"../../tube":3,"../../bufferloader":4,"./decode":5,"./render-canvas":6}],2:[function(require,module,exports){// var b = Benchmark();
+// b.start("something") // start a timer named "something"
+// b.stop("something") // stop it
+// b.something <-- time it took between start and stop calls in ms
+// b.total <--- total ms of all benchmarks
+
+var Benchmark = function(){
+ var b = Object.create(Benchmark.proto);
+ b.total = 0;
+ return b;
+};
+Benchmark.proto = {};
+Benchmark.proto.start = function(name){
+ this[name+"_start_timestamp"] = Date.now();
+};
+Benchmark.proto.stop = function(name){
+ if (!this[name+"_start_timestamp"]) return;
+ var val = Date.now() - this[name+"_start_timestamp"];
+ delete this[name+"_start_timestamp"];
+ this[name] = val + (this[name] || 0);
+ this.total += val;
+};
+
+module.exports = Benchmark;
+
+// export Benchmark; +},{}],3:[function(require,module,exports){var setproto = require('./object/setproto');
+var tokenize = require('./string/tokenize');
+var globber = require('./string/globber');
+var Uid = require('./uid');
+var nextTick = require('./nexttick');
+
+ // import queueOrNextTick from 'lib/nexttick4';
+
+var globcache = {};
+
+var Tube = function(opts){
+
+ opts = opts || {};
+
+ if (opts.queue){
+ var c = function(){
+ var args = arguments;
+ // queueOrNextTick (function(){ c.send.apply(c, args) });
+ nextTick (function(){ c.send.apply(c, args) });
+ return c;
+ };
+ } else {
+ var c = function(){
+ c.send.apply(c, arguments);
+ return c;
+ };
+ }
+
+ setproto(c, Tube.proto);
+
+ c.listeners = {};
+ c.globListeners = {};
+
+ return c;
+};
+
+Tube.total = {};
+
+Tube.proto = {};
+
+
+/*
+adds fns as listeners to a channel
+
+on("msg", fn, {opts})
+on("msg", [fn, fn2], {opts})
+on("msg msg2 msg3", fn, {opts})
+on({"msg": fn, "msg2": fn2}, {opts})
+*/
+Tube.proto.on = function(){ var chan = this;
+
+ if (typeof arguments[0] === "string") {
+ //if (arguments.length > 1) { // on("msg", f)
+ var msgMap = {};
+ msgMap[arguments[0]] = arguments[1];
+ var opts = arguments[2] || {};
+ } else { // on({"msg": f, ...})
+ var msgMap = arguments[0];
+ var opts = arguments[1] || {};
+ }
+
+ for (var string in msgMap){
+
+ var msgs = string.split(" ");
+
+ var fs = msgMap[string];
+ if (!Array.isArray(fs)) fs = [fs];
+
+ for(var i=0, f; f=fs[i]; i++){
+ if (!f.uid) f.uid = Uid();
+ }
+
+ for(var i=0, msg; msg=msgs[i]; i++){
+
+ var listeners = (msg.indexOf("*") === -1) ?
+ chan.listeners :
+ chan.globListeners;
+
+ // todo: this probably wastes a lot of memory?
+ // make a copy of the listener, add to it, and replace the listener
+ // why not just push directly?
+ // send might be iterating over it... and that will fuck up the iteration
+ listeners[msg] = (msg in listeners) ?
+ listeners[msg].concat(fs) :
+ fs.concat();
+ }
+ }
+
+ return chan;
+
+};
+
+
+
+/*
+off()
+off("a:b:c")
+off(f)
+off("a:b:c", f)
+off("a:b:c d:e:f")
+off([f, f2])
+off({"a": f, "b": f2})
+*/
+Tube.proto.off = function(){ var chan = this;
+
+ var listeners, i, msgs, msg;
+
+ // off() : delete all listeners. but replace, instead of delete
+ if (arguments.length === 0) {
+ chan.listeners = {};
+ chan.globListeners = {};
+ return chan;
+ }
+
+ // off("a:b:c d:e:f")
+ // remove all matching listeners
+ if (arguments.length === 1 && typeof arguments[0] === "string"){
+ // question... will this fuck up send if we delete in the middle of it dispatching?
+ msgs = arguments[0].split(" ");
+
+ for (i=0; msg=msgs[i]; i++){
+ delete chan.listeners[msg];
+ delete chan.globListeners[msg];
+ }
+ return chan;
+ }
+
+ // off(f) or off([f, f2])
+ // remove all matching functions
+ if (typeof arguments[0] === "function" || Array.isArray(arguments[0])) {
+ var fs = (typeof arguments[0] === "function") ?
+ [arguments[0]] :
+ arguments[0];
+ // TODO
+ return chan;
+ }
+
+ // off("a:b:c", f) or off({"a": f, "b": f2})
+ if (arguments.length > 1) { // off("msg", f)
+ var msgMap = {};
+ msgMap[arguments[0]] = arguments[1];
+ } else { // off({"msg": f, ...})
+ var msgMap = arguments[0];
+ }
+
+ for (var string in msgMap){
+ msgs = string.split(" ");
+
+ var fs = msgMap[string];
+ if (typeof fs === "function") fs = [fs];
+
+ for(var i=0; msg=msgs[i]; i++){
+
+ if (msg in chan.listeners)
+ listeners = chan.listeners;
+ else if (msg in chan.globListeners)
+ listeners = chan.globListeners;
+ else
+ continue;
+
+ // gotta do this carefully in case we are still iterating through the listener in send
+ // build a new array and assign it to the property, instead of mutating it.
+
+ // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length)
+ // console.log(listeners[msg].join(","));
+ // console.log(fs.join(","));
+
+ listeners[msg] = listeners[msg].filter(
+ function(f){ return fs.indexOf(f) === -1 }
+ );
+ // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length)
+
+ }
+ }
+
+ return chan;
+
+};
+
+
+/*
+
+c = Tube()
+c.on("foo", fn)
+c("foo", "bar", [])
+
+will call fn("bar", [], "foo")
+
+*/
+Tube.proto.send = function(msgString /*, data... */){
+
+ // todo: don't do this?
+ if (!Tube.total[msgString]) Tube.total[msgString] = 0
+ Tube.total[msgString]+=1;
+
+ var listener,
+ listeners = this.listeners,
+ globListeners = this.globListeners,
+ //args = Array.prototype.splice.call(arguments, 1),
+ msgs = tokenize(msgString),
+ msg, f;
+
+ if (arguments.length) {
+ var args = Array.prototype.splice.call(arguments, 1);
+ args.push(msgString);
+
+ } else {
+ var args = [];
+ }
+
+ for (var m=0; msg=msgs[m]; m++){
+
+ var fsToRun = [];
+ var uidKeyFnValue = {};
+ var uidKeyMsgStringValue = {};
+
+ // note this will die on errors
+ // todo: implement http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/
+ // exact matches
+ if (listener = listeners[msg]) {
+ for (var i=0; f=listener[i]; i++){
+ // fsToRun.push([f, msg]);
+ uidKeyFnValue[f.uid] = f;
+ uidKeyMsgStringValue[f.uid] = msg;
+ }
+ }
+
+ // glob matches
+ var msgSplit = msg.split(":");
+
+ for (var pattern in globListeners){
+
+ if (pattern !== "*") { // * always matches
+ var patternSplit = globcache[pattern] || (globcache[pattern] = pattern.split(":"));
+ if (!globber(patternSplit, msgSplit)) continue;
+ }
+
+ listener = globListeners[pattern];
+
+ for (var i=0; f=listener[i]; i++){
+ //f.apply(window, args); // hm possibly pass the actual message to the func
+ // fsToRun.push([f, msg]);
+ uidKeyFnValue[f.uid] = f;
+ uidKeyMsgStringValue[f.uid] = msg;
+ }
+ }
+
+ var fns = [];
+ for (var f in uidKeyFnValue) fns.push(uidKeyFnValue[f]);
+
+ for (var i=0, f; f=fns[i]; i++)
+ f.apply(f, args);
+
+ }
+
+ return this;
+
+};
+
+module.exports = Tube; +},{"./object/setproto":7,"./string/tokenize":8,"./string/globber":9,"./uid":10,"./nexttick":11}],7:[function(require,module,exports){// TODO: replace all uses of
+// setproto(foo, proto)
+// with
+// foo.__proto__ = proto
+// when IE is no longer shit
+
+var setproto = function(obj, proto){
+ if (obj.__proto__)
+ obj.__proto__ = proto;
+ else
+ for (var key in proto)
+ obj[key] = proto[key];
+};
+module.exports = setproto; +},{}],8:[function(require,module,exports){// trimmed string into array of strings
+var tokenize = function(str, splitOn){
+ return str
+ .trim()
+ .split(splitOn || tokenize.default);
+};
+tokenize.default = /\s+/g;
+
+module.exports = tokenize; +},{}],9:[function(require,module,exports){// can use * to match 0 or more : separated msgs
+// globber("*".split(":"), "a:b:c".split(":")) => true
+// globber("*:c".split(":"), "a:b:c".split(":")) => true
+// globber("a:*".split(":"), "a:b:c".split(":")) => true
+// globber("a:*:c".split(":"), "a:b:c".split(":")) => true
+
+// based on codegolf.stackexchange.com/questions/467/implement-glob-matcher
+var globber = function(patterns, strings) {
+ // console.log("globber called with: " + patterns.join(":"), strings.join(":"))
+ var first = patterns[0],
+ rest = patterns.slice(1),
+ len = strings.length,
+ matchFound;
+
+ if(first === '*') {
+ for(var i = 0; i <= len; ++i) {
+ // console.log("* " + i + " trying " + rest.join(":") + " with " + strings.slice(i).join(":"))
+ if(globber(rest, strings.slice(i))) return true;
+ }
+ return false;
+ } else {
+ matchFound = (first === strings[0]);
+ // console.log ("literal matching " + first + " " + strings[0] + " " + !!matched)
+ }
+
+ return matchFound && ((!rest.length && !len) || globber(rest, strings.slice(1)));
+};
+module.exports = globber; +},{}],10:[function(require,module,exports){var Uid = function(){
+ return (Uid.counter++ + "");
+}
+
+Uid.counter = 1;
+
+module.exports = Uid; +},{}],11:[function(require,module,exports){// based on https://github.com/timoxley/next-tick/blob/master/index.js
+
+// postMessage behaves badly on IE8
+if (window.ActiveXObject || !window.postMessage) {
+
+ var nextTick = function(fn) {
+ setTimeout(fn, 0);
+ }
+
+} else {
+
+ // based on setZeroTimeout by David Baron
+ // - http://dbaron.org/log/20100309-faster-timeouts
+ var timeouts = []
+ , name = 'next-tick-zero-timeout'
+
+ window.addEventListener('message', function(e){
+ if (e.source == window && e.data == name) {
+ if (e.stopPropagation) e.stopPropagation();
+ if (timeouts.length) timeouts.shift()();
+ }
+ }, true);
+
+ var nextTick = function(fn){
+ timeouts.push(fn);
+ window.postMessage(name, '*');
+ }
+
+}
+
+module.exports = nextTick; +},{}],4:[function(require,module,exports){var Benchmark = require('./benchmark');
+var setproto = require('./object/setproto');
+var extend = require('./object/extend');
+var Tube = require('./tube');
+
+// usage: Buffer(url || File || ArrayBuffer)
+var BufferLoader = function(src, opts){
+
+ var loader = Tube();
+ setproto(loader, BufferLoader.proto);
+
+ if (opts && opts.benchmark) loader.benchmark = opts.benchmark;
+
+ //buf.remember("load error")
+ // loader.on("load", function(abuf){ setupBuffer(loader, abuf) }); // <-- hm, needs ref to buf
+
+ loader.src = src;
+
+ return loader;
+}
+
+BufferLoader.proto = {};
+extend(BufferLoader.proto, Tube.proto);
+
+BufferLoader.proto.load = function(){ var loader = this;
+ var src = loader.src;
+ if (typeof src === "string") loader.loadFromUrl(src)
+ else if (src instanceof File) loader.loadFromFile(src);
+ else if (src instanceof ArrayBuffer) loader("load", src);
+}
+
+BufferLoader.proto.loadFromFile = function(file){ var loader = this;
+
+ var r = new FileReader();
+
+ r.addEventListener('load', function(e){
+ if (loader.benchmark) loader.benchmark.stop("fetch-from-disk");
+ loader('load', r.result, e);
+ // console.log(r.file.name + " 100%: " + r.result.byteLength + " bytes")
+ });
+
+ r.addEventListener('error', function(e){
+ // console.log(arguments);
+ loader('error', e, r)
+ });
+
+ r.addEventListener('progress', function(e){
+ //console.log(r.file.name + " " + (e.loaded / e.total * 100) + "%: " + e.loaded + " bytes")
+ loader('progress', e)
+ });
+
+ if (loader.benchmark) loader.benchmark.start("fetch-from-disk");
+ r.readAsArrayBuffer(file);
+
+};
+
+
+BufferLoader.proto.loadFromUrl = function(url){ var loader=this;
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.responseType = "arraybuffer";
+
+ xhr.addEventListener('load', function(e){
+ if (loader.benchmark) loader.benchmark.stop("fetch-from-network");
+ // console.log(r.file.name + " 100%: " + r.result.byteLength + " bytes")
+ loader('load', xhr.response, e);
+ });
+
+ xhr.addEventListener('error', function(e){
+ // console.log(arguments);
+ loader('error', e, xhr);
+ });
+
+ xhr.addEventListener('progress', function(e){
+ //console.log(r.file.name + " " + (e.loaded / e.total * 100) + "%: " + e.loaded + " bytes")
+ loader('progress', e)
+ });
+
+ if (loader.benchmark) loader.benchmark.start("fetch-from-network");
+ // xhr.open("GET", url);
+
+ xhr.send();
+
+};
+
+module.exports = BufferLoader; +},{"./benchmark":2,"./object/setproto":7,"./object/extend":12,"./tube":3}],12:[function(require,module,exports){module.exports = function(destination, source) {
+ for (var property in source)
+ destination[property] = source[property];
+ return destination;
+}; +},{}],5:[function(require,module,exports){(function(){var blockSigs = require('./spec').blockSigs;
+var extSigs = require('./spec').extSigs;
+var palette = require('./palette');
+var makeCurrentFrame = require('./animate').makeCurrentFrame;
+
+var BinarySpecReader = require('../../binaryspec');
+var spec = require('./spec').spec;
+var specReader = BinarySpecReader(spec);
+
+var decode = function(gif, opts){
+ decodeNextPart(gif);
+};
+
+
+// this gets called when more binary data is ready to process
+// it gets set as BufferedReader's onload function
+/*
+var moreDataInBufferEvent = function(gif){
+ if (gif.waitingForFileBuffer) decodeNextPart(gif);
+};
+*/
+
+/*
+loads the next part of the gif, eventually calling the gif's onload function when every part has been loaded
+
+this function gets called when a previous part has finished loading,
+and when there's more data in the buffered reader
+
+if the next part has a fixed size according to spec, we know whether we have to wait on the buffer to fill or not
+if the next part has a variable size, call the howToDecode function which will try to load it
+*/
+var decodeNextPart = function(gif, nextPart){
+ //gif.waitingForFileBuffer = false;
+
+ nextPart = nextPart || "header";
+ var buf = gif.buf;
+
+ while (nextPart !== "done" && nextPart !== "error"){
+
+ if (nextPart in howToDecode && typeof howToDecode[nextPart] === "function") { // dont know size
+ nextPart = howToDecode[nextPart](gif);
+ } else { // we know exact size of next part
+
+ var partSize = specReader.parts[nextPart].byteSize;
+
+ if (buf.abuf.byteLength < partSize + buf.cursor){
+ gif.waitingForFileBuffer = true;
+ return;
+ } else {
+ var fields = specReader.decodeBinaryFieldsToJSON(nextPart, buf.cursor, buf);
+ buf.cursor += specReader.parts[nextPart].byteSize;
+ nextPart = handleLoadedPart[nextPart](gif, fields);
+ }
+ }
+ // todo: maybe do this when and return unknown from dataBlock read
+ // if (nextPart === "unknown") nextPart = "dataBlock"
+
+ }
+
+ if (nextPart === "done"){
+ // create palette from all frames' palettes
+
+ if (gif.benchmark) gif.benchmark.start("palette");
+ gif.paletteTotal = palette.create(gif);
+ if (gif.benchmark) gif.benchmark.stop("palette");
+
+ // todo: move elsewhere
+ makeCurrentFrame.bind(gif)();
+// GIF.decode.doneDecodingCleanup(gif);
+ //gif.tube("progress", "done");
+ gif.decoded = true;
+ gif.tube("decoded");
+ return;
+ }
+
+};
+
+
+var howToDecode = {
+ "globalPalette": function(gif){ var buf = gif.buf;
+ var paletteSize = gif.paletteSize * 3; // r,g,b bytes
+
+ if (buf.abuf.byteLength < buf.cursor + paletteSize) {
+ gif.waitingForFileBuffer = true;
+ return;
+ }
+
+ //gif.palette = GIF.palette.binary2rgba(gif.reader.buffer.slice(gif.cursor, gif.cursor + paletteSize));
+ gif.palette = palette.binary2rgba(new Uint8Array(buf.abuf, buf.cursor, paletteSize));
+ buf.cursor += paletteSize;
+
+ return "dataBlock";
+ },
+ "localPalette": function(gif){ var buf = gif.buf;
+ var paletteSize = gif.frames[gif.frames.length - 1].paletteSize * 3; // r,g,b bytes
+
+ if (buf.abuf.byteLength < buf.cursor + paletteSize) {
+ gif.waitingForFileBuffer = true;
+ return;
+ }
+
+ gif.frames[gif.frames.length - 1].palette = palette.binary2rgba(new Uint8Array(buf.abuf, buf.cursor, paletteSize));
+ buf.cursor += paletteSize;
+ return "imageData";
+ },
+ "dataBlock": function(gif){ var buf = gif.buf;
+
+ if (buf.abuf.byteLength < buf.cursor + 1) {
+ gif.waitingForFileBuffer = true;
+ return;
+ }
+
+ var blockType = "unknown",
+ extType = "unknown",
+ nextPart;
+
+ var blockSig = buf.u8[buf.cursor];
+
+ if (blockSig in blockSigs) {
+ blockType = blockSigs[blockSig];
+ } else {
+ //console.log("unknown data block type ("+ Number(blockSig).toString(16) +")!");
+ nextPart = "done";
+ }
+
+ if (blockType === "extension"){ // we need to determine what kind of extension the block is
+ // need next two bytes to find out what kind of extension the data block is
+ if (buf.abuf.byteLength < buf.cursor + 2) {
+ gif.waitingForFileBuffer = true;
+ return;
+ console.log("ran out of buffer")
+ }
+ var extSig = buf.u8[buf.cursor+1];
+ if (extSig in extSigs) {
+ extType = extSigs[extSig];
+ nextPart = extType;
+ } else {
+ //console.log("unknown extension type ("+ Number(extSig).toString(16) +")")
+ nextPart = "done";
+ }
+ } else {
+ if (blockType === "unknown") { blockType = "dataBlock"; buf.cursor += 1 }
+ nextPart = blockType;
+ }
+
+ return nextPart;
+
+ },
+ "trailer": function(gif){
+ return "done";
+ }
+};
+
+var handleLoadedPart = {
+ "header": function(gif, fields){
+
+ if (fields.signature != "GIF"){
+ gif.tube("error", "file doesn't seem to be a gif (file signature: '"+fields.signature+"')");
+ return "error";
+ }
+
+ // gif.version = fields.version
+
+ return "screenDesc";
+ },
+ "screenDesc": function(gif, fields){
+ for (var field in fields) gif[field] = fields[field];
+ // todo: make nicer
+ // this should be a u3 not this bit[3] bit-shifted horseshit
+ gif.paletteSize = (gif.paletteSize[0] << 2) + (gif.paletteSize[1] << 1) + (gif.paletteSize[2]);
+ gif.paletteSize = Math.pow(2, gif.paletteSize + 1)
+
+ if (gif.paletteExists) return "globalPalette";
+ else return "dataBlock";
+ },
+ "imageDesc": function(gif, fields){
+ // make a blank image frame if none exists or the last frame already has image data
+ // we don't know if a blank frame has already been created because a graphic control block might
+ // have come before this block and made one
+ if (!gif.frames.length || ("w" in gif.frames[gif.frames.length - 1]))
+ gif.frames.push({})
+
+ var frame = gif.frames[gif.frames.length - 1]
+
+ for (var field in fields) frame[field] = fields[field];
+ frame.paletteSize = (frame.paletteSize[0] << 2) + (frame.paletteSize[1] << 1) + (frame.paletteSize[2]);
+ frame.paletteSize = Math.pow(2, frame.paletteSize + 1)
+
+ if (frame.paletteExists) return "localPalette";
+ else return "imageData";
+ },
+ // if sublocks was able to read:
+ // increase cursor from subblocks
+ // increase cursor from fields
+ // else
+ // set waiting
+ // subtract 2 from cursor (for the 2 that was read to get the data block)
+ // subtract part.bytesize from cursor (not needed in this case?)
+ "applicationExtension": function(gif, fields){
+ var blockinfo = readSubBlocks(gif);
+ if (blockinfo === false) {
+ gif.waitingForFileBuffer = true
+ gif.buf.cursor -= specReader.parts.applicationExtension.byteSize
+ return;
+ } else {
+ var extension = {"data": blockinfo};
+ for(var field in fields)
+ extension[field] = fields[field]
+ gif.extensions.push(extension)
+ return "dataBlock";
+ }
+ },
+ "comment": function(gif, fields){
+ var blockinfo = readSubBlocks(gif);
+ if (blockinfo === false) {
+ gif.waitingForFileBuffer = true;
+ gif.buf.cursor -= specReader.parts.comment.byteSize;
+ return;
+ } else {
+ var extension = {"comment": blockinfo};
+ for(var field in fields)
+ extension[field] = fields[field];
+ gif.extensions.push(extension);
+ return "dataBlock";
+ }
+ },
+ "plainText": function(gif, fields){
+ var blockinfo = readSubBlocks(gif);
+ if (blockinfo === false) {
+ gif.waitingForFileBuffer = true;
+ gif.buf.cursor -= specReader.parts.plainText.byteSize;
+ return;
+ } else {
+ var extension = {"plainText": blockinfo};
+ for(var field in fields)
+ extension[field] = fields[field];
+ gif.extensions.push(extension);
+ return "dataBlock";
+ }
+ },
+ "graphicControl": function(gif, fields){
+ var dm = (fields.disposalMethod[0] << 2) + (fields.disposalMethod[1] << 1) + (fields.disposalMethod[2]);
+ gif.frames.push({"delay": fields.delay,
+ "transparentIndex": fields.transparentColor ? fields.transparentIndex : -1,
+ "disposalMethod": dm})
+ return "dataBlock";
+ },
+ "imageData": function(gif, fields){
+ var blockinfo = readSubBlocks(gif);
+ if (blockinfo === false) {
+ gif.waitingForFileBuffer = true
+ gif.buf.cursor -= specReader.parts.imageData.byteSize
+ console.log("fucked")
+ return;
+ } else {
+
+// TODO: ENABLE THIS!
+// gif.tube("progress", "found " + gif.frames.length + " frames");
+
+ var frame = gif.frames[gif.frames.length - 1];
+ //var palette = ("palette" in frame) ? frame.palette : gif.palette // local palette otherwise global palette
+ frame.lzwCodeSize = fields.lzwCodeSize;
+ frame.blockinfo = blockinfo;
+// var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1
+ return "dataBlock";
+ }
+
+ }
+};
+
+// read subblocks out of a gif's buffer...
+// reads block sizes and returns an object with a start cursor and an array of block ends
+// returns false if there's not enough data
+var readSubBlocks = function(gif){
+
+ if (gif.benchmark) gif.benchmark.start("read-subblocks");
+
+ var blockEnds = [];
+
+ var buf = gif.buf,
+ u8 = buf.u8,
+ byteLength = u8.byteLength;
+
+ // gif.cursor is for whole file... pos is a cursor for just this blob
+ var startBlockCursor = buf.cursor;
+ var pos = buf.cursor;
+ startBlockCursor += 1;
+ var byteSize = 0;
+ var outOfData = false
+
+ // only actually advance cursor if we can read in all the sub blocks from the buffer
+ var cursorTemp = 0;
+
+ while(!outOfData){
+ // get block size
+ if (byteLength < pos + 1) { outOfData = true; break;}
+ byteSize = u8[pos];
+ pos += 1;
+
+ // a sub block with size 0 indicates end of sub blocks
+ if (byteSize === 0) { cursorTemp += 1; break;}
+
+ // read block
+ if (byteLength < pos + byteSize) { outOfData = true; break;}
+ blockEnds.push(pos + byteSize);
+
+ pos += byteSize
+ cursorTemp += byteSize + 1
+ // gif.subBlocksRead += 1
+ }
+
+ // TODO? CLEAN UP!
+ if (outOfData) {
+ // gif.bufferMisses += 1
+ // gif.benchmark.wasted += (Date.now() - start) / 1000
+ console.log("out of data")
+ return false
+
+ } else { // end of sub blocks happened
+ buf.cursor += cursorTemp
+ //if ("onprogress" in gif) gif.onprogress(gif, Math.floor(gif.cursor / gif.file.size * 100))
+// gif.benchmark.subblocks += (Date.now() - start) / 1000;
+ if (gif.benchmark) gif.benchmark.stop("read-subblocks");
+ return {start: startBlockCursor, blockEnds: blockEnds};
+
+ }
+};
+
+module.exports = decode; +})() +},{"./spec":13,"./palette":14,"./animate":15,"../../binaryspec":16}],13:[function(require,module,exports){var blockSigs = {
+ 0x21: "extension",
+ 0x2c: "imageDesc",
+ 0x3b: "trailer"
+};
+
+exports.blockSigs = blockSigs;
+
+var extSigs = {
+ 0xf9: "graphicControl",
+ 0xfe: "comment",
+ 0x01: "plainText",
+ 0xff: "applicationExtension"
+};
+
+exports.extSigs = extSigs;
+
+/*
+GIF.getGeneralBlockType = function(sig){
+ if (sig == 0x3b)
+ return "trailer"
+ else if (sig < 0x7F)
+ return "graphic rendering block"
+ else if (sig < 0xF9)
+ return "control block"
+ else return "special purpose block"
+}
+
+*/
+
+var spec = {
+ "header": [
+ "str[3] signature",
+ "str[3] version"
+ ],
+ "screenDesc": [
+ "u16 w",
+ "u16 h",
+ "bit paletteExists",
+ "bit[3] resolution ignore",
+ "bit sortFlag ignore",
+ "bit[3] paletteSize",
+ "u8 bgColorIndex",
+ "u8 aspectRatio ignore"
+ ],
+ "imageDesc": [
+ "u8 sig ignore",
+ "u16 x",
+ "u16 y",
+ "u16 w",
+ "u16 h",
+ "bit paletteExists",
+ "bit interlaced",
+ "bit sortFlag",
+ "bit[2] reserved ignore",
+ "bit[3] paletteSize"
+ ],
+ "applicationExtension": [
+ "u8 sig ignore",
+ "u8 extSig ignore",
+ "u8 blockSize ignore",
+ "str[8] identifier",
+ "str[3] authCode ignore"
+ ],
+ "graphicControl": [
+ "u8 sig ignore",
+ "u8 extSig ignore",
+ "u8 blockSize ignore",
+ "bit[3] reserved ignore",
+ "bit[3] disposalMethod",
+ "bit userInput ignore",
+ "bit transparentColor",
+ "u16 delay",
+ "u8 transparentIndex",
+ "u8 blockTerminator ignore"
+ ],
+ "comment": [
+ "u8 sig ignore",
+ "u8 extSig ignore"
+ ],
+ "plainText": [
+ "u8 sig ignore",
+ "u8 extSig ignore",
+ "u8 blockSize",
+ "u16 textGridLeft",
+ "u16 textGridTop",
+ "u16 textGridWidth",
+ "u16 textGridHeight",
+ "u8 charCellWidth",
+ "u8 charCellHeight",
+ "u8 fgColorIndex",
+ "u8 bgColorIndex"
+ ],
+ "imageData": [
+ "u8 lzwCodeSize"
+ ]
+};
+
+exports.spec = spec; +},{}],14:[function(require,module,exports){(function(){var rgba2css = require('../../color/rgba2css');
+var create2d = require('../../create/2d');
+var createImageData = require('../../create/imagedata');
+
+var palette = {};
+
+// flat typed array of r g b a values from binary GIF palette
+palette.binary2rgba = function(abuf /*, transparentIndex */){
+ var table = new Uint8Array(abuf.byteLength/3*4);
+ var counter = 0
+ for(var i = 0, length = abuf.byteLength/3*4; i<length; i+=4){
+ table[i] = abuf[counter];
+ table[i+1] = abuf[counter+1];
+ table[i+2] = abuf[counter+2];
+ table[i+3] = 255;
+ counter += 3;
+ }
+/*
+ if (transparentIndex !== undefined)
+ table[transparentIndex*4 + 3] = 0;
+*/
+ return table;
+};
+
+
+// TODO. break this up, make less confusing, etc.
+// horrible monolithic palette mappings constructor
+// builds a hash lookup to map rgba value to palette index
+// builds an array to map indexes to rgba values
+// builds an array to map indexes to css values
+// builds an imagedata to map indexes to colors
+//
+// gif palette index numbers are not preserved...
+// this makes a palette from all local palettes (frames) plus global palette
+// only ONE transparent value is included... 0,0,0,0, others are ignored
+palette.create = function(gif){
+
+ // boot up palette with a transparent color to start with
+ var rgba2Index = {"0":0},
+ index2Css = ["rgba(0,0,0,0)"],
+ index2Rgba = [[0,0,0,0]];
+
+ var addPalette = function(palette){
+ for(var i=0, size=palette.length; i<size; i+=4){
+ var a = palette[i+3]
+ if (a === 0) continue; // skip transparent values... just use first palette index for them
+ var r = palette[i],
+ g = palette[i+1],
+ b = palette[i+2],
+ index = (r | (g << 8) | (b << 16) | (a << 24)).toString();
+ // http://jsperf.com/rgba-hash-lookup
+ // also... this overflows and flips sign... is that safe over diff. js implementations?
+ if (index in rgba2Index) continue;
+ rgba2Index[index] = index2Rgba.length
+ var rgba = [r,g,b,a]
+ index2Rgba.push(rgba)
+ index2Css.push(rgba2css(rgba))
+ }
+ };
+
+ if ("palette" in gif)
+ addPalette(gif.palette)
+
+ for(var f=0; f<gif.frames.length; f++){
+ var frame = gif.frames[f];
+ if (!("palette" in frame)) continue;
+ addPalette(frame.palette)
+ }
+
+ // create this last, as we don't know palette size until it is built
+ var imagedata = createImageData(index2Rgba.length, 1)
+ for(var i=0, size=index2Rgba.length*4, data=imagedata.data; i<size; i+=4){
+ var rgba = index2Rgba[i/4]
+ data[i] = rgba[0]
+ data[i+1] = rgba[1]
+ data[i+2] = rgba[2]
+ data[i+3] = rgba[3]
+ }
+
+ return {"rgba2Index": rgba2Index,
+ "index2Rgba": index2Rgba,
+ "index2Css": index2Css,
+ "imagedata": imagedata,
+ "length": index2Rgba.length}
+};
+
+module.exports = palette; +})() +},{"../../color/rgba2css":17,"../../create/2d":18,"../../create/imagedata":19}],17:[function(require,module,exports){var rgba2css = function(rgba){
+ //return "rgba(" + rgba.join(",") + ")"
+ // arraybuffers have no join...
+ return "rgba(" + rgba[0] + "," + rgba[1] + "," + rgba[2] + "," + rgba[3] + ")"
+}
+module.exports = rgba2css; +},{}],18:[function(require,module,exports){var create2d = function(w, h){
+ var canvas = document.createElement("canvas");
+ canvas.width = w || 0;
+ canvas.height = h || 0;
+ return canvas.getContext("2d");
+};
+
+if (typeof module !== "undefined") module.exports = create2d; +},{}],19:[function(require,module,exports){var createImageData = function createImageData(w, h){ + return createImageData.ctx.createImageData(w, h); +}; +createImageData.ctx = document.createElement("canvas").getContext("2d"); + +if (typeof module !== "undefined") module.exports = createImageData; +},{}],15:[function(require,module,exports){//make a function on a gif that determines which frame to show given a timestamp.
+// this allows all gifs to be synced no matter when they are loaded
+// however, this is undesirable if you want to start an animation on frame 0 when it is displayed.
+var makeCurrentFrame = function(){
+ var defaultDelay = 100; // msecs
+
+ if (this.frames.length === 1){ // shortcut for static gifs
+ this.currentFrame = function(){ return 0 };
+ }
+
+ var totalDelay = 0;
+ var delays = [];
+ for(var i=0; i<this.frames.length; i++){
+ var frame = this.frames[i];
+ var delay = ("delay" in frame && frame.delay > 0) ? frame.delay * 10 : defaultDelay;
+ totalDelay += delay;
+ delays.push(totalDelay);
+ }
+
+ this.currentFrame = makeCurrentFrameFunction(delays);
+};
+
+var makeCurrentFrameFunction = function(delays){
+ var totalTime = delays[delays.length - 1];
+ return function(timestamp){
+ var r = (timestamp || Date.now()) % totalTime;
+ for(var i=0; i<delays.length; i++)
+ if (r < delays[i])
+ return i;
+ return i;
+ }
+};
+
+exports.makeCurrentFrame = makeCurrentFrame; +},{}],16:[function(require,module,exports){var tokenize = require('./string/tokenize');
+var BitView = require('../deps/bitview/bitview.timb.js');
+
+var BinarySpecReader = function(spec){
+ var reader = Object.create(BinarySpecReader.proto);
+ var parts = reader.parts = {};
+
+ for (var partName in spec)
+ parts[partName] = decodeSpecPart(spec[partName]);
+
+ return reader;
+};
+
+BinarySpecReader.proto = {};
+
+// decodes GIF parsing info from the specification
+var decodeSpecPart = function(part){
+
+ var bitSizes = {
+ "bit": 1,
+ "str": 8,
+ "i8": 8,
+ "u8": 8,
+ "i16": 16,
+ "u16": 16,
+ "i32": 32,
+ "u32": 32
+ };
+
+ var parsedSpec = {"fields": []};
+ var size = 0;
+
+ for (var i=0; i<part.length; i++){
+
+ var instruction = tokenize(part[i]);
+ var ignore = /(^|\s)ignore($|\s)/.test(part[i]);
+ var typeInfo = instruction[0].split("["); // eg "uint(16)"
+ var fieldType = typeInfo[0];
+ var fieldLength = parseInt(typeInfo[1] || 1);
+ var isArray = (typeInfo.length > 1);
+ // char(2) is 16 bits. uint(16) is 16 bits
+ var bitSize = bitSizes[fieldType] * fieldLength;
+
+ parsedSpec.fields.push({"name": instruction[1],
+ "type": fieldType,
+ "ignore": ignore,
+ "bitSize": bitSize,
+ "isArray": isArray});
+ size += bitSize;
+ }
+
+ parsedSpec.bitSize = size;
+ parsedSpec.byteSize = size / 8;
+
+ return parsedSpec;
+};
+
+// decodes a chunk according to data types in gif.spec.js
+// todo: rewrite the binary decoding stuff to not be so shit
+BinarySpecReader.proto.decodeBinaryFieldsToJSON = function(partName, cursor, buf){ var reader = this;
+
+ var part = reader.parts[partName];
+
+ var fields = {}, numFields = part.fields.length, bitPos = 0;
+
+ for(var i = 0; i < numFields; i++){
+
+ var field = part.fields[i];
+ if (!field.ignore) {
+ var bitOffset = bitPos % 8;
+ var decodeByteStart = Math.floor((bitPos - bitOffset) / 8);
+ var decodeByteEnd = decodeByteStart + Math.ceil(field.bitSize / 8);
+
+ switch(field.type){
+ case "u8":
+ fields[field.name] = buf.u8[cursor + decodeByteStart]; break;
+ case "i8":
+ fields[field.name] = buf.dv.getInt8(cursor + decodeByteStart); break;
+ case "u16":
+ fields[field.name] = buf.dv.getUint16(cursor + decodeByteStart, true); break;
+ case "i16":
+ fields[field.name] = buf.dv.getInt16(cursor + decodeByteStart, true); break;
+ case "u32":
+ fields[field.name] = buf.dv.getUint32(cursor + decodeByteStart, true); break;
+ case "i32":
+ fields[field.name] = buf.dv.getInt32(cursor + decodeByteStart, true); break;
+ case "str":
+ fields[field.name] = abuf2str(buf.abuf, cursor + decodeByteStart, field.bitSize >> 3); break;
+ case "bit":
+ if (!field.isArray) {
+ fields[field.name] = new BitView(buf.abuf, cursor + decodeByteStart).getBit(bitOffset);
+ } else {
+ var bv = new BitView(buf.abuf, cursor + decodeByteStart);
+ var bits = [];
+ for (var bb=bitOffset; bb<bitOffset+field.bitSize; bb++){
+ bits.push(bv.getBit(bb))
+ }
+ fields[field.name] = bits;
+ }
+ break;
+ // fields[field.name] = abuf2str(buf.abuf, cursor + decodeByteStart, field.bitSize / 8); break;
+ default:
+ console.log("please implement: " + field.type + ", " + partName);
+ }
+ }
+ bitPos += field.bitSize;
+ }
+ //gif.cursor += part.byteSize;
+ //GIF.decode.handleLoadedPart[gif.nextPart](gif, fields);
+ // console.log(fields);
+ return fields
+}
+
+// todo: wasteful of space?
+var abuf2str = function(abuf, offset, len) {
+ return String.fromCharCode.apply(null, new Uint8Array(abuf, offset, len));
+}
+
+
+module.exports = BinarySpecReader; +},{"./string/tokenize":8,"../deps/bitview/bitview.timb.js":20}],20:[function(require,module,exports){// @timb: edit of https://github.com/kig/bitview.js by Ilmari Heikkinen +var BitView = function(buf, offset) { + this.buffer = buf; + this.u8 = new Uint8Array(buf, offset); +}; + +BitView.prototype.getBit = function(idx) { + var v = this.u8[idx >> 3]; + var off = idx & 0x7; + return (v >> (7-off)) & 1; +}; + +module.exports = BitView; +},{}],6:[function(require,module,exports){var create2d = require('../../create/2d');
+var createImageData = require('../../create/imagedata');
+//import queueOrNextTick from 'lib/nexttick4';
+var nextTick = require('../../nexttick');
+
+var lzwImageData = require('./decode-lzw');
+
+/*
+ this has two rendering types... canvas and webgl
+ both build gif image frames from binary data that was decoded when the gif loaded
+
+ canvas method will build full-frame canvas objects and place them into each frame of the gif.
+ eg, render.canvas(gif) will create gif.frames[0].ctx and so on
+
+ webgl method builds imagedata textures and tries to pack multiple frames efficiently into channels if it can.
+
+ in gifs, each frame stored might just be a rectangle that changed from the previous frame.
+ these are referred to as "raw frames" in this code
+*/
+
+var render = function(gif, config){
+
+ config = config || {};
+
+ var bench = gif.benchmark || false;
+
+ var frameNum = config.frameNum || 0;
+
+ if (frameNum === 0){ // preallocate all frames
+ for (var i=0; i<gif.frames.length; i++){
+ gif.frames[i].ctx = create2d(gif.w, gif.h);
+ gif.buf.pixeldata = new Uint8Array(gif.w * gif.h);
+ }
+ }
+
+ if (frameNum >= gif.frames.length) { // done making frames
+ //gif.tube("progress", "done")
+ gif.rendered = true;
+ gif.tube("rendered");
+ return
+ }
+
+ var frame = gif.frames[frameNum];
+ var pixeldata = gif.buf.pixeldata;
+
+ // gif.percentLoaded = frameNum / gif.frames.length
+ //gif.tube("decompressing frame " + (frameNum+1))
+
+
+
+ // lzw
+ if (bench) bench.start("decompress-lzw");
+ // var pixeldata = new Uint8Array(frame.w * frame.h);
+ //pixeldata.area = frame.w * frame.h;
+ lzwImageData(frame.blockinfo, gif.buf.u8, frame.lzwCodeSize, frame.w, frame.h, pixeldata);
+ if (bench) bench.stop("decompress-lzw");
+
+ // deinterlace
+ if (frame.interlaced) {
+ if (bench) bench.start("deinterlace");
+ pixeldata = deinterlacePixels(pixeldata, frame.w, frame.h)
+ if (bench) bench.stop("deinterlace");
+ }
+
+ // canvas-ize
+ if (bench) bench.start("pixeldata-to-canvas");
+ makeFullFrame(pixeldata, gif, frameNum);
+ if (bench) bench.stop("pixeldata-to-canvas");
+
+ // todo: queue this better
+ var func = render.bind(undefined, gif, {"frameNum": frameNum+1});
+ // queueOrNextTick(func);
+ nextTick(func);
+ // setZeroTimeout(func);
+ //setTimeout(func, 1) // otherwise progress won't show in chrome
+
+};
+
+var makeFullFrame = function(pixeldata, gif, frameNum){
+
+ var frame = gif.frames[frameNum],
+ ctx = frame.ctx;
+
+ if (frameNum === 0){ // don't need previous frame info to do disposal if it's the first frame
+ ctx.putImageData(pixelDataToImageData(pixeldata, gif, frame), frame.x, frame.y,
+ 0,0,frame.w,frame.h);
+ return;
+ }
+
+ var prevFrameNum = frameNum-1,
+ prevFrame = gif.frames[prevFrameNum],
+ prevCanvas = prevFrame.ctx.canvas,
+ rawCtx;
+
+ // disposal method is 0 (unspecified) or 1 (do not dispose)
+ // do nothing, paste new frame image over old one
+ if (prevFrame.disposalMethod === 0 || prevFrame.disposalMethod === 1){
+ rawCtx = makeRawFrameAsContext(gif, frameNum, pixeldata);
+ ctx.drawImage(prevCanvas, 0, 0)
+ ctx.drawImage(rawCtx.canvas, 0,0,frame.w,frame.h, frame.x,frame.y,frame.w,frame.h)
+ }
+
+ // disposal method is 2 (restore to background color)
+ // but everyone just restores to transparency
+ // see notes on http://www.imagemagick.org/Usage/anim_basics/#background
+ if (prevFrame.disposalMethod === 2){
+ // fast path... whole frame cleared
+ if (prevFrame.x === 0 && prevFrame.y === 0 && prevFrame.w === gif.w && prevFrame.h === gif.h) {
+ // var rawContext = makeRawFrameAsContext(gif, frameNum)
+ // frame.context.drawImage(rawContext.canvas, frame.x, frame.y);
+ ctx.putImageData(makeRawFrameAsImageData(gif, frameNum, pixeldata), frame.x, frame.y,
+ 0,0,frame.w,frame.h);
+ } else { // draw the edges of the previous frame and then draw the current frame overtop
+ /*
+ .__________.
+ |__________|
+ | | | |
+ | | | |
+ |_|______|_|
+ |__________|
+ */
+ //top
+ if (prevFrame.y > 0)
+ ctx.drawImage(prevCanvas, 0,0, gif.w,prevFrame.y,
+ 0,0, gif.w,prevFrame.y);
+ //left
+ if (prevFrame.x > 0)
+ ctx.drawImage(prevCanvas, 0,prevFrame.y, prevFrame.x,prevFrame.h,
+ 0,prevFrame.y, prevFrame.x,prevFrame.h);
+ // right
+ if (prevFrame.x+prevFrame.w < gif.w)
+ ctx.drawImage(prevCanvas, prevFrame.x+prevFrame.w, prevFrame.y, (gif.w-prevFrame.x-prevFrame.w),prevFrame.h,
+ prevFrame.x+prevFrame.w, prevFrame.y, (gif.w-prevFrame.x-prevFrame.w),prevFrame.h);
+ // bottom
+ if (prevFrame.y+prevFrame.h < gif.h)
+ ctx.drawImage(prevCanvas, 0,prevFrame.y+prevFrame.h, gif.w,(gif.h-prevFrame.y-prevFrame.h),
+ 0,prevFrame.y+prevFrame.h, gif.w,(gif.h-prevFrame.y-prevFrame.h))
+
+ rawCtx = makeRawFrameAsContext(gif, frameNum, pixeldata);
+ ctx.drawImage(rawCtx.canvas, 0,0,frame.w,frame.h, frame.x,frame.y,frame.w,frame.h);
+
+ }
+ }
+
+ // disposal method is 3 (restore to previous)
+ if (prevFrame.disposalMethod === 3){
+ // look for last previous frame that doesn't have "previous" disposal method
+ while(prevFrameNum > 0 && gif.frames[prevFrameNum].disposalMethod === 3) prevFrameNum -= 1;
+ prevFrame = gif.frames[prevFrameNum]
+ // console.log(prevFrameNum)
+ if (prevFrame.disposalMethod != 3) ctx.drawImage(prevFrame.ctx.canvas, 0, 0)
+ rawCtx = makeRawFrameAsContext(gif, frameNum, pixeldata);
+ ctx.drawImage(rawCtx.canvas, 0,0,frame.w,frame.h, frame.x, frame.y,frame.w,frame.h)
+ }
+
+}
+
+var makeRawFrameAsContext = function(gif, frameNum, pixeldata){
+ // cache a context to reuse
+ if (makeRawFrameAsContext.ctx &&
+ makeRawFrameAsContext.ctx.canvas.width === gif.w &&
+ makeRawFrameAsContext.ctx.canvas.height === gif.h) {
+ var ctx = makeRawFrameAsContext.ctx;
+ } else {
+ var ctx = makeRawFrameAsContext.ctx = create2d(gif.w, gif.h);
+ }
+
+ var frame = gif.frames[frameNum];
+ pixeldata = pixeldata || frame.pixelData;
+ var palette = ("palette" in frame) ? frame.palette : gif.palette;
+ var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1;
+
+ if (transparentIndex > -1) palette[(transparentIndex*4)+3] = 0;
+
+ var rawImageData = pixelData2imageData(gif, palette, pixeldata, frame.w, frame.h, transparentIndex);
+
+ ctx.putImageData(rawImageData, 0, 0, 0,0,frame.w,frame.h)
+
+ return ctx
+
+ // return imageData2contextDirty(rawImageData, 0,0,frame.w,frame.h);
+};
+
+var makeRawFrameAsImageData = function(gif, frameNum, pixeldata){
+ var frame = gif.frames[frameNum];
+ pixeldata = pixeldata || frame.pixelData;
+ var palette = ("palette" in frame) ? frame.palette : gif.palette;
+ var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1;
+
+ if (transparentIndex > -1) palette[(transparentIndex*4)+3] = 0;
+
+ return pixelData2imageData(gif, palette, pixeldata, frame.w, frame.h, transparentIndex)
+};
+
+var pixelDataToImageData = function(pixeldata, gif, frame){
+ // var frame = gif.frames[frameNum];
+ var palette = ("palette" in frame) ? frame.palette : gif.palette;
+ var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1;
+
+ if (transparentIndex > -1) palette[(transparentIndex*4)+3] = 0;
+
+ return pixelData2imageData(gif, palette, pixeldata, frame.w, frame.h, transparentIndex)
+};
+
+
+var imageData2context = function(imageData){
+ var ctx = create2d(imageData.width, imageData.height)
+ ctx.putImageData(imageData, 0, 0)
+ return ctx
+};
+var imageData2contextDirty = function(imageData, dx,dy,dw,dh){
+ var ctx = create2d(imageData.width, imageData.height)
+ ctx.putImageData(imageData, 0, 0, dx,dy,dw,dh)
+ return ctx
+};
+
+var pixelData2imageData = function(gif, palette, pixeldata, w, h, transparentIndex){
+ if (pixelData2imageData.imagedata &&
+ pixelData2imageData.imagedata.width === gif.w &&
+ pixelData2imageData.imagedata.height === gif.h) {
+ var imagedata = pixelData2imageData.imagedata;
+ } else {
+ var imagedata = pixelData2imageData.imagedata = createImageData(gif.w, gif.h);
+ }
+
+ var data = imagedata.data
+ // var i = pixeldata.length;
+ var i = 0;
+
+ for (var y=0; y<h; y++){ var yinc = y*gif.w;
+ for (var x=0; x<w; x++){ //var i = y*gif.w+x;
+ // var cp = i*4
+ var cp = (x + yinc)*4
+ var ct = pixeldata[i]*4 //palette[pixeldata[i]]
+ data[cp] = palette[ct]
+ data[cp+1] = palette[ct+1]
+ data[cp+2] = palette[ct+2]
+ data[cp+3] = palette[ct+3]
+ i += 1;
+ }
+ }
+
+ return imagedata;
+};
+
+// deinterlace the 4 chunks from one imageData blob in one pass
+// see appendix e of gif spec
+//
+// ideas: canvasize first, then deinterlace...
+// and do canvas drawImage calls or putImageData calls to move the rows around?
+var deinterlacePixels = function(oldpixels, w, h){
+
+ var pixels = new Uint8Array(oldpixels.length)
+ var stripes2 = Math.ceil(h/8) // the row where the 2nd interlaced chunk starts
+ var stripes3 = Math.ceil(h/4)
+ var stripes4 = Math.ceil(h/2)
+
+ // pixels[(w*y) +x] = oldpixels[(w*y) +x] <- base calculation
+
+ for(var y=0; y<h; y++){
+ var interlacedRowOffset
+ var rowOffset = w * y
+ if (y % 8 === 0)
+ interlacedRowOffset = w* (y/8)
+ else if ((y + 4) % 8 === 0)
+ interlacedRowOffset = w* ((y-4)/8+stripes2)
+ else if (y % 2 === 0)
+ interlacedRowOffset = w* ((y-2)/4+stripes3)
+ else
+ interlacedRowOffset = w* ((y-1)/2+stripes4)
+ for(var x=0; x<w; x++)
+ pixels[rowOffset+x] = oldpixels[interlacedRowOffset+x]
+ }
+
+ return pixels;
+};
+
+module.exports = render; +},{"../../create/2d":18,"../../create/imagedata":19,"../../nexttick":11,"./decode-lzw":21}],21:[function(require,module,exports){// looked at imagemagick implementation instead of trying to figure out my buggy one
+
+/*
+ decodes an lzw compressed gif frame
+ mostly based on imagemagick's implementation
+
+ data, binary string
+ data_size, lzw compression code size
+ w, h, width and height of image
+ paletteRemap is an optional array of different palette indexes to use
+
+ returns typed array of palette index values
+
+ this assumes we have the entire data for the frame buffered...
+ its possible to modify this so that a resumable state could be passed in, but prob not necessary
+*/
+// TODO: cache stacks?
+var lzwImageData = function(blockinfo, u8buf, data_size, w, h, pixels, paletteRemap){
+ var MaxStack = 4096;
+ var NullCode = -1;
+ var cursor = 0
+ var npix = w * h;
+ var code, in_code, old_code;
+ var first = 0;
+ var top = 0;
+ var pi = 0
+ var pixels = pixels || new Uint8Array(npix);
+ var prefix = new Uint16Array(MaxStack*2) //Uint16Array(MaxStack*1.5) <-- this makes a nice glitch
+ var suffix = new Uint8Array(MaxStack)
+ // var prefix = lzwImageData.prefix;
+ // var suffix = lzwImageData.suffix;
+ var pixelStack = new Uint8Array(MaxStack+1)
+
+ // Initialize GIF data stream decoder.
+ var clear = 1 << data_size;
+ var end_of_information = clear + 1;
+ var available = clear + 2;
+ old_code = NullCode;
+ var code_size = data_size + 1;
+ var code_mask = (1 << code_size) - 1;
+ for (code = 0; code < clear; code++){
+ prefix[code] = 0;
+ suffix[code] = code;
+ }
+
+ var bits = 0; var datum = 0;
+ var dvcursor = blockinfo.start;
+ var blockEnds = blockinfo.blockEnds;
+ var blockEnd = blockEnds.shift();
+
+ // Decode GIF pixel stream.
+ for (var i = 0; i < npix;){
+ if (top === 0){
+
+ if (bits < code_size){
+
+ datum += u8buf[dvcursor] << bits;
+
+ bits += 8;
+
+ dvcursor++;
+ if (dvcursor === blockEnd){
+ dvcursor += 1;
+ blockEnd = blockEnds.shift();
+ }
+
+ continue;
+
+ }
+ code = datum & code_mask;
+ datum >>= code_size;
+ bits -= code_size;
+
+ if (code > available) {
+ console.log(":(");
+ // console.log(num2bin(code));
+ // break;
+ } // if we get here something bad happened ;(
+ if (code === end_of_information) { console.log("fuck"); break};
+ if (code === clear) {
+ code_size = data_size + 1;
+ code_mask = (1 << code_size) - 1;
+ available = clear + 2;
+ old_code = NullCode;
+ continue;
+ }
+ if (old_code === NullCode){
+ pixelStack[top++] = suffix[code];
+ old_code = code;
+ first = code;
+ continue;
+ }
+ in_code = code;
+ if (code === available){
+ pixelStack[top++] = first;
+ code = old_code;
+ }
+ while (code > clear){
+ pixelStack[top++] = suffix[code];
+ code = prefix[code];
+ }
+ first = (suffix[code]) // & 0xff;
+ // ^^ timb: not needed?
+ // Add a new string to the string table,
+ if (available >= MaxStack) { /*console.log("maxstack!");*/ /*break;*/ }
+ pixelStack[top++] = first;
+ prefix[available] = old_code;
+ suffix[available] = first;
+ available++;
+ /* why does == have higher precedence than & ?
+ i just looked it up and i think it is because javascript
+ has basically the same precedence as C and
+ "once upon a time, C didn't have the logical operators && and ||,
+ and the bitwise operators & and | did double duty."
+ */
+ if ((available & code_mask) === 0 && available < MaxStack){
+ code_size++;
+ code_mask += available;
+ }
+ old_code = in_code;
+ }
+
+ top--;
+ if (paletteRemap)
+ pixels[pi++] = paletteRemap[pixelStack[top]];
+ else
+ pixels[pi++] = pixelStack[top];
+ i++;
+ }
+
+ // not needed: typed arrays init'd to 0 already
+ // for (i = pi, len=pixels.length; i < len; i++)
+ // pixels[i] = 0; // clear missing pixels
+
+
+ return pixels
+};
+
+var MaxStack = 4096;
+// lzwImageData.prefix = new Uint16Array(MaxStack*2)
+// lzwImageData.suffix = new Uint8Array(MaxStack)
+
+module.exports = lzwImageData; +},{}]},{},[1]); |
