关于性能:为什么putImageData这么慢?

Why is putImageData so slow?

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

但是,事实证明,putImageData非常慢。实际上,它比简单地从头重新绘制整个Canvas慢,后者涉及多个drawImage覆盖大部分表面,并且超过40.000 lineTo操作,然后进行笔触和填充。

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

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

那么为什么putImageData比其他方法(使用getDataURL或只是重绘)要慢得多?我如何才能进一步加快速度?是否存在,如果可以,通常什么是存储画布状态的最佳方法?

(所有数字均使用Firefox内部的Firebug进行测量)


仅此做一个最好的方法的小更新。我实际上写了关于高性能ECMAScript和HTML5 Canvas的学士论文(pdf,德语),因此,到目前为止,我已经在该主题上积累了一些专业知识。显然最好的解决方案是使用多个画布元素。从一个画布上绘制到另一块画布上的速度与在画布上绘制任意图像一样快。因此,"存储"画布的状态与稍后使用两个画布元素再次恢复它的速度一样快。

这个jsPerf测试用例非常清楚地展示了各种方法以及它们的优缺点。

为了完整起见,这是您真正应该如何做:

1
2
3
4
5
6
7
8
9
10
11
// 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);

根据浏览器的不同,此解决方案的速度比获得支持的解决方案快5000倍。


在Firefox 3.6.8中,我能够通过改用toDataUrl / drawImage来解决putImageData的慢度问题。对我来说,它的运行速度足够快,我可以在处理mousemove事件时调用它:

要保存:

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

要还原的:

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


首先,您说您正在使用Firebug进行测量。实际上,我发现Firebug大大降低了JS的执行速度,因此您可能无法获得良好的性能指标。

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

也许一旦WebGL ByteArray类型可用,这种事情就可以变得更快。

base64解码和解压缩数据(使用PNG数据URL)更快似乎很奇怪,但是只用一个JS字符串调用一个JS函数,因此它主要使用本机代码和类型。


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

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