在<canvas>中绘制纵横字谜网格的最快算法是什么?

10

我正在渲染一个单元格网格,非常类似于填字游戏中的网格,但使用四种不同的颜色来填充每个单元格(不仅限于黑色或白色)。

该网格大小约为160x120,我需要尽可能快地进行渲染,因为它将用于显示细胞自动机动画。

我尝试了两种不同的方法来渲染网格:

  • Render each cell using something like:

    var w = x + step;
    var h = y + step;
    canvasContext.fillStyle=cell.color;
    canvasContext.fillRect(x+1,y+1,w-1,h-1);
    canvasContext.strokeRect(x,y,w,h);
    
  • Render the all of cells without the border, and then render the grid lines using:

    var XSteps = Math.floor(width/step);
    canvasContext.fillStyle = gridColor;
    for (var i = 0, len=XSteps; i<len; i++) {
        canvasContext.fillRect(i*step, 0, 1, height);
    }
    //Similar thing for Y coord
    
两种算法表现都不佳:在两种情况下,绘制网格比单元格更慢。我错过了什么吗?我该如何优化这些算法?还有其他我应该尝试的方法吗?
注意:网格会移动,因为用户可以移动它或缩放视图。
总的问题是:在一个元素上绘制一个单元格网格的最快算法是什么?

1
请告诉我更多关于您的需求。网格是否移动?您是否有示例代码可供我修改? - Simon Sarris
1
我不知道这是否有帮助,但你可以看一下我做的东西:演示源代码。一个想法(我所做的)是在不同的画布上分别绘制网格和单元格(因为网格不经常改变)。 - Felix Kling
1
你可以用网格颜色填充画布,然后将单元格绘制为比一个像素小的大小。 - Basic
@SimonSarris 网格不动了(现在我明白在每次迭代中重新绘制它毫无意义)。如果您感兴趣,代码托管在 https://bitbucket.org/cinos/wireworld/ 上(非常 alpha 状态)。这个特定的代码在 src/grid.js 的 105-107 行。 - Sergio Cinos
1
@SimonSarris 嗯,实际上它是“移动”的,因为用户可以移动和缩放视图。 - Sergio Cinos
啊,那么Phrogz的方法可能是你绘制网格的最佳选择! - Simon Sarris
3个回答

9

做事最快的方法是根本不去做。

在一个画布上绘制您不变的网格,然后在另一个覆盖(或下方)的画布上绘制(清除和重绘)您的元胞自动机。让浏览器(在其所有本地编译优化的荣耀中)为您处理脏污、重绘和合成。

或者(更好的方式),如果您不打算更改网格大小,只需创建一个小图像,让 CSS 填充为背景即可。

CSS 背景图像转换为画布演示:http://jsfiddle.net/LdmFw/3/

基于 this excellent demo,这里有一个完全通过 CSS 创建的背景图像网格;通过这种方式,您可以按需要更改大小(以整像素为增量)。

CSS3 网格转换为画布演示:http://jsfiddle.net/LdmFw/5/

如果您必须绘制一个网格,则最快的方法是仅绘制线条:

function drawGrid(ctx,size){
  var w = ctx.canvas.width,
      h = ctx.canvas.height;
  ctx.beginPath();
  for (var x=0;x<=w;x+=size){
    ctx.moveTo(x-0.5,0);      // 0.5 offset so that 1px lines are crisp
    ctx.lineTo(x-0.5,h);
  }
  for (var y=0;y<=h;y+=size){
    ctx.moveTo(0,y-0.5);
    ctx.lineTo(w,y-0.5);
  }
  ctx.stroke();               // Only do this once, not inside the loops
}

网格绘制演示: http://jsfiddle.net/QScAk/4/

对于 mn 列,这只需要在单次绘制中进行 m+n 条线的绘制。与绘制 m×n 个矩形相比,性能差异可能会非常显著。

例如,在一个 512×512 的 8×8 格子的网格中,使用上述代码只需要在一次 stroke() 调用中绘制 128 条线,而在朴素情况下则需要进行 4,096 次 fillRect() 调用。


1
这个会起作用,但如果它不动,为什么不使用CSS背景图片呢? - Simon Sarris
@SimonSarris 我想如果 OP 想在运行时动态改变/计算网格大小的话,这是一个好建议。不过这个建议很有价值,我也会把它加入到答案中。 - Phrogz
1
请查看此fiddle以获取data-url-image,或者查看此演示以获取CSS3-grid-background。(相关问题 - Bergi
@Bergi 非常棒的演示,只使用了CSS3背景。 - Phrogz

3
您正在使用fill绘制线条;我认为更快的方法是定义路径并描边它:
canvasContext.beginPath();
var XSteps = Math.floor(width / step);
canvasContext.fillStyle = gridColor;
var x = 0;
for (var i = 0, len = XSteps; i < len; i++) {
   canvasContext.moveTo(x, 0);
   canvasContext.lineTo(x, height);
   x += step;
}
// similar for y
canvasContext.stroke();

3

没有看到所有代码,很难确定性能的瓶颈所在,但是从最开始就可以:

  • 你可以使用一次drawImage调用来绘制背景网格,而不是使用stroke绘制。这将更快。如果它真的是静态的,那么你可以将css background-image设置为所需网格的图像。
  • 你经常使用fillRect和strokeRect,这些可以用rect()(路径命令)的多个调用替换,并仅在最后使用单个fill调用。这样所有填充的单元格都可以使用单个填充(或描边或两者都有)命令进行渲染。
  • 尽可能少地设置fillStyle/strokeStyle(如果可以避免就不要在循环内部设置)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接