summaryrefslogtreecommitdiff
path: root/js/client.concat.js
diff options
context:
space:
mode:
authorJulie Lala <jules@okfoc.us>2014-01-29 09:31:53 -0500
committerJulie Lala <jules@okfoc.us>2014-01-29 09:31:53 -0500
commit219aad8ea57538868c6735860dbcc0873f62dcd8 (patch)
tree2cc691da2fe6a0c5c6ae75ad80920fc74d66f809 /js/client.concat.js
parent0aa06bbe84961b9393029f904bc7a49e79b513f4 (diff)
grunt build process for worker
Diffstat (limited to 'js/client.concat.js')
-rw-r--r--js/client.concat.js626
1 files changed, 626 insertions, 0 deletions
diff --git a/js/client.concat.js b/js/client.concat.js
new file mode 100644
index 0000000..d896a61
--- /dev/null
+++ b/js/client.concat.js
@@ -0,0 +1,626 @@
+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);
+}
+;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;
+})()
+
+;// 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 = 6;
+
+// 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 width = 0;
+ var height = 0;
+ var frames_done = 0;
+
+ 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(){
+ resetFrames()
+ neuquant = null;
+ colortab = null;
+ base.quantized = false
+ }
+ var resetFrames = this.resetFrames = function(){
+ canvases = [];
+ contexts = [];
+ frames = [];
+ delays = [];
+ width = 0;
+ height = 0;
+ frames_done = 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 addFrames = this.addFrames = function(canvas_array, delay){
+ for (var i = 0; i < canvas_array.length; i++) {
+ var canvas = canvas_array[i]
+ var ctx = canvas.getContext('2d');
+ canvases.push(canvas);
+ contexts.push(ctx);
+ delays.push(delay);
+ }
+
+ if (canvases.length == canvas_array.length) {
+ width = canvas_array[0].width;
+ height = canvas_array[0].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('js/vendor/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.fire = function(){
+ for (var i in ww) {
+ ww[i].postMessage("close");
+ }
+ ww = []
+ base.init()
+ }
+ 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) {
+ if (! canvases.length) {
+ throw Error ("No frames to encode")
+ }
+ nq = nq || neuquant
+ ct = ct || colortab
+
+ started = Date.now();
+ frames_done = 0;
+
+ 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("done_sending")
+ }
+ sendWork();
+ }
+
+ function receiveEncode(e){
+ var frame_index = e.data["frame_index"];
+ var frame_data = e.data["frame_data"];
+
+ frames[frame_index] = frame_data;
+
+ base.tube("encoded-frame", frames.length, canvases.length)
+ 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");
+
+ workers.fire()
+ }
+
+// 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;
+ }
+
+}