如何提高 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 是的,当然。其实一开始我根本不想优化它(ctxcreateBuffer()都在函数里面),后来觉得编码浪费,把它们移到外面,所以建议也是(它获得了0.5-1 毫秒,顺便说一句)。 最有可能在上面的代码中需要时间的是 sqrt 操作。使用 LUT 应该会更快。

以上是关于如何提高 HTML 画布绘制像素的性能的主要内容,如果未能解决你的问题,请参考以下文章

如何计算偏移量以绘制到画布中应用了比例变换的最近像素?

在 HTML5 画布上绘制一个点 [重复]

使用画布绘制单个像素

如何在没有抗锯齿的情况下在画布上绘制像素字体

传单 js:将 POI 绘制为画布

canvas使用图像(绘制图、图像平铺、图像剪裁、像素处理、绘制文字)