【问题标题】:How do I draw multiple rectangles into a canvas at once?如何一次将多个矩形绘制到画布中?
【发布时间】:2015-08-08 20:46:06
【问题描述】:

我正在使用 drawRect 在 HTML5 画布上绘制大量(约 20000 个)矩形,每个矩形都位于不同的预定位置。我是从一个循环中做到这一点的:

for (var i = 0; i < 100; ++i) {
    for (var j = 0; j < 200; ++j) {
        context.fillStyle = '#000000';
        context.fillRect(i * 8, j * 2, 6, 1);
    }
}

Working demo on JSFiddle

这个 sn-p 在 800 像素宽的画布中绘制 100 个条形图,每个条形图宽度为 6 像素,每个条形图由多个相互堆叠的小 (1x6) 矩形组成:在提供的示例中静态计数为 200 ,但我的应用程序中的计数是动态变化的(因此需要重新渲染它们)。

这需要几十毫秒,本身并不严重。但是整个过程被重复调用,这显着影响了性能,而且效果不佳。

是否有解决方案或变通方法可以在一个画布指令中绘制这样的条,希望更好地利用硬件加速?

每个矩形在我的应用程序中都会发光,因此水平切割它们不是一个好方法。我已经尝试过使用离屏画布,在那里绘制,并将其图像输出渲染到主画布,但没有明显的性能提升。

【问题讨论】:

  • 您是否尝试过为下一次迭代保留屏幕外画布?如果内容是“静态的”,为什么还要不断重绘呢?
  • @Amit 条形高度根据输入数据而变化,因此需要重新渲染。我想我会把它编辑成问题。
  • 好的...那么您至少可以将 1 个条复制到其余条吗? (可能以“二进制增量” 1 - 2 - 4 - 8... 方式?)
  • @Amit 这在理论上是可能的,但在实践中似乎非常不可行。用于绘制条形的输入数据是来自Web Audio API Analyser node 的字节频率数据。
  • 相反方法:在静态图像/屏幕外画布中放置完整条,将其复制到目标画布,然后ctx.clearRect 不需要的区域。这样可以吗?

标签: javascript canvas


【解决方案1】:

为自己制作一个包含单个全高条的单个(屏幕外)画布图像,然后对于每个动态高度条,仅将所需数量的垂直像素从其复制到屏幕画布上。

【讨论】:

  • @约翰·怀特。 Alnitak 在这里有你的答案。复制图像比绘制新矩形要快得多。您可以使用drawImage 的剪辑版本,仅将所需数量的垂直矩形从源栏复制到屏幕画布。顺便说一句,我知道您的示例代码仅用于演示,但将 context.fillStyle='#000000' 移出您的循环 - 它只需要在循环开始之前设置一次。并且用while 循环替换for 循环也会使您的演示更快。
  • @markE 是的,这是我认为我应该做的事情,尽管我认为我缺少一些微不足道的东西。
  • @JohnWhite。我无法抗拒在加法和减法之间进行时间测试:jsfiddle.net/m1erickson/0vjz3dqh。使用这两种方法,我在速度适中的计算机上每屏幕条的性能大约为 1 毫秒。
  • @MarkE 在我的笔记本电脑上,一个条形方法的速度约为 0.15 毫秒,另一种方法的速度为 0.45 毫秒。
  • @MarkE 2015 年初 13" rMBP 双核 3.1 GHz i7,带有(相对糟糕的)集成显卡
【解决方案2】:

你可以像这样初始化有用的变量:

var context = canvas.getContext("2d");
var map = context.getImageData(startX, startY, width, height);

canvas 是 document.getElementById("myCanvas") 之类的返回值。

startX 是起点 x,startY 是起点 y。您可以使用 0 作为它们的值,但我不想假设它从画布的左上角开始。 width 是宽度,height 是高度。现在,您可以使用循环和设置值,如下所示:

map.data[4 * index] = r; //red
map.data[4 * index + 1] = g; //green
map.data[4 * index + 2] = b; // blue

当你完成你的循环后,保存一些东西,像这样:

context.putImageData(map, startX, startY);

应该提高速度,因为您只阅读一次,只绘制一次。至于循环,您只是在设置值,与在画布中绘图相比,这是一个便宜的操作。因此,这种优化的想法是:一次读取内容,在循环中设置值并只绘制一次,而不是每次获得一些输入时都绘制。您也可以省略读取相关部分并自己生成数据,但我决定向您展示如何读取数据,如果您需要了解一些已绘制的内容。

【讨论】:

  • 这意味着一切都必须“手工”完成。 Context2D 对象有很多功能,包括所有的描边、填充、效果等等。
  • @Amit,不是真的。我的回答不是需要手工完成。这是关于避免重复绘制。我的解决方案建议是“手工完成”,因为我对画布的工作还不够多,无法熟记它的功能。你可以按照我的建议去做,或者你可以从某个地方使用一些功能。虽然我没有隐瞒您解决问题的方法的重要性,但我强调最重要的是这里的想法:即应该减少要优化的图纸数量。
  • @LajosArpad。我怀疑这种方法会慢很多,因为它会更改单个像素而不是像素组,而且getImageDataputImageData 没有经过硬件优化。
  • @markE,此方法从画布加载图像数据,解析并存储。如果此方法单独存储每个像素,您关于单个像素更改的声明将是有效的。相反,它准备所有像素,然后一次存储准备好的值。所以,我不同意你的第一个说法。
  • @markE,关于你关于硬件优化的第二个陈述,我相信这个问题至少有点复杂,因为有许多浏览器跨越许多操作系统。除非您的陈述对每个浏览器都是正确的(这些功能不是硬件优化的),否则您的观点很重要。但是,是否有替代方案,一些功能可以在所有成功的浏览器中执行相同的任务并进行硬件优化?
【解决方案3】:

看到这看起来像是某种背景,您将在上面绘制其他东西,您是否考虑过将其转换为静态图像并将画布背景设置为此?您可以关闭/打开背景,或者您在画布上绘制的任何内容都会在填充/绘制发生的区域与背景重叠。

当然,如果您要动态调整它,也可以考虑将一个单独的画布元素精确定位在另一个画布元素之下。

【讨论】:

  • 感谢您的帮助,但条形高度会根据输入数据而变化,这就是需要重新渲染的原因。我忘了在我的问题中包含这个。
  • 啊,好吧!我错误地认为这是背景。一组输入数据的所有条形高度/宽度是否相同?顺便说一句,您可以将 fillStyle 移出循环以获得一些 minor 性能增益(如果这不会改变)
  • 是的,没错。事实上,它很快就会成为一个音频可视化组件,因此对于频率上的相同值,这些小矩形的相同数量将被绘制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-19
  • 1970-01-01
  • 2015-10-22
  • 1970-01-01
  • 2013-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多