使用getImageData从Canvas获取像素的颜色给出错误的颜色

时间:2016-12-15 21:41:27

标签: javascript canvas

我正在制作一个基于维恩图的游戏,并实施了一个填充填充算法来填充每个区域,如下所示。

enter image description here

由于我不希望能够填充圆圈的轮廓,我会检查以确保如果点击边缘则不会填充。这是通过检查颜色是否为红色来完成的。

function circleEdgeClicked(x, y) {
   var colorLayer = context.getImageData(0, 0, canvasWidth, canvasHeight);
   var pixelPos = (x * canvasWidth + y) * 4;

   startR = colorLayer.data[pixelPos];
   startG = colorLayer.data[pixelPos + 1];
   startB = colorLayer.data[pixelPos + 2];

   if (startR === 255 && startG === 0 && startB === 0) {
     console.log("Colour is red");
   }

   return (startR === 255 && startG === 0 && startB === 0);
 }

所需的行为是点击将洪水填充为黄色,另一次点击将通过填充白色来恢复。但是,有时在圆圈内部点击时,无论是黄色还是白色,该区域都不会填充相反的颜色。调试原因显示问题是因为getImageData错误地说点击的像素是红色的。

以下是JSFiddle中的代码,只需单击圆圈即可复制错误,经过几次尝试后,它将无法填充,日志会显示颜色为红色,即使它不是&# 39;吨。

在getImageData文档中有这样的说明 Due to the lossy nature of converting to and from premultiplied alpha color values, pixels that have just been set using putImageData() might be returned to an equivalent getImageData() as different values.

这与它有什么关系,是否有解决方法?

2 个答案:

答案 0 :(得分:1)

我不太确定,但由于像素数据是一维数组,为了将x,y转换为像素数据中数组的索引,你必须使用

(x + canvasWidth * y) * 4

因为y位置将表示你必须跳过多少行水平像素才能到达像素,那将是canvasWidth * y,然后是水平轴的+ x。

以上只是为了修复你的错误,但我的建议和问题是,你为什么不直接使用getImageData? getImageData()采用x,y,width和height参数,因此您只能通过它的位置提取单个像素。

function circleEdgeClicked(x, y) {
   // will return an array of 4 integers (rgba)
   var colorLayer = context.getImageData(x, y, 1, 1);

   startR = colorLayer.data[0];
   startG = colorLayer.data[1];
   startB = colorLayer.data[2];

   if (startR === 255 && startG === 0 && startB === 0) {
     console.log("Colour is red");
   }

   return (startR === 255 && startG === 0 && startB === 0);
 }

答案 1 :(得分:1)

从以下位置更改此行:

var pixelPos = (x * canvasWidth + y) * 4;

var pixelPos = (y * canvasWidth + x) * 4; // or use <<2 instead of * 4

所以y(代表行/步幅)而不是x乘以宽度。

预先缓存位图或读取单个像素也会更快:

var colorLayer = context.getImageData(x, y, 1, 1);

在这种情况下,您可能希望对x / y与宽度/高度进行边界检查。您也可以将其读取为32位值并直接比较0xff0000ff(ABGR),假设没有alpha,或者将其移除以删除alpha:

function circleEdgeClicked(x, y) {
   var color = new Uint32Array(context.getImageData(x, y, 1, 1).data.buffer)[0];
   return (color<<8) === 0xff00;  // is red? ignoring alpha
 }

另一个提升是在初始化红色轮廓时抓取位图。由于我们将阻止填充,你可以在这种状态下缓存位图,并在每次需要检查像素时检查而不传输新的位图(并且没有太多的内存开销,因为Uint32视图将共享缓冲区原始位图):

// by here the red outlines are drawn, so we grab it at this stage
var pixels =
      new Uint32Array(context.getImageData(0, 0, canvasWidth, canvasHeight).data.buffer);

// then reuse the buffer when a pixel needs to be checked
function circleEdgeClicked(x, y) {
   var color = pixels[y * canvasWidth + x];  // all values are 32-bit
   return (color<<8) === 0xff00;  // is red? ignoring alpha
 }

关于有损性质:存在这个问题,在某些情况下是由舍入误差引起的。最重要的是,如果从图像中绘制源图形,则加载时可以通过gamma或ICC配置文件更改位图值。

解决方法是实现公差,例如红色,接受值在250-255而不是正好255,蓝色和绿色范围为0-5等。(+/- 5这里是任意的,只是试验找到一个最佳值)。如果基本绘图具有非透明背景,您还会发现需要考虑alpha。

相关问题