Canvas: 如何修改自定义路径中存在的ImageData 像素?

Canvas: How to modify ImageData pixels that exist within a custom path?

我正在尝试创造一种体验,让用户可以在图像上移动自定义光标并更改自定义光标内的像素。

mousemove 事件中,我尝试重新创建此自定义光标路径并使用 isPointInPath 确定自定义光标是否“超过”主 canvas 中的给定点的上下文。

代码确实改变了主 canvas 中的一些像素。但是,下面列出了以下意外行为:

(见下图)

为什么会这样?如何更改代码,使自定义光标(又名圆圈)仅更改与主要部分对应的像素 canvas?

let image;
let imageData;

const cursorCanvas = document.createElement("canvas");
const cursorCanvasContext = cursorCanvas.getContext("2d");
cursorCanvas.width = 100;
cursorCanvas.height = 100;
drawCursor();

const imageCanvas = document.querySelector("canvas");
imageCanvas.width = window.innerWidth;
imageCanvas.height = window.innerHeight;
const imageCanvasContext = imageCanvas.getContext("2d");

document.querySelector("img").addEventListener("load", (event) => {
  image = event.target;
  image.crossOrigin = "Anonymous";
  drawBackground();
  imageData = imageCanvasContext.getImageData(
    0,
    0,
    imageCanvas.width,
    imageCanvas.height
  );
});

window.addEventListener("mousemove", (event) => {
  requestAnimationFrame(() => {
    if (imageData) {
      imageCanvasContext.beginPath();
      imageCanvasContext.arc(
        event.clientX,
        event.clientY,
        cursorCanvas.width / 2,
        0,
        2 * Math.PI
      );
      imageCanvasContext.closePath();

      for (let i = 0; i < imageData.data.length; i += 4) {
        const y = Math.floor(i / imageData.width);
        const x = i - y * imageData.width;

        if (imageCanvasContext.isPointInPath(x, y)) {
          imageData.data[i] = 40;
          imageData.data[i + 1] = 40;
          imageData.data[i + 2] = 40;
        }
      }
      imageCanvasContext.putImageData(imageData, 0, 0);
    }

    imageCanvasContext.drawImage(
      cursorCanvas,
      event.clientX - cursorCanvas.width / 2,
      event.clientY - cursorCanvas.height / 2
    );
  });
});

function drawBackground() {
  imageCanvasContext.clearRect(
    0,
    0,
    imageCanvas.width,
    imageCanvas.height
  );

  if (image) {
    const ratio =
      window.innerWidth > window.innerHeight ?
      window.innerWidth / window.innerHeight :
      window.innerHeight / window.innerWidth;

    imageCanvasContext.drawImage(
      image,
      0,
      0,
      image.width,
      image.height,
      0,
      0,
      ratio * imageCanvas.width,
      ratio * imageCanvas.height
    );
  }
}

function drawCursor() {
  cursorCanvasContext.beginPath();
  cursorCanvasContext.arc(
    cursorCanvas.width / 2,
    cursorCanvas.width / 2,
    cursorCanvas.width / 2,
    0,
    2 * Math.PI
  );
  cursorCanvasContext.stroke();
  cursorCanvasContext.closePath();
}
html,
body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

body {
  font-family: sans-serif;
  margin: 0;
}

img {
  visibility: hidden;
  position: absolute;
}

canvas {
  cursor: none;
}
<img src="https://i.imgur.com/vzGhITt.jpeg" />
<canvas />

啊,好像我计算错了imageData的x和y坐标

不正确:

const y = Math.floor(i / imageData.width);
const x = i - y * imageData.width;

正确:

const x = (i / 4) % imageData.width;
const y = Math.floor(i / 4 / imageData.width);

let image;
let imageData;

const cursorCanvas = document.createElement("canvas");
const cursorCanvasContext = cursorCanvas.getContext("2d");
cursorCanvas.width = 100;
cursorCanvas.height = 100;
drawCursor();

const imageCanvas = document.querySelector("canvas");
imageCanvas.width = window.innerWidth;
imageCanvas.height = window.innerHeight;
const imageCanvasContext = imageCanvas.getContext("2d");

document.querySelector("img").addEventListener("load", (event) => {
  image = event.target;
  image.crossOrigin = "Anonymous";
  drawBackground();
  imageData = imageCanvasContext.getImageData(
    0,
    0,
    imageCanvas.width,
    imageCanvas.height
  );
});

window.addEventListener("mousemove", (event) => {
    if (imageData) {
      imageCanvasContext.beginPath();
      imageCanvasContext.arc(
        event.clientX,
        event.clientY,
        cursorCanvas.width / 2,
        0,
        2 * Math.PI
      );
      imageCanvasContext.closePath();

      for (let i = 0; i < imageData.data.length; i += 4) {
        const x = (i / 4) % imageData.width;
        const y = Math.floor(i / 4 / imageData.width);

        if (imageCanvasContext.isPointInPath(x, y)) {
          imageData.data[i] = 40;
          imageData.data[i + 1] = 40;
          imageData.data[i + 2] = 40;
        }
      }
      imageCanvasContext.putImageData(imageData, 0, 0);
    }

    imageCanvasContext.drawImage(
      cursorCanvas,
      event.clientX - cursorCanvas.width / 2,
      event.clientY - cursorCanvas.height / 2
    );
});

function drawBackground() {
  imageCanvasContext.clearRect(
    0,
    0,
    imageCanvas.width,
    imageCanvas.height
  );

  if (image) {
    const ratio =
      window.innerWidth > window.innerHeight ?
      window.innerWidth / window.innerHeight :
      window.innerHeight / window.innerWidth;

    imageCanvasContext.drawImage(
      image,
      0,
      0,
      image.width,
      image.height,
      0,
      0,
      ratio * imageCanvas.width,
      ratio * imageCanvas.height
    );
  }
}

function drawCursor() {
  cursorCanvasContext.beginPath();
  cursorCanvasContext.arc(
    cursorCanvas.width / 2,
    cursorCanvas.width / 2,
    cursorCanvas.width / 2,
    0,
    2 * Math.PI
  );
  cursorCanvasContext.stroke();
  cursorCanvasContext.closePath();
}
html,
body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

body {
  font-family: sans-serif;
  margin: 0;
}

img {
  visibility: hidden;
  position: absolute;
}

canvas {
  cursor: none;
}
<img src="https://i.imgur.com/vzGhITt.jpeg" />
<canvas />