抓取HTML5画布的每个帧。

22

这些调色板循环图简直让人惊叹:http://www.effectgames.com/demos/canvascycle/?sound=0

我想把它们中的一些(或全部)做成桌面背景。

我可以使用动画 gif 版本,但是我不知道如何从画布“动画”中获取它。是否有什么工具可用于完成此类工作(特别针对该链接和一般情况)。


4
这些作品在技术和艺术上都非常惊人!我找到的最接近答案的链接是:https://dev59.com/ynNA5IYBdhLWcg3wh-YC问题在于,它似乎只允许捕捉静态图像,而不是动画。也许可以通过编程将每个“帧”作为单独的图像捕捉,然后将它们拼接成一个GIF? - peterjmag
也许那个方法可行,也许您应该将评论变成答案,以便如果没有其他人想出主意,我可以给您分数 :) - Matthew Groves
3
我的尝试:http://imgur.com/p9gcV.gif - Cristian Sanchez
5个回答

24

我有一个解决方案,但是它需要您熟悉Firefox(安装Firebug插件)、Chrome或Safari的Javascript控制台

如果您不熟悉,请Google它,或在页面上右键单击任何地方,选择“检查元素”,然后查找“控制台”...

代码功能:

它允许您每1/10秒拍摄10个CANVAS元素的屏幕截图。这两个值都很容易修改,因为您可以调整以找到所需的迭代次数。对于您给出的示例,画布元素ID为“mycanvas”。

一旦它获得了屏幕截图,就会将图像输出到页面中。此时,您可以保存单个图像。

运行代码:

将以下代码粘贴到Javascript控制台中:

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

按下CTRL-Enter以执行它。

运行需要几秒钟,请耐心等待。

完成后,您应该拥有创建动画GIF所需的全部框架(可能还有更多),可通过ImageMagick、此网站MakeAGif.com或其他应用程序创建动画GIF。

附注

如果由于某种原因您需要输出为GIF或JPG而不是PNG,请根据需要更新以下内容:

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

变成下列其中一个:

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

支持输出为gifjpg的功能可能并不在所有浏览器中可用(大多数浏览器应该都支持)。

(大)更新#1

首先,我保留了上面的代码而不是更新它,因为两种方法都可能对其他人有帮助。

其次,这段新代码并没有解决问题。它几乎可以做到,但有一个主要的缺点。它创建了一个动画GIF(耶!),但它是各种绿色的(噢!)。不确定如何/为什么,但也许有人可以接手并看看我错过了什么。

所以我们开始吧...同样的规则适用-将其复制并粘贴到浏览器的Javascript控制台中(在Firefox中会有一些延迟,但在Google Chrome中运行得相当快...大约需要10秒钟左右)。

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

它使用了一些有用的代码、指针和JS文件,这些都在这篇博客文章中引用:JavaScript to (Animated) GIF。我直接使用了一些JS文件,但如果你要频繁使用它,你应该将这些文件复制到本地。

对我来说,输出结果是这个GIF:alt text

所以这算是一个东西,但可能不是你需要的...


1
哎呀,你说得对。我忘记把它移除了。我已经相应地更新了代码。谢谢你的提醒。 - donohoe
1
您应该能够使用鼠标右键单击,并出现“另存为图像...”选项(这取决于浏览器等)。尝试使用Firefox或其他浏览器。 - donohoe
1
对的。我可能错了,但我认为我们不是在谈论数百张图片...可能是10或20张。在这种情况下它还是可管理的。我正在寻找其他选项,在那里它可以创建一个GIF...但迄今为止它是各种绿色(什么鬼!?) - donohoe
1
我已经添加了额外的代码,希望有人能解决这个绿色问题... - donohoe
1
@michael,使用Imagick会导致图像中没有任何绿色的东西。请查看我的答案。 - Cristian Sanchez
显示剩余5条评论

14

编辑:最终将代码投入使用,这是结果:

alt text

Imagick生成了一张大图,因此我使用gimp进行了优化。

客户端代码是Michael代码的修改版本:

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

这段代码将会在屏幕上输出一些base64字符串。复制并保存到文本文件中,然后上传到您的Web服务器上。接着运行下面的脚本,它会将图像写入您的Web服务器。生成的图像可能会很大且可能会有卡顿,在GIMP中打开并进行优化处理和转换为GIF格式。保存时,强制所有帧使用相同的延迟时间,以使动画更加流畅。


使用PHP实现可能不太难。

  1. 使用JS获取每个帧的DataURL(Base64编码字符串)并将其发送到服务器。
  2. 解码Base64字符串并将其转换为Imagick对象。
  3. 将这些Imagick对象作为帧添加到新的Imagick对象中。
  4. 将Imagick对象另存为GIF文件到文件系统中。

既然已经有一种不错的JS解决方案了,如果您希望自动化处理,则可以添加服务器端代码:

<?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);

?>

我已经有一两年没写过PHP了,所以请务必仔细检查所有内容等等。


1
我并不是要贬低你的努力(毕竟这是对这个问题最成功的尝试),但我想指出你的图像正遭受着艺术家所警告的那种断断续续和笨拙循环的问题。最终动画的周期长度必须非常小心地选择,以减少这些伪影。一些试错或一些纸笔计算可能会有所帮助。不错的工作。 - Isabelle Wedin
1
@WCWedin:我同意——尽管直到动画结束时它没有像预期的那样回到最初的循环位置,但整个过程还是相当流畅的。我本来想更深入地思考一下,但认为生成gif的“概念证明”已经足够了——而且考虑到在我想出这个图像的时候答案已经被选择了,所以继续下去的动力很小。不过,如果有人能给我正确的帧数据,我会很乐意重新生成gif。如果我有时间,我想我可能会查看源代码,看看是否可以提取完整的循环。 - Cristian Sanchez

2

我看了一下代码,似乎只需要进行一些小改动就可以实现。

通过查看Palette.Cycles中的循环对象列表,您应该能够使用cycle.high、cycle.low和cycle.rate字段确定每个循环的周期长度。不过,对于cycle.reverse的六种可能值,您需要使用不同的算法。

一旦您知道了列表中每个循环的长度(如果我正确地阅读了代码,则为毫秒),您可以找到所有循环长度的最小公倍数,这将告诉您动画的总长度。不过,在实践中,您需要首先将它们按样本周期向下取整(例如,每秒十帧的100毫秒),以获得更小的公倍数。

然后在main.js中设置animate函数以接受tickCount参数,并将其传递到palette.cycle中,而不是使用基于实际时间的任何内容。每次迭代增加tick计数器的采样周期。

从那里开始,您应该能够修改Bitmap类的render方法,添加必要的逻辑来将画布拆分为文件。似乎有一些库可以为您管理这个最后的部分。建议使用tick计数作为文件名保存文件(前面加上足够数量的前导零以保持顺序)。使用合适的软件批量将所有图像拼接成一个动态GIF可能是可行的。

当然,我实际上还没有尝试过这个。您可能需要进行检查,例如确保您不会遇到具有史诗般循环长度并在硬盘上创建数百万个图像的动画。

此外,如果愿意再花一些功夫,您还可以确定下一个更新的确切时间并进行不规则采样,但您必须找出如何存储该延迟信息,以便将其用于组装完成的GIF。


1

尝试PhantomJS

这个脚本可以保存100帧。

var webPage = require('webpage');
var fs = require('fs');
var page = webPage.create();

var NB_FRAME = 100;
var current = 0;

page.open('http://www.effectgames.com/demos/canvascycle/?sound=0',
function(status) {
  if (status === "success") {
     var current = 0;
     var grabber = setInterval(function () {
      var frame = page.evaluate(function() {
       return document.getElementById('mycanvas').toDataURL("image/png").split(",")[1];
      });
      fs.write("./frame-" + current + ".png",atob(frame), 'wb');
  if (++current === NB_FRAME) {
     window.clearInterval(grabber);
     phantom.exit(0);
  }
   }, 1000);
}
});

运行它:

 phantomjs SaveCanvasFrame.js

然后使用ImageMagick
convert *.png animated.gif

这里我们来: animated


1

很遗憾,根据艺术创作者的说法,由于图片的不同部分具有不同的周期,将其转换为GIF动画并不是完全可能的。


1
我认为,由于动画可以通过GIF在帧之间有效地压缩匹配像素,因此需要数千帧并不像艺术家所担心的那样成为问题。当然,他的审美偏向于经济实用,所以他自然会反对仅仅将硬盘空间扔到问题上,但这并不意味着这是不可能的! - Isabelle Wedin
确实有办法,只是不是直接的方法。我也同意WCWedin所说的。 - donohoe
好的,那么你只需要找出所有动画循环的最小公倍数。这将是你需要保存的图像数量。 - syockit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接