已解决:从线性阵列中获取相邻像素(如 context.getImageData)

SOLVED: get neighboring pixels from linear array (as in context.getImageData)

我有一张中间有红色实心圆圈的图片。我想读取该图像,分析像素,然后将其写回 canvas,仅包含圆的轮廓。所以我正在检查像素是否被红色通道值为 255 的像素包围。如果是,我将其设置为透明。如果不是,我将像素涂成青色。

我使用了,作为编写脚本的参考。

我做错了什么。不是用青色勾勒圆圈,并使中心透明,而是将所有白色像素着色为青色。

非常感谢您的帮助!

代码:

<img alt="input" height="170" id="input" src="img/input.png" width="170">
<canvas height="170" id="myCanvas" style="border:1px solid #d3d3d3;" width="170"></canvas>
let neighbors = []

  function isSurrounded(index, imageData, data) {
    neighbors = [] // clear array
    neighbors[0] = data[index - imageData.width * 4 - 4] // Upper left
    neighbors[1] = data[index - imageData.width * 4]     // Upper middle
    neighbors[2] = data[index - imageData.width * 4 + 4] // Upper right
    neighbors[3] = data[index - 4] // left
    neighbors[4] = data[index + 4] // right
    neighbors[5] = data[index + imageData.width * 4 - 4] // Lower left
    neighbors[6] = data[index + imageData.width * 4]     // lower middle
    neighbors[7] = data[index + imageData.width * 4 + 4] // Lower right

    // check red channel
    for (let i = 0; i < neighbors.length; i++) {
      let redPixel = neighbors[i]
      if (redPixel !== 255) {
        return false
      }
    }
    return true
  }

  let img = document.getElementById("input")
  img.onload = function () {
    let c = document.getElementById("myCanvas")
    let ctx = c.getContext("2d")
    ctx.drawImage(img, 0, 0)
    let imgData = ctx.getImageData(0, 0, c.width, c.height)
    let pxl = imgData.data
    for (let i = 0; i < pxl.length; i += 4) {
      if (isSurrounded(i, imgData, pxl)) {
        pxl[i] = 0
        pxl[i + 1] = 255
        pxl[i + 2] = 255
        pxl[i + 3] = 255
      } else {
        pxl[i + 3] = 0
      }
    }
    ctx.putImageData(imgData, 0, 0)
  }

也许不同的方法会奏效。我没有为此使用实际图像,以免处理安全问题,但该过程应该仍然有效。

在处理 img.data 时,我喜欢做的第一件事是创建一个数组,为每个像素提供自己的数组;一个二维数组。即 [[0, 0, 0, 0,], [255, 0, 255, 0]...]。对我个人而言,它更容易处理。

  let imgData = ctx.getImageData(0, 0, c.width, c.height);
  let data = imgData.data.reduce(
    (pixel, key, index) =>
      (index % 4 == 0
        ? pixel.push([key])
        : pixel[pixel.length - 1].push(key)) && pixel,
    []
  );

无论您如何处理 data 数组(1d 或 2d),您都需要检查左-右-上-下像素的透明度是否为 0。现在如果图像您使用的没有透明度,那么您需要更正它。 Canvas 自动透明,直到你在上面着色。

for (let i = 0; i < data.length; i++) {
    if (data[i][0] >= 250 && data[i + 1][3] === 0) {
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
}

虽然我用纯红色为我的圆圈着色,但在靠近边缘的情况下 r 值不会是 255,但可能会略低。我还想看看右边的像素是否透明。如果是这样,则意味着它必须在圆圈的边缘。对所有面都这样做,如果为真,我将像素设置为蓝色。

一旦完成,我就可以遍历数组并将所有剩余的红色像素更改为透明。

data.forEach((px) => {
    if (px[0] >= 250) {
      px[0] = 0;
      px[1] = 0;
      px[2] = 0;
      px[3] = 0;
    }
  });

现在我们需要创建一个新的图像数据,并在绘制之前将我们的新数组设置为它的数据。我在这里使用 flat() 因为我的数据数组是 2d 而我们需要它是 1d。

  let newImage = ctx.createImageData(170, 170);
  newImage.data.set(data.flat());
  ctx.putImageData(newImage, 0, 0);

现在你应该有一个轮廓圆

let c = document.getElementById("myCanvas");
let ctx = c.getContext("2d");

function drawCircle() {
  ctx.fillStyle = "red";
  ctx.arc(c.width / 2, c.height / 2, 70, 0, Math.PI * 2);
  ctx.fill();
}
drawCircle();

window.onload = function () {
  let imgData = ctx.getImageData(0, 0, c.width, c.height);
  let data = imgData.data.reduce(
    (pixel, key, index) =>
      (index % 4 == 0
        ? pixel.push([key])
        : pixel[pixel.length - 1].push(key)) && pixel,
    []
  );
  for (let i = 0; i < data.length; i++) {
    if (data[i][0] >= 250 && data[i + 1][3] === 0) {
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    }
    if (data[i][0] >= 250 && data[i - 1][3] === 0) {
      //  console.log(data[i-1])
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    }
    if (data[i][0] >= 250 && data[i - 170][3] === 0) {
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    }
    if (data[i][0] >= 250 && data[i + 170][3] === 0) {
      data[i][0] = 0;
      data[i][1] = 0;
      data[i][2] = 255;
      data[i][3] = 255;
    }
  }
  data.forEach((px) => {
    if (px[0] >= 250) {
      px[0] = 0;
      px[1] = 0;
      px[2] = 0;
      px[3] = 0;
    }
  });
  let newImage = ctx.createImageData(170, 170);
  newImage.data.set(data.flat());
  ctx.putImageData(newImage, 0, 0);
};
<canvas height="170" id="myCanvas" style="border:1px solid #d3d3d3;" width="170"></canvas>