// 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'); var worker = new Worker(workerURL); 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; } }