Javascript 使用getImageData从画布获取像素的颜色,给出错误的颜色

Javascript 使用getImageData从画布获取像素的颜色,给出错误的颜色,javascript,canvas,Javascript,Canvas,我正在制作一个基于维恩图的游戏,并实现了一个洪水填充算法来填充每个区域,如下所示 因为我不想用洪水来填充圆的轮廓,所以我做了一个检查,确保如果单击了边,那么它不会被填充。这是通过检查颜色是否为红色来完成的 function circleEdgeClicked(x, y) { var colorLayer = context.getImageData(0, 0, canvasWidth, canvasHeight); var pixelPos = (x * canvasWidth +

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

因为我不想用洪水来填充圆的轮廓,所以我做了一个检查,确保如果单击了边,那么它不会被填充。这是通过检查颜色是否为红色来完成的

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错误地表示单击的像素为红色

下面是a中的代码,只需点击圆圈即可复制错误,几次尝试后,错误将不会填充,日志将显示颜色为红色,即使它不是红色

在getImageData文档中有以下注释
由于与预乘alpha颜色值之间的转换具有损耗性质,刚使用putImageData()设置的像素可能会作为不同的值返回到等效的getImageData()。


这与此有关吗?是否有解决办法?

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

(x + canvasWidth * y) * 4
因为y位置将表示需要跳过多少行水平像素才能到达像素,这将是canvasWidth*y,然后是水平轴的+x

以上只是为了修复错误,但我的建议和问题是,为什么不直接使用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);
 }

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

(x + canvasWidth * y) * 4
因为y位置将表示需要跳过多少行水平像素才能到达像素,这将是canvasWidth*y,然后是水平轴的+x

以上只是为了修复错误,但我的建议和问题是,为什么不直接使用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);
 }
将此行更改为:

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

在这种情况下,您可能需要对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
 }
函数循环单击(x,y){
var color=newuint32array(context.getImageData(x,y,1,1.data.buffer)[0];
返回(颜色将此行更改为:

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

在这种情况下,您可能需要对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
 }
函数循环单击(x,y){
var color=newuint32array(context.getImageData(x,y,1,1.data.buffer)[0];

return(color)我不太清楚你在说什么,我正在用
var pixelPos=(x*canvasWidth+y)*4;
是的,但是为了将x,y坐标转换为pixelPos,你必须使用(x+canvasWidth*y)*4而不是(x*canvasWidth+y)*4。但是,你可以使用getImageData(x,y,1,1)为了得到精确的像素,而不是读取整个画布并找到像素。我不太确定你在说什么,我正在使用
var pixelPos=(x*canvasWidth+y)*4;
是,但是为了将x,y坐标转换为pixelPos,你必须使用(x+canvasWidth*y)*4而不是(x*canvasWidth+y)*4.但与此相反,您可以使用getImageData(x,y,1,1)获得精确的像素,而不是读取整个画布并找到像素。说得好,但我仍然建议使用。getImageData(x,y,1,1)提取单个像素。这比读取整个画布更简单、更清晰、占用内存更少。这太棒了!我的
circleEdgeClicked
函数执行大约需要7毫秒,而你的要快得多。只是出于兴趣,第一次执行函数需要3毫秒,然后每次后续调用都不到0.1毫秒。为什么第一次调用之后的所有调用都要快得多?@JaAnTr浏览器可能能够在第二次运行时对其进行优化-因为它涉及位移位和整数值,这些都是很好的优化候选者。读取/比较完整的32位值也比读取/比较3字节值快。@LukaKvavilashvili在大多数情况下,您是绝对正确的,但您可以n如果可能的话,在给定的场景中(这里似乎有),也可以使用它预先缓存位图,这样我们也可以利用它-我添加了一个关于这个的新部分(boost add)@K3N ABGR用于大端系统,RGBA用于小端系统。使用UINT32阵列应进行端测试,或使用UINT32阵列获得已知颜色并与该颜色进行比较,但我仍然建议使用。getImageData(x,y,1,1)提取单个像素。这比读取整个画布更简单、更清晰、占用内存更少。这太棒了!我的
circleEdgeClicked
函数执行大约需要7毫秒,而你的要快得多。只是出于兴趣,第一次执行函数需要3毫秒,然后每次后续调用都不到0.1毫秒。为什么第一次调用之后的所有调用都要快得多?@JaAnTr浏览器可能能够在第二次运行时对其进行优化-因为它涉及到位移位和整数值,这些都是很好的优化候选者。读取/比较完整的32位值比读取/比较3字节值更快。@LukaKvavilashvili