为什么putImageData这么慢?

时间:2010-10-17 10:37:32

标签: performance html5 canvas

我正在使用相对较大的Canvas,其中绘制了各种(复杂)内容。然后我想保存Canvas的状态,所以我可以快速将其重置为现在的状态。我为此使用getImageData并将数据存储在变量中。然后我将更多东西绘制到画布上,稍后将使用putImageData将Canvas重置为保存状态时的状态。

但事实证明,putImageData非常慢。事实上,它比简单地从头开始重绘整个Canvas要慢,这涉及覆盖大部分表面的几个drawImage,以及超过40,000个lineTo操作,后续是笔画和填充。

从头开始重绘大约2000 x 5000像素的画布需要大约170毫秒,使用putImageData虽然需要高达240毫秒。与重绘画布相比,为什么putImageData这么慢,尽管重绘画布包括用drawImage填充几乎整个画布,然后使用lineTo,stroke和fill再次填充大约50%的画布和多边形。所以基本上每个像素在重绘时至少触摸一次。

因为drawImage似乎比putImageData快得多(毕竟,重绘画布的drawImage部分需要不到30毫秒)。我决定尝试不使用getImageData来保存画布的状态,而是使用canvas.toDataURL,然后从数据URL创建一个Image,我将坚持使用drawImage将其绘制到画布上。事实证明,整个过程要快得多,只需要大约35毫秒即可完成。

那么为什么putImageData比替代品(使用getDataURL或只是重绘)慢得多?我怎么能进一步加快速度呢?有没有,如果,通常什么是存储画布状态的最佳方式?

(所有数字都是使用Firefox中的Firebug测量的)

4 个答案:

答案 0 :(得分:77)

只需更新一下,了解最佳方法。我实际上在High Performance ECMAScript and HTML5 Canvas(pdf,德语)上写了我的学士论文,所以我现在收集了一些关于这个主题的专业知识。最明显的解决方案是使用多个画布元素。从一个画布到另一个画布的绘制速度与将任意图像绘制到画布一样快。因此,“存储”画布的状态与使用两个画布元素时再次恢复它一样快。

This jsPerf testcase非常清楚地显示了各种方法及其优点和缺点。

为了完整起见,这里 应该这样做:

// setup
var buffer = document.createElement('canvas');
buffer.width = canvas.width;
buffer.height = canvas.height;


// save
buffer.getContext('2d').drawImage(canvas, 0, 0);

// restore
canvas.getContext('2d').drawImage(buffer, 0, 0);

此解决方案取决于浏览器,比获得upvotes的解决方案快5000倍。

答案 1 :(得分:11)

在Firefox 3.6.8中,我能够通过使用toDataUrl / drawImage来解决putImageData的缓慢问题。对我而言,它的工作速度足够快,我可以在处理mousemove事件时调用它:

保存:

savedImage = new Image()
savedImage.src = canvas.toDataURL("image/png")

要恢复:

ctx = canvas.getContext('2d')
ctx.drawImage(savedImage,0,0)

答案 2 :(得分:2)

首先,你说你正在用Firebug进行测量。我实际上发现Firebug大大减慢了JS的执行速度,因此你可能无法获得良好的性能数据。

对于putImageData,我怀疑这是因为函数需要一个包含许多Number个对象的大型JS数组,所有这些对象都必须检查范围(0..255)并复制到本机画布缓冲区。

也许一旦WebGL ByteArray类型可用,就可以更快地完成这类事情。

base64解码和解压缩数据(使用PNG数据URL)的速度似乎很奇怪,但只能用一个JS字符串调用一个JS函数,所以它主要使用本机代码和类型。

答案 3 :(得分:0)

这大概是因为现代浏览器对<canvas>元素使用了硬件加速,并且getImageData() / putImageData()要求将图像数据从图形卡传输到主机,反之亦然。众所周知,那太慢了。

使用两个画布将更快,因为所有数据都保留在图形卡上。