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

Time:12-07

I have an image with a red filled-in circle in the middle. I want to read that image, analyze the pixels, and write it back to the canvas with just the outline of the circle. So I am checking to see if the pixel is surrounded by pixels with red channel value of 255. If yes, I make transparent. If no, I color the pixel cyan.

I used this post, as reference for writing the script.

I'm doing something wrong. Instead of outlining the circle in cyan, and making the center transparent, it is coloring all the white pixels cyan.

I'd really appreciate some help!

Code:

<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)
  }

CodePudding user response:

Maybe a different approach will work. I'm not using an actual image for this to as to not deal with security issues but the process should still work.

First thing I like to do when dealing with img.data is I like to create an array that gives each pixel it's own array; a 2d array. i.e [[0, 0, 0, 0,], [255, 0, 255, 0]...]. For me personally it makes it easier to deal with.

  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,
    []
  );

No matter how you handle you data array (1d or 2d) you will want to check to see if the pixel to the left-right-top-bottom has transparency of 0. Now if the image you use doesn't have transparency then you'll need to correct that. Canvas is automatically transparent until you color on it.

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;
}

Although I colored my circle with pure red there are cases near the edge where the r value won't be 255 but may be slightly lower. I am also looking to see if the pixel to the right is transparent. If so then that means it must be at the edge of the circle. Do this for all sides and if true I set the pixels blue.

Once that's done I can then go through the array and change all left over red pixels to transparent.

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

Now we need to create an new image data and set our new array as the data for it before we draw it. I use flat() here because my data array was 2d and we need it to be 1d for this.

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

Now you should have an outlined circle

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>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related