如何在 HTML5 画布上逐个像素地绘制
Posted
技术标签:
【中文标题】如何在 HTML5 画布上逐个像素地绘制【英文标题】:How to draw on an HTML5 Canvas, pixel-by-pixel 【发布时间】:2014-01-16 21:52:13 【问题描述】:假设我有一个 900x900 html5 Canvas 元素。
我有一个名为computeRow
的函数,它接受网格上的行数作为参数,并返回一个包含 900 个数字的数组。每个数字代表一个 0 到 200 之间的数字。有一个名为 colors
的数组,其中包含一个字符串数组,例如 rgb(0,20,20)
。
基本上,我的意思是我有一个函数可以逐个像素地告诉画布上给定行中的每个像素应该是什么颜色。多次运行这个函数,我可以计算出画布上每个像素的颜色。
运行computeRow
900次的过程大约需要0.5秒。
但是,图像的绘制时间比这要长得多。
我所做的是我编写了一个名为drawRow
的函数,它接受一个包含 900 个数字的数组作为输入并将它们绘制在画布上。 drawRow
的运行时间比 computeRow
长得多!我该如何解决这个问题?
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);
【问题讨论】:
【参考方案1】:我不确定你到底想做什么,所以如果我错了,我深表歉意。
如果您尝试为画布上的每个像素写入颜色,您可以这样做:
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,但应该注意的是,虽然这可能比使用 fillRect() 更快,但也不是很快,因为它不使用硬件加速。它将纹理从 GPU 中拉出,对其进行操作,然后将其发送回。另外,getImageData doesn't work when the canvas is tainted by cross-domain data,但是当您对旧内容不感兴趣时,您可以随时使用createImageData()。 那么,您是说这样做的一种方法是操作内存中的像素,而不是重复调用fillRect?我会试试这个,但我仍然愿意接受其他选择。【参考方案2】:如果您将颜色值作为字符串从数组中读取每个像素,那么您使用哪种技术作为瓶颈并不重要。
对于每个像素,成本按(大致)以下步骤进行拆分:
查找数组(实际上是 javascript 中的节点/链表) 获取字符串 将字符串传递给fillStyle 将字符串(内部)解析为颜色值 准备绘制单个像素这些在性能方面是非常昂贵的操作。为了提高效率,您需要将该颜色数组转换为其他内容,而不是在绘图操作之前包含字符串的数组。
您可以通过多种方式做到这一点:
如果数组来自服务器,请尝试将数组格式化为 blob/类型化数组,而不是在发送之前。这样您就可以将返回数组的内容几乎原样复制到画布的像素缓冲区中。 使用网络工作者解析数组并将其作为可转移对象传回,您可以将其复制到画布缓冲区中。这可以直接复制到画布上 - 或者反过来,将像素缓冲区转移到工作人员,在那里填充并返回。 按颜色值对数组进行排序并按颜色组更新颜色。这样,您可以使用 fillStyle 或将颜色计算为 Uint32 值,然后使用 Uint32 缓冲区视图将其复制到画布上。如果颜色非常分散,则此方法效果不佳,但如果颜色代表一个小调色板,则效果很好。如果您对颜色的格式感到困惑,那么我会推荐第二种选择,主要取决于尺寸。它使您的代码异步,因此这也是您需要处理的一个方面(即操作完成时的回调)。
您当然可以只在同一个线程上解析数组,并找到一种方法为用户稍微伪装一下,以防它造成明显的延迟(即使对于较慢的计算机,900x900 也不应该是那么大的问题)。
如果您将数组转换为无符号 32 位值并将结果存储在类型化数组中。这样,您可以使用 Uint32 迭代您的画布像素缓冲区,这比使用逐字节方法快得多。
【讨论】:
【参考方案3】:fillRect
仅用于此目的 - 用单一颜色填充一个区域,而不是逐个像素。如果你逐个像素地做,它一定会因为你受 CPU 限制而变慢。在这些情况下,您可以通过观察 CPU 负载来检查它。如果
创建一个单独的图像并填充所需的图像数据。您可以使用工作线程在后台填充此图像。博客文章http://gpupowered.org/node/11
中提供了使用工作线程的示例然后,使用context.drawImage(image, dx, dy)
将图像blit 到您想要的2d 上下文中。
【讨论】:
我熟悉使用工作线程。你能告诉我如何“在背景中填充图像”吗? 我编辑了帖子以添加有关如何使用后台工作人员进行二进制处理的具体信息。以上是关于如何在 HTML5 画布上逐个像素地绘制的主要内容,如果未能解决你的问题,请参考以下文章