在 JS 中使用像素操作 (WebImage) 填充未知的非对称多边形

Fill an Unknown Asymmetric Polygon with Pixel Manipulation (WebImage) in JS

最近,我一直在尝试创建代码来用颜色填充任何形状的多边形。我已经能够正确地填充只有一个边框大小的线条的形状,尽管我发现自己无法做更多的事情。问题在于代码不知道何时考虑一行大于它预期的像素的行作为形状的垂直或水平边界。我从左到右遍历形状的每个像素,并通过检查 alpha 值是否为 0 来检查是否有任何像素具有任何形式的颜色。一旦它发现一个像素的 alpha 值不为 0,它就会向前移动一个像素,然后使用 even/odd 技术来确定该点是否在多边形的一部分内(它使一个向右无限线并确定与彩色线的碰撞次数是否为奇数,如果是,则该点在多边形内)。通常,我们将单个单独的像素视为一条线,并且我们将超过一个像素的水平线视为两条线,因为水平线是否经常是边框的一部分。假设以下场景:

这里,红点是我们开始测试的点(像素)。如果我们不认为中间的水平线是两个点(如红线和 x 所示),我们将只有两个交点,因此不会填充像素,尽管我们肯定会这样做想要填充那个像素。然而,如前所述,这会带来另一个不同场景的问题:

在这种情况下,如果我们将超过一个像素的水平线算作两条单独的线,我们最终不会填充任何边框比粗的区域预期的厚度。供大家参考,处理这个的函数如下:

//imgData is essentially a WebImage object (explained more below) and r, g, and b are the color values for the fill color
function fillWithColor(imgData, r, g, b) {
  //Boolean determining whether we should color the given pixel(s) or not
  var doColor = false;
  //Booleans determining whether the last pixel found in the entire image was colored
  var blackLast = false;
  //Booleans determining whether the last 1 or 2 pixels found after a given pixel were colored
  var foundBlackPrev, foundBlackPrev2 = false;
  //The number of colored pixels found
  var blackCount = 0;
      
  //Loop through the entire canvas
  for(var y = 0; y < imgData.height; y += IMG_SCALE) {
    for(var x = 0; x < imgData.width; x += IMG_SCALE) {
      //Test if given pixel is colored
      if(getAlpha(imgData, x, y) != 0) {
        //If the last pixel was black, begin coloring
        if(!blackLast) {
          blackLast = true;
          doColor = true;
        }
      } else {
        //If the current pixel is not colored, but the last one was, find all colored lines to the right
        if(blackLast){
          for(var i = x; i < imgData.width; i += IMG_SCALE) {
            //If the pixel is colored...
            if(getAlpha(imgData, i, y) != 0) {
              //If no colored pixel was found before, add to the count
              if(!foundBlackPrev){
                blackCount++;
                foundBlackPrev = true;
              } else {
                //Otherwise, at least 2 colored pixels have been found in a row
                foundBlackPrev2 = true;
              }
            } else { 
              //If two or more colored pixels were found in a row, add to the count
              if(foundBlackPrev2) {
                blackCount++;
              }
              //Reset the booleans
              foundBlackPrev2 = foundBlackPrev = false;
            }
          }
        }
            
        //If the count is odd, we start coloring
        if(blackCount & 1) {
          blackCount = 0;
          doColor = true;
        } else {
          //If the last pixel in the entire image was black, we stop coloring
          if(blackLast) {
            doColor = false;
          }
        }
          
        //Reset the boolean
        blackLast = false;
        //If we are to be coloring the pixel, color it
        if(doColor) {
          //Color the pixel
          for(var j = 0; j < IMG_SCALE; j++) {
            for(var k = 0; k < IMG_SCALE; k++) {
              //This is the same as calling setRed, setGreen, setBlue and setAlpha functions from the WebImage API all at once (parameters in order are WebImage object equivalent, x position of pixel, y position of pixel, red value, green value, blue value, and alpha value)
              setRGB(imgData, x + j, y + k, r, g, b, 255);
            }
          }
        }
      }
    }
  }
  //Update the image (essentially the same as removing all elements from the given area and calling add on the image)
  clearCanvas();
  putImageData(imgData, 0, 0, imgData.width, imgData.height);
  //Return the modified data
  return imgData;
}

在哪里...

imgData 是给定区域中所有像素的集合(本质上是一个 WebImage 对象)

IMG_SCALE 是图像按比例放大的整数值(这也给了我们像素的比例)。在这个例子中,它等于 4,因为图像被放大到 192x256(从 48x64)。这意味着您在图像中看到的每个“像素”实际上都由 4x4 块相同颜色的像素组成。

所以,我在这里真正要寻找的是一种方法来确定一个给定的彩色像素是水平边界的一部分,还是只是另一个包含垂直边界厚度的部分。此外,如果我总体上对这个问题有错误的方法,我将不胜感激任何关于如何更有效地做到这一点的建议。谢谢。

据我了解,您不能“将超过一个像素的水平线视为两条线”。我认为您不需要像现在这样计算黑色像素,而是计算 1 个或多个像素的组。

我还会通过避免使用“doColor”布尔变量来整理代码。您可以将着色代码移至新函数 color(x,y) 并立即调用它。

const ctx = document.querySelector("canvas").getContext("2d");

//ctx.lineWidth(10);//-as you asked we are setting greater border or line width,BUT "LINEWIDTH" IS NOT WORKING IN INBUILT Whosebug SNIPPET USE IT IN A FILE I THINK Whosebug IS NOT UP-TO-DATE,IN ANY IDE UNCOMENT THIS

ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(250, 70);
ctx.lineTo(270, 120);
ctx.lineTo(170, 140);
ctx.lineTo(190, 80);
ctx.lineTo(100, 60);
ctx.lineTo(50, 130);
ctx.lineTo(20, 20);
ctx.stroke();

function getMousePosition(canvas, event) {
  let rect = canvas.getBoundingClientRect();
  let mx = event.clientX - rect.left;
  let my = event.clientY - rect.top;
  console.log("Coordinate x: " + mx, "Coordinate y: " + my);
  floodFill(ctx, mx, my, [155, 0, 255, 255], 128);
}

let canvasElem = document.querySelector("canvas");

canvasElem.addEventListener("mousedown", function(e) {
  getMousePosition(canvasElem, e);
});



function getPixel(imageData, x, y) {
  if (x < 0 || y < 0 || x >= imageData.width || y >= imageData.height) {
    return [-1, -1, -1, -1]; // impossible color
  } else {
    const offset = (y * imageData.width + x) * 4;
    return imageData.data.slice(offset, offset + 4);
  }
}

function setPixel(imageData, x, y, color) {
  const offset = (y * imageData.width + x) * 4;
  imageData.data[offset + 0] = color[0];
  imageData.data[offset + 1] = color[1];
  imageData.data[offset + 2] = color[2];
  imageData.data[offset + 3] = color[0];
}

function colorsMatch(a, b, rangeSq) {
  const dr = a[0] - b[0];
  const dg = a[1] - b[1];
  const db = a[2] - b[2];
  const da = a[3] - b[3];
  return dr * dr + dg * dg + db * db + da * da < rangeSq;
}

function floodFill(ctx, x, y, fillColor, range = 1) {
  // read the pixels in the canvas
  const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);

  // flags for if we visited a pixel already
  const visited = new Uint8Array(imageData.width, imageData.height);

  // get the color we're filling
  const targetColor = getPixel(imageData, x, y);

  // check we are actually filling a different color
  if (!colorsMatch(targetColor, fillColor)) {

    const rangeSq = range * range;
    const pixelsToCheck = [x, y];
    while (pixelsToCheck.length > 0) {
      const y = pixelsToCheck.pop();
      const x = pixelsToCheck.pop();

      const currentColor = getPixel(imageData, x, y);
      if (!visited[y * imageData.width + x] &&
        colorsMatch(currentColor, targetColor, rangeSq)) {
        setPixel(imageData, x, y, fillColor);
        visited[y * imageData.width + x] = 1; // mark we were here already
        pixelsToCheck.push(x + 1, y);
        pixelsToCheck.push(x - 1, y);
        pixelsToCheck.push(x, y + 1);
        pixelsToCheck.push(x, y - 1);
      }
    }

    // put the data back
    ctx.putImageData(imageData, 0, 0);
  }
}
<canvas></canvas>

这是基于其他答案 注意:“LINEWIDTH”在内置的 Whosebug 片段中不起作用在我认为 Whosebug 不是最新的文件中使用它,

但在简单的HTML,JS网站

中效果很好

我理解这个问题,我认为如果你在这里改变你的策略,你会做得更好。我们知道以下内容:

  • 起点在形状内部
  • 应该为形状内的每个像素填充颜色

所以,我们总是可以将当前点的邻居推入队列中进行处理,注意避免对同一个点进行两次处理,这样就遍历了所有有用的像素,并将其纳入着色计划。以下功能未经测试。

function fillColor(pattern, startingPoint, color, boundaryColor) {
    let visitQueue = [];
    let output = {};
    if (startingPoint.x - 1 >= 0) visitQueue.push({startingPoint.x - 1, startingPoint.y});
    if (startingPoint.x + 1 < pattern.width) visitQueue.push({startingPoint.x + 1, startingPoint.y});
    if (startingPoint.y + 1 < pattern.height) visitQueue.push({startingPoint.x, startingPoint.y + 1});
    if (startingPoint.y - 1 >= 0) visitQueue.push({startingPoint.x, startingPoint.y - 1});
    let visited = {};
    while (visitQueue.length > 0) {
        let point = visitQueue[0];
        visitQueue.shift();
        if ((!visited[point.x]) || (visited[point.x].indexOf(point.y) < 0)) {
            if (!visited[point.x]) visited[point.x] = [];
            visited[point.x].push(point.y);
            if (isBlank(pattern, point)) { //you need to implement isBlank
                if (!output[point.x]) output[point.x] = [];
                output[point.x].push(point.y);
                if (point.x + 1 < pattern.width) visitQueue.push({point.x + 1, point.y});
                if (point.x - 1 >= 0) visitQueue.push({point.x - 1, point.y});
                if (point.y + 1 < pattern.height) visitQueue.push({point.x, point.y + 1});
                if (point.y - 1 >= 0) visitQueue.push({point.x, point.y - 1})
            }
        }
    }
    return output;
}