views:

166

answers:

4

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).

+1  A: 

Sadly, according to the art creator, it is not quite possible to convert it to GIF animation due to different parts of the picture having different cycles.

syockit
I would think that, since animated can GIF's pretty effectively compress matching pixels across frames, the fact that it would take thousands of frames isn't actually as much of a problem as the artist suspects. Of course, his aesthetic favors economy, so he would be naturally opposed to just throwing hard drive space at the problem, but that doesn't mean it isn't possible!
WCWedin
It is - just not directly. +1 to what WCWedin said too.
michael
+4  A: 

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.

CD Sanchez
I don't mean to be disparaging (this is the most successful effort put forth on this question, after all), but I did want to point out that your image suffers from exactly the choppiness and awkward looping that the artist warned about. The cycle length of the final animation has to be chosen very carefully the minimize these artifacts. A little trial and error or some pen-and-paper math might do the trick, though. Nice work.
WCWedin
@WCWedin: I agree -- although it's pretty smooth up until the end of the animation where it didn't end where the cycle originally began, as expected. I would have put more thought into it, but figured a "proof of concept" for generating the gif would have been enough -- and also considering the answer had already been chosen at the time I came up with the image, there was little motivation to continue. Though if anyone can get me correct frame data, I'd be glad to regenerate the gif. If I have the time I think I may give the source a look to see if i can extract a full cycle.
CD Sanchez
+7  A: 

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...

michael
You never use the `context` variable after declaring it. Does it have any use?
CD Sanchez
Oops. You're right. Forgot to remove it. I've updated the code accordingly. Thanks for noticing.
michael
this is a fantastic solution, but I can't figure out how to actually save the images to files. Neither DownThemAll or Opera Links seems to be able to see the images.
mgroves
You should be able to Right-Click with the mouse and the option to "Save Image As..." (this will vary depending on browser etc). Try Firefox or another browser.
michael
yeah of course that works, but if we're talking 100+ images that could take a while :)
mgroves
Correct. I might be wrong but I think we're not talking about 100's of images... possibly 10 or 20. In such a case its manageable. I'm looking into other option where it does create a GIF ... but so far its in various shades of green (wtf!?)
michael
I've updated it with extra code in the hopes that someone can solve the Green-problem...
michael
@michael, Using Imagick results in an image without none of the green stuff. See my answer.
CD Sanchez
+1  A: 

I took a look at the code, and it seems like it should be possible with a little hacking.

By looking at the list of cycle objects in Palette.Cycles, you should be able to figure out the cycle length of each cycle with the cycle.high, cycle.low, and cycle.rate fields. That said, you'll need a different algorithm for each of the six possible values for cycle.reverse.

Once you know the length of each cycle in the list (in milliseconds, if I'm reading the code correctly), you can find the least common multiple of all of the cycle lengths, which would tell you the total length of the animation. In practice, though, you'd want to floor-divide them by your sample period first, (say, 100 milliseconds for ten frames a second) in order to get a lower common multiple.

Then rig the animate function in main.js to take a tickCount parameter and pass that into palette.cycle, instead of using anything based on real time. Increase the tick count by your sample period with each iteration.

From there, you should be able to modify the Bitmap class's render method, adding the necessary logic to rip the canvas to a file. There appear to be libraries that can manage this last bit for you. I would recommend saving the files using the tick count as the file name (with enough leading zeros to keep them in order.) Stitching all the images together into an animated GIF might be possible to execute as a batch job using the right software.

Of course, I haven't actually tried this. You might want to put in checks, for instance, to make sure that you don't stumble upon an animation with an epic cycle length and creation millions of images on your hard drive.

As an aside, you could also, with a little more work, figure out the exact time until the next update and take irregular samples, but you'd have to figure out how to store that delay information such that you could use it to assemble the completed GIF.

WCWedin