Skip to content

Instantly share code, notes, and snippets.

@pvdz
Last active August 29, 2015 14:07
Show Gist options
  • Select an option

  • Save pvdz/2e11b770f5b370ecc648 to your computer and use it in GitHub Desktop.

Select an option

Save pvdz/2e11b770f5b370ecc648 to your computer and use it in GitHub Desktop.
Creating animated gif. Demonstrates web worker lag after some frames unless respawned. For other deps see https://github.com/antimatter15/jsgif
importScripts('gifencoder/LZWEncoder.js', 'gifencoder/NeuQuant.js', 'gifencoder/GIFEncoder.js');
self.onmessage = function(event) {
//console.log("message!");
//alert("message");
//self.postMessage(event.data);
var frame_index = event.data.frame_index;
var frame_length = event.data.frame_length;
var height = event.data.height;
var width = event.data.width;
var imageData = event.data.imageData;
var delay = event.data.delay;
var worker_id = event.data.workerId;
var encoder = new GIFEncoder(); //create a new GIFEncoder for every new job
encoder.setRepeat(0); //0 -> loop forever
//1+ -> loop n times then stop
encoder.setQuality(1);
encoder.setSize(width, height);
encoder.setDelay(delay); //go to next frame every n milliseconds
if (frame_index == 0) {
encoder.start();
} else {
encoder.cont();
encoder.setProperties(true, false); //started, firstFrame
}
encoder.addFrame(imageData, true);
if (frame_length == frame_index) encoder.finish();
self.postMessage({
frame_index: frame_index,
worker_id: worker_id,
frame_data: encoder.stream().getData()
});
};
<!-- html -->
<!doctype html>
<html>
<head>
</head>
<body>
<!-- https://github.com/antimatter15/jsgif -->
<script type="text/javascript" src="gifencoder/LZWEncoder.js"></script>
<script type="text/javascript" src="gifencoder/NeuQuant.js"></script>
<script type="text/javascript" src="gifencoder/GIFEncoder.js"></script>
<canvas style="border: 1px solid black;"></canvas>
<script>
var ENABLE_WORKAROUND = true;
var WORKER_COUNT = 5;
var TOTAL_FRAMES = 200;
var FRAME_DELAY = 100;
var canvas = document.querySelector('canvas');
canvas.width = 500;
canvas.height = 300;
var frames = [];
var framesReceived = 0;
var workers = [];
var frameIndex = 0;
var fifo = [];
function newWorker() {
console.log('Spawning new worker');
var worker = new Worker('./animworker.js');
worker.onmessage = workerOnMessage;
worker.age = 10;
return worker;
}
function workerOnMessage(e) {
var worker = workers[e.data.worker_id];
console.log(
'onMessage! frame index: %d, worker id: %d, process time: %d',
e.data.frame_index,
e.data.worker_id,
Date.now()-worker.busy
);
++framesReceived;
frames[e.data.frame_index] = e.data.frame_data;
worker.busy = false;
if (ENABLE_WORKAROUND && --worker.age <= 0) {
console.log("Killing off worker %d", e.data.worker_id);
workers[e.data.worker_id] = null;
}
if (framesReceived < TOTAL_FRAMES) return flush();
console.log('done!');
var binary_gif = frames.join('');
var data_url = 'data:image/gif;base64,' + window.btoa(binary_gif);
var gifItem = new Image();
gifItem.src = data_url;
gifItem.style.cssFloat = 'right';
gifItem.style.border = '1px solid red';
document.body.appendChild(gifItem);
}
window.addFrame = function(context){
fifo.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height));
flush();
};
function flush(){
if (!fifo.length) return console.log('fifo empty, waiting...');
var free = -1;
for (var i = 0; i < WORKER_COUNT; ++i) {
if (!workers[i]) {
workers[i] = newWorker();
free = i;
break;
}
if (!workers[i].busy) {
free = i;
break;
}
}
if (free < 0) return console.log('no free worker found, waiting...');
var worker = workers[free];
worker.busy = Date.now();
var idata = fifo.shift();
// attempt to delay
setTimeout(function(){
worker.postMessage({
frame_index: frameIndex++,
delay: FRAME_DELAY,
frame_length: TOTAL_FRAMES,
height: idata.height,
width: idata.width,
imageData: idata.data,
workerId: free
});
}, 200);
}
// create a random line, add the canvas to the gif generator after each new line
var count = 200;
var context = canvas.getContext('2d');
context.lineWidth = 3;
context.strokeStyle = 'red';
while (--count>=0) {
context.beginPath();
context.moveTo(Math.random() * 500, Math.random() * 300);
context.lineTo(Math.random() * 500, Math.random() * 300);
context.stroke();
window.addFrame(context);
}
</script>
ENABLE_WORKAROUND=false :
onMessage! frame index: 45, worker id: 1, process time: 1324
onMessage! frame index: 47, worker id: 4, process time: 987
onMessage! frame index: 48, worker id: 3, process time: 956
onMessage! frame index: 50, worker id: 0, process time: 924
onMessage! frame index: 49, worker id: 2, process time: 1331
onMessage! frame index: 51, worker id: 1, process time: 1176
onMessage! frame index: 53, worker id: 3, process time: 1166
onMessage! frame index: 52, worker id: 4, process time: 1428
onMessage! frame index: 55, worker id: 2, process time: 1218
onMessage! frame index: 54, worker id: 0, process time: 4985
onMessage! frame index: 57, worker id: 3, process time: 4935
onMessage! frame index: 56, worker id: 1, process time: 5555
onMessage! frame index: 59, worker id: 2, process time: 6546
onMessage! frame index: 58, worker id: 4, process time: 7136
onMessage! frame index: 60, worker id: 0, process time: 5795
onMessage! frame index: 62, worker id: 1, process time: 5236
onMessage! frame index: 61, worker id: 3, process time: 5708
onMessage! frame index: 63, worker id: 2, process time: 4387
onMessage! frame index: 64, worker id: 4, process time: 4725
ENABLE_WORKAROUND=true :
onMessage! frame index: 39, worker id: 1, process time: 964
onMessage! frame index: 41, worker id: 2, process time: 994
onMessage! frame index: 42, worker id: 3, process time: 939
onMessage! frame index: 43, worker id: 0, process time: 1075
onMessage! frame index: 44, worker id: 4, process time: 1070
onMessage! frame index: 46, worker id: 2, process time: 903
Killing off worker 2
Spawning new worker
onMessage! frame index: 45, worker id: 1, process time: 1276
Killing off worker 1
Spawning new worker
onMessage! frame index: 47, worker id: 3, process time: 1258
Killing off worker 3
Spawning new worker
onMessage! frame index: 50, worker id: 2, process time: 929
onMessage! frame index: 51, worker id: 1, process time: 929
onMessage! frame index: 48, worker id: 0, process time: 1305
Killing off worker 0
Spawning new worker
onMessage! frame index: 49, worker id: 4, process time: 1449
Killing off worker 4
Spawning new worker
onMessage! frame index: 52, worker id: 3, process time: 1194
onMessage! frame index: 53, worker id: 2, process time: 1071
onMessage! frame index: 54, worker id: 1, process time: 1035
onMessage! frame index: 56, worker id: 4, process time: 965
onMessage! frame index: 55, worker id: 0, process time: 1311
onMessage! frame index: 57, worker id: 3, process time: 976
...
onMessage! frame index: 185, worker id: 3, process time: 707
onMessage! frame index: 186, worker id: 0, process time: 828
onMessage! frame index: 187, worker id: 2, process time: 724
onMessage! frame index: 189, worker id: 4, process time: 728
onMessage! frame index: 188, worker id: 1, process time: 925
</body>
</html>
@pvdz
Copy link
Copy Markdown
Author

pvdz commented Oct 6, 2014

I'm also sorry for the mess. I've been trying to get this to run at an acceptable speed in a desirable way but it's just meh. It's also cut from a way larger project where the actual canvas stuff happens, of course. And I know the output gif isn't always consistently animated, but I don't think that's relevant to the test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment