如何在HTML5画布上逐像素绘制?

9
假设我有一个大小为900x900的HTML5画布元素。
我有一个名为computeRow的函数,它接受一个参数,即网格上某一行的数字,并返回由900个数字组成的数组。每个数字代表0到200之间的数字。还有一个名为colors的数组,其中包含类似于rgb(0,20,20)的字符串数组。
基本上,我的意思是,我有一个函数逐像素地告诉给定行上每个像素的颜色应该是什么。运行此函数多次,我可以计算出画布上每个像素的颜色。
运行computeRow 900次的过程大约需要0.5秒钟。
然而,绘制图像所需的时间比这要长得多。
我编写了一个名为drawRow的函数,它以900个数字的数组作为输入并在画布上绘制它们。相比computeRow,drawRow需要更长的运行时间!我该如何解决?
drawRow非常简单。它看起来像这样:
function drawRow(rowNumber, result /* array */) {
    var plot, context, columnNumber, color;
    plot = document.getElementById('plot');
    context = plot.getContext('2d');
    // Iterate over the results for each column in the row, coloring a single pixel on
    // the canvas the correct color for each one.
    for(columnNumber = 0; columnNumber < width; columnNumber++) {
        color = colors[result[columnNumber]];
        context.fillStyle = color;
        context.fillRect(columnNumber, rowNumber, 1, 1);
    }
}
3个回答

9

我不确定你想要做什么,如果我理解有误,请见谅。

如果你想将每个像素点的颜色写入画布中,这是你需要做的:

var ctx = document.getElementById('plot').getContext('2d');
var imgdata = ctx.getImageData(0,0, 640, 480);
var imgdatalen = imgdata.data.length;
for(var i=0;i<imgdatalen/4;i++){  //iterate over every pixel in the canvas
  imgdata.data[4*i] = 255;    // RED (0-255)
  imgdata.data[4*i+1] = 0;    // GREEN (0-255)
  imgdata.data[4*i+2] = 0;    // BLUE (0-255)
  imgdata.data[4*i+3] = 255;  // APLHA (0-255)
}
ctx.putImageData(imgdata,0,0);

这比为每个像素绘制一个矩形要快得多。您唯一需要做的是将颜色分离为rgba()值。

1
+1,但需要注意的是,尽管这比使用fillRect()更快,但它也不是非常快,因为它不使用硬件加速。它会从GPU中提取纹理,对其进行操作,然后发送回去。此外,当画布被跨域数据污染时,getImageData无法工作,但如果旧内容不重要,您可以始终改用createImageData()。 - Philipp
那么,您的意思是通过操作内存中的像素而不是重复调用fillRect来完成这个任务的一种方法?我会尝试这样做,但我仍然愿意接受其他方案。 - Vivian River
有关createImageData()的更多信息,请参见此处:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createImageData - argon

4

fillRect 用于填充一个区域,使用单一颜色而不是逐像素填充。如果你逐像素填充,由于 CPU 受限,速度相对较慢。在这些情况下,您可以通过观察 CPU 负载来进行检查。如果:

  • 创建带有所需图像数据的单独图像。您可以使用工作线程在后台中填充此图像。有关使用工作线程的示例,请参阅http://gpupowered.org/node/11 博客文章。

  • 然后,使用 context.drawImage(image, dx, dy) 将图像传输到您需要的 2d 上下文中。


我熟悉使用工作线程。你能告诉我如何“在后台填充图像”吗? - Vivian River
我编辑了这篇文章,添加了关于如何使用后台工作线程进行二进制处理的具体信息。 - Prabindh

4
如果你从一个数组中将颜色值作为字符串读取每个像素,那么你使用的技术并不重要,瓶颈就在这里。对于每个像素,成本大致分为以下几个步骤:
- 查找数组(实际上在JavaScript中是一个节点/链表) - 获取字符串 - 将字符串传递给fillStyle - 将字符串解析(内部)成颜色值 - 准备绘制单个像素
这些操作在性能方面非常耗费时间。为了使其更加高效,您需要在绘图操作之前将该颜色数组转换为其他形式而不是带有字符串的数组。
您可以通过以下几种方式来实现:
- 如果数组来自服务器,请在发送之前将数组格式化为 blob/typed array。这样,您几乎可以将返回的数组内容直接复制到画布的像素缓冲区中。 - 使用Web Workers解析数组,并将其作为可传输对象传递回去,然后将其复制到画布的缓冲区中。这可以直接复制到画布中,或者反过来,将像素缓冲区传输到工作者,填充其中并返回。 - 按颜色值对数组进行排序,并按颜色组更新颜色。这样,您可以使用 fillStyle 或计算颜色到 Uint32 值中,然后使用 Uint32 缓冲区视图将其复制到画布中。如果颜色非常分散,则这种方法效果不佳,但如果颜色表示小的调色板,则效果还可以。
如果您被颜色的格式所困扰,那么我首先建议使用第二种选项(取决于大小)。它会使您的代码异步化,因此这也是您需要处理的一个方面(即完成操作时的回调)。
当然,你也可以在同一线程上解析数组,并想办法为用户掩盖它,以防它创建可感知的延迟(对于较慢的计算机而言,900x900 不应该是什么大问题)。
如果您将数组转换为无符号 32 位值并将结果存储在 Typed Array 中,则可以使用 Uint32 而不是每字节使用更快的方法来迭代您的画布像素缓冲区。

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