javascript 抓取 HTML5 画布的每一帧

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3346069/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 00:54:24  来源:igfitidea点击:

Grabbing each frame of an HTML5 canvas

javascriptanimationhtmlscreen-scraping

提问by Matthew Groves

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

这些调色板循环图像令人叹为观止: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).

我可以使用动画 gif 版本,但我不知道如何从画布“动画”中获取它。是否有任何可用的东西可以按照这些方式做一些事情(特别是对于该链接和一般而言)。

回答by donohoe

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

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

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

它允许您CANVAS每 1/10 秒对元素进行 10 次屏幕抓取。这两个值都很容易修改,因为您可以调整以找到想要获得的迭代次数。对于您给出的示例,画布元素 ID 是“ 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:

将以下代码粘贴到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);

and press CTRL-Enterto execute it.

并按CTRL-Enter执行它。

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.

之后,您应该拥有通过 ImageMagick、本网站MakeAGif.com或其他应用程序创建动画 GIF 所需的所有帧(可能更多)。

Side Note

边注

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

如果由于某种原因您需要输出为 JPG 的 GIF 而不是 PNG 只需根据需要更新:

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 gifor jpgmay not be in all browsers (should be most).

可能并非所有浏览器都支持输出为gifjpg(应该是大多数)。

(BIG) UPDATE #1

(大)更新 #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.

其次,这个新代码不能解决问题。这只是一个主要缺点。它创建了一个动画 GIF(Yipee!),但它有各种深浅不同的绿色(Boooo!)。不知道如何/为什么,但也许有人可以从这里拿走它,看看我错过了什么。

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

所以我们开始......适用相同的规则 - 将其复制并粘贴到浏览器的 Javascript 控制台中(它在 Firefox 中滞后,但谷歌浏览器非常快......运行 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);

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.

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

The output for me was this GIF: alt text

我的输出是这个 GIF: 替代文字

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

所以它的东西,但不是你需要的......

回答by Cristian Sanchez

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.

Imagick 生成了一个大图像,所以我继续使用 gimp 进行了优化。

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

客户端代码是 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);

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.

它会将一堆 base64 字符串写入屏幕。复制它们并将它们保存到文本文件中,然后将其上传到您的 Web 服务器。然后运行另一个脚本(见下文),它会将图像写入您的 Web 服务器。生成的图像会很大并且可能会断断续续,因此打开 GIMP 并针对差异和 GIF 进行优化。保存时,强制它对所有帧使用相同的延迟,以便动画流畅。



May not be too hard using PHP.

使用 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.
  1. 用 JS 抓取每一帧的 dataURL(base64 编码的字符串)并将其发送到服务器。
  2. 解码 base64 字符串并将它们转换为 Imagick 对象。
  3. 将这些 Imagick 对象作为框架添加到新的 Imagick 对象中。
  4. 将 Imagick 对象作为 GIF 保存到文件系统。

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

由于 michael 已经发布了一个不错的 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);

?>

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

我已经有一两年没有写太多 PHP 了,所以一定要仔细检查一切等等。

回答by WCWedin

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.

通过查看 Palette.Cycles 中的循环对象列表,您应该能够通过 cycle.high、cycle.low 和 cycle.rate 字段计算出每个循环的循环长度。也就是说,您需要为 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.

一旦您知道列表中每个周期的长度(以毫秒为单位,如果我没有正确阅读代码),您就可以找到所有周期长度的最小公倍数,这将告诉您动画的总长度。但是,在实践中,您首先希望将它们按采样周期进行地板划分(例如,每秒 10 帧的 100 毫秒)以获得较低的公倍数。

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.

然后在 main.js 中装配 animate 函数以获取一个 tickCount 参数并将其传递到 Palette.cycle,而不是使用任何基于实时的东西。每次迭代时,按采样周期增加滴答计数。

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.

从那里,您应该能够修改 Bitmap 类的渲染方法,添加必要的逻辑以将画布翻录到文件中。似乎有一些库可以为您管理这最后一点。我建议使用滴答计数作为文件名保存文件(有足够的前导零以保持它们的顺序。)将所有图像拼接成动画 GIF 可能可以使用正确的软件作为批处理作业执行。

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.

顺便说一句,您还可以通过多做一点工作,找出下一次更新之前的确切时间并进行不规则采样,但您必须弄清楚如何存储该延迟信息,以便您可以使用它来组装完成的GIF。

回答by youyoup

Try PhantomJS

试试 PhantomJS

This script saves 100 frames.

此脚本保存 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);
}
});

Run it:

运行:

 phantomjs SaveCanvasFrame.js

Then use ImageMagick

然后使用ImageMagick

convert *.png animated.gif

Here we go : animated

开始了 : 动画

回答by syockit

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.

可悲的是,根据艺术创作者的说法,由于图片的不同部分具有不同的周期,因此不太可能将其转换为GIF动画。