如何提高 HTML 画布绘制像素的性能
Posted
技术标签:
【中文标题】如何提高 HTML 画布绘制像素的性能【英文标题】:How to improve HTML canvas performance drawing pixels 【发布时间】:2020-02-17 07:10:39 【问题描述】:我正在 600x600 像素的 html 画布上绘制数据的可视化。每个像素需要大约 10 次加法/乘法,所以计算量很小,但不是很多。不幸的是,这需要大约半秒钟的时间来绘制。这意味着我的滑块(通常会影响每个像素)不会平滑地更新可视化效果。
是否可以通过以更有效的方式进行绘图来提高性能?我目前正在为每个像素调用以下内容:
ctx.fillStyle = computedPixelColor
ctx.fillRect(x, y, 1, 1)
我读到创建图像并修改像素,然后将该图像插入 HTML 会更慢。真的吗?这个过程有什么技巧吗?
【问题讨论】:
developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/… 使用getImageData
/ putImageData
,您可以通过操纵表示图像的内存数组来加快速度。查看this我几年前写的旧教程,768x768 图像,每个像素也有几个加法/乘法,但动画很流畅。
我刚刚尝试了getImageData
/putImageData
机制,它的速度大致相同:(。
确保将整个图像放在一个调用中,而不是更新每个像素。请参阅链接示例。如果仍有问题,请使用实际代码创建minimal verifiable example。
你能分享一下你有什么吗?使用 ImageData(注意,只有一个)肯定会比 360000 fillRect 快。此外,根据获取像素的方式,您可以处理 ImageData 缓冲区的 Uint32Array 视图,以便更快地访问。
【参考方案1】:
莫尔效应示例,在 90 年代的演示中很流行。这个实现,直接ImageData
操作对我来说有 4-5 毫秒的帧时间(计算 600x600 像素,并用putImageData()
显示结果),虽然它是一个相对较新的 PC。当然,你的计算在做什么可能很重要,这个很简单。
var ctx=cnv.getContext("2d");
var data=ctx.createImageData(600,600);
var buf=new Uint32Array(data.data.buffer);
function draw(x1,y1,x2,y2)
var i=0;
for(var y=0;y<600;y++)
for(var x=0;x<600;x++)
var d1=(Math.sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1))/10) & 1;
var d2=(Math.sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2))/10) & 1;
buf[i++]=d1==d2?0xFF000000:0xFFFFFFFF;
ctx.putImageData(data,0,0);
var cnt=0;
setInterval(function()
cnt++;
var start=Date.now();
draw(300+300*Math.sin(cnt*Math.PI/180),
300+300*Math.cos(cnt*Math.PI/180),
500+100*Math.sin(cnt*Math.PI/100),
500+100*Math.cos(cnt*Math.PI/100));
t.innerText="Frame time:"+(Date.now()-start)+"ms";
,20);
<div id="t"></div>
<canvas id="cnv" ></canvas>
为了比较,每像素fillRect()
s 的相同效果产生 205 毫秒的帧时间:
var ctx=cnv.getContext("2d");
function draw(x1,y1,x2,y2)
//ctx.clearRect(0,0,600,600);
//ctx.fillStyle="black";
for(var y=0;y<600;y++)
for(var x=0;x<600;x++)
var d1=(Math.sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1))/10) & 1;
var d2=(Math.sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2))/10) & 1;
//if(d1==d2)ctx.fillRect(x,y,1,1);
ctx.fillStyle=d1==d2?"black":"white";
ctx.fillRect(x,y,1,1);
var cnt=0;
setInterval(function()
cnt++;
var start=Date.now();
draw(300+300*Math.sin(cnt*Math.PI/180),
300+300*Math.cos(cnt*Math.PI/180),
500+100*Math.sin(cnt*Math.PI/100),
500+100*Math.cos(cnt*Math.PI/100));
t.innerText="Frame time:"+(Date.now()-start)+"ms";
,20);
<div id="t"></div>
<canvas id="cnv" ></canvas>
即使使用“优化”变体(仅使用全局clearRect()
和fillRect()
-ing 黑色像素,请参阅注释部分),仍然需要每帧 60-70-80 毫秒(速度跳跃很多)。
所以无论你在做什么每像素的东西,真正阅读 Mozilla tutorial 关于 ImageData
已经在 cmets 中建议,使用直接缓冲区很重要。
【讨论】:
您可以一次写入整个像素。获取 32 位数组var buf = new Uint32Array(data.data.buffer);
然后通道顺序为 ABGR
所以要写入白色和黑色像素,用 buf[i++] = d1===d2 ? 0xFFFFFFFF : 0xFF000000;
替换内循环的最后 3 行
@Blindman67 是的,当然。其实一开始我根本不想优化它(ctx
和createBuffer()
都在函数里面),后来觉得编码浪费,把它们移到外面,所以建议也是(它获得了0.5-1 毫秒,顺便说一句)。
最有可能在上面的代码中需要时间的是 sqrt 操作。使用 LUT 应该会更快。以上是关于如何提高 HTML 画布绘制像素的性能的主要内容,如果未能解决你的问题,请参考以下文章