Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grabbing each frame of an HTML5 canvas

Tags:

These palette cycle images are breathtaking: http://www.effectgames.com/demos/canvascycle/?sound=0

I'd like to make some (or all) of these into desktop backgrounds.

I could use an animated gif version, but I have no idea how to get that from the canvas "animation". Is there anything available yet that can do something along these lines (speficially for that link and generally speaking).

like image 782
Matthew Groves Avatar asked Jul 27 '10 17:07

Matthew Groves


People also ask

How do I capture a canvas image?

If you wish to make the user download the file as it is saved you can do the following: var canvas = document. getElementById("mycanvas"); var image = canvas. toDataURL("image/png").

Is HTML5 Canvas still used?

The HTML5 canvas has the potential to become a staple of the web, enjoying ubiquitous browser and platform support in addition to widespread webpage support, as nearly 90% of websites have ported to HTML5.

What is the canvas element in HTML5?

The HTML <canvas> element is used to draw graphics, on the fly, via JavaScript. The <canvas> element is only a container for graphics. You must use JavaScript to actually draw the graphics. Canvas has several methods for drawing paths, boxes, circles, text, and adding images.

How do I get an image from a video element?

All you have to do is modify your code to look like this: let canvas = document. createElement('canvas'); let video = document. getElementById('my-video'); let image = ''; video.


2 Answers

I have a solution but it is dependent on you being familiar with the Javascript Console in Firefox (install the Firebug plugin), Chrome or Safari.

In case you're not, google it, or try to just right click anywhere on the page, choose "Inspect Element" and look for "Console"...

What the code does:

It allows you to take 10 screen-grabs of the CANVAS element every 1/10th of a second. Both these values are easily modified since you can tweak to find the number of iterations you'd like to get. For the example you give, the canvas element ID is 'mycanvas'.

Once it has the screen-grabs it outputs the images into the page. At that point you can save the individual images.

Running the code

Paste in the following code into the Javascript Console:

var canvas = document.getElementById("mycanvas"); var shots  = []; var grabLimit = 10;  // Number of screenshots to take var grabRate  = 100; // Miliseconds. 500 = half a second  var count     = 0;  function showResults() {     //console.log(shots);     for (var i=0; i<shots.length; i++) {       document.write('<img src="' + shots[i] + '"/>\n');     } }  var grabber = setInterval(function(){   count++;    if (count>grabLimit) {     clearInterval(grabber);     showResults();   }    var img     = canvas.toDataURL("image/png");   shots.push(img); }, grabRate); 

and press CTRL-Enter to execute it.

It should take a few seconds to run so please be patient.

After that you should have all the necessary frames (any maybe more) to create an animated GIF via ImageMagick, this website MakeAGif.com, or other app.

Side Note

If for some reason you need to output as GIF of JPG instead of PNG just update, as needed, this:

var img     = canvas.toDataURL("image/png"); 

to one of these:

var img     = canvas.toDataURL("image/gif"); var img     = canvas.toDataURL("image/jpg"); 

Support for output as gif or jpg may not be in all browsers (should be most).

(BIG) UPDATE #1

First, I'm keeping the code above intact rather than updating it because both approaches could be helpful to others.

Second, this new code DOES NOT SOLVE the problem. It kind-of does but one major drawback. It creates an animated GIF (Yipee!) but its in various shades of green (Boooo!). Not sure how/why, but maybe someone can take it from here and see what I've missed.

So here we go... same rules apply - copy and paste it into the Javascript Console of a browser (it lags in Firefox but Google Chrome its pretty fast... 10 seconds or so to run).

var jsf  = ["/Demos/b64.js", "LZWEncoder.js", "NeuQuant.js", "GIFEncoder.js"]; var head = document.getElementsByTagName("head")[0];  for (var i=0;i<jsf.length;i++) {   var newJS = document.createElement('script');   newJS.type = 'text/javascript';   newJS.src = 'http://github.com/antimatter15/jsgif/raw/master/' + jsf[i];   head.appendChild(newJS); }  // This post was very helpful! // http://antimatter15.com/wp/2010/07/javascript-to-animated-gif/  var w = setTimeout(function() { // give external JS 1 second of time to load      console.log('Starting');      var canvas = document.getElementById("mycanvas");     var context = canvas.getContext('2d');     var shots  = [];     var grabLimit = 10;  // Number of screenshots to take     var grabRate  = 100; // Miliseconds. 500 = half a second     var count     = 0;      function showResults() {         console.log('Finishing');         encoder.finish();         var binary_gif = encoder.stream().getData();         var data_url = 'data:image/gif;base64,'+encode64(binary_gif);         document.write('<img src="' +data_url + '"/>\n');     }      var encoder = new GIFEncoder();     encoder.setRepeat(0);  //0  -> loop forever, 1+ -> loop n times then stop     encoder.setDelay(500); //go to next frame every n milliseconds     encoder.start();      var grabber = setInterval(function(){       console.log('Grabbing '+count);       count++;        if (count>grabLimit) {         clearInterval(grabber);         showResults();       }        var imdata = context.getImageData(0,0,canvas.width,canvas.height);       encoder.addFrame(context);      }, grabRate);  }, 1000); 

It uses some helpful code, pointers and JS files referenced in this blog post JavaScript to (Animated) GIF. I use some JS files directly but you should copy these locally if you're going to use it a lot.

The output for me was this GIF: alt text

So its something, but not what you need...

like image 105
donohoe Avatar answered Sep 30 '22 18:09

donohoe


EDIT: Finally put that code to use, here's the result:

alt text

Imagick generated a large image, so I went ahead and optimized with gimp.

The client-side code is a modified version of Michael's code:

var canvas = document.getElementById("mycanvas"); var shots  = []; var grabLimit = 30;  // Number of screenshots to take var grabRate  = 100; // Miliseconds. 500 = half a second  var count     = 0;  function postResults() {    console.log("START---------");    for (var i = 0; i < shots.length; i++) {       document.write(shots[i]+"<br />");    }        console.log("END-----------"); }  var grabber = setInterval(function(){   count++;    if (count>grabLimit) {     clearInterval(grabber);     postResults();   }    var img     = canvas.toDataURL("image/png");   shots.push(img.replace("data:image/png;base64,","")); }, grabRate); 

It will write a bunch of base64 strings to the screen. Copy them and save them into a text file and then upload it to your web server. Then run the other script (see below) and it will write the image to your web server. The resulting image will be large and possibly choppy, so open up GIMP and optimize for difference and GIF. When saving, force it to use the same delay for all frames so the animation is smooth.


May not be too hard using PHP.

  1. Grab the dataURL (base64 encoded string) for each frame with JS and send it to the server.
  2. Decode the base64 strings and convert them to Imagick objects.
  3. Add these Imagick objects as frames to a new Imagick object.
  4. Save the Imagick object to the file system as a GIF.

Since michael already posted a nice JS solution, I'll add the server side code if you wish to automate it:

<?php $imageData = array_map('rtrim', file('data.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); //base 64 strings separated by newlines $delay = 100; $filename = 'coolmoviebro.gif'; $gif = new Imagick();  for($i = 0; $i < count($imageData); $i++) {    $tempImg = new Imagick();    $tempImg->readimageblob(base64_decode($imageData[$i]));    $gif->addImage($tempImg); }  $gif->setFormat('gif'); $gif->setImageDelay($delay); $gif->writeImages($filename, true);  ?> 

I haven't written much PHP for a year or two so be sure to double check everything and so on.

like image 25
Cristian Sanchez Avatar answered Sep 30 '22 17:09

Cristian Sanchez