在<canvas>?</canvas>中绘制填字游戏网格的最快算法

时间:2012-08-03 15:14:55

标签: javascript performance algorithm canvas

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

网格大小约为160x120,我需要尽可能快地渲染它,因为它将用于显示Cellular automaton动画。

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

  • 使用以下内容渲染每个单元格:

    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);
    
  • 渲染没有边框的所有单元格,然后使用:

    渲染网格线
    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
    

两种算法都表现不佳:在两种情况下,绘制网格的速度都比单元格慢。我错过了什么吗?我该如何优化这些算法?还有其他方法我应该尝试吗?

注意:网格会移动,因为用户可以移动它或缩放视图。

一般的问题是:在元素上绘制单元格网格的最快算法是什么?

3 个答案:

答案 0 :(得分:8)

做某事的最快方法就是不要这样做。

在一个画布上绘制一次不变的网格,并在上面(或下面)分层的另一个画布上绘制(并清除和重绘)细胞自动机。让浏览器(在其所有本机编译优化的荣耀中)为您处理弄脏,重绘和合成。

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

将CSS背景图像演示到Canvas:http://jsfiddle.net/LdmFw/3/

基于this excellent demo,这是一个完全通过CSS创建的背景图像网格;使用此功能,您可以根据需要更改大小(以整像素为增量)。

CSS3 Grid to Canvas的演示: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/

对于 m 行和 n 列,这需要 m + n 行一次绘制。将此与绘制 m × n 个别作品进行对比,您可以看到性能差异非常显着。

例如,在天真的情况下,512×512网格的8×8个小区将需要4,096个fillRect()个调用,但只需要在单个 stroke()使用上面的代码调用。

答案 1 :(得分:3)

如果没有看到所有代码知道性能的发展方向,那真的很难提供帮助,但这只是昙花一现:

  • 您可以使用一次调用drawImage来绘制背景网格,而不是使用笔划绘制背景网格吗?那会更快。如果它真的是静态的,那么你可以在画布上设置一个css background-image到你想要的网格图像。
  • 您正在使用fillRect和strokeRect,这些可能会被多次调用rect()(路径命令)所取代,最后只能调用fill。因此,所有填充的单元格都可以使用单个填充(或抚摸或两者)命令一次渲染。
  • 尽可能少地设置fillStyle / strokeStyle(如果可以避免的话,不要在循环内部)

答案 2 :(得分:3)

您正在使用填充绘制线条;我认为,定义一条路径并对其进行划分会更快:

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();