You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas:如何修改自定义路径范围内的ImageData像素值

问题原因
  • 像素坐标计算错误:imageData.data数组的索引i对应的是RGBA通道的位置,每4个连续索引对应1个像素的完整信息。原代码直接用通道索引i计算坐标,没有先转换为像素索引,导致坐标完全错位,出现多区域同时被修改的问题。
  • 全局imageData持续复用:仅在图片加载时获取了一次原始画布的像素数据,之后所有修改都会永久保存在这个变量里,光标移动过的区域都会被持续修改,无法恢复原图状态。
  • 背景绘制逻辑缺陷:原缩放逻辑会导致图片拉伸变形甚至超出画布范围,也会间接影响像素坐标匹配。
修复方案
  1. 修正坐标计算逻辑:先将通道索引转换为像素索引,再计算对应x/y坐标
  2. 保存原始像素数据副本,每次修改都基于原始数据操作,避免修改痕迹累加;如果需要保留光标划过的修改痕迹,可去掉副本逻辑直接修改全局像素数据
  3. 优化遍历逻辑:仅遍历光标覆盖的正方形区域,不需要遍历全画布,大幅提升性能,同时去掉性能较低的isPointInPath判断,直接用距离公式判断点是否在圆形光标内
  4. 修正背景图片缩放逻辑,按比例居中绘制避免变形

修复后完整代码

JavaScript

let image;
// 保存原始像素数据,避免修改累加
let originalImageData;

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");
// 光标半径
const cursorRadius = cursorCanvas.width / 2;

document.querySelector("img").addEventListener("load", (event) => {
  image = event.target;
  image.crossOrigin = "Anonymous";
  drawBackground();
  // 保存原始像素数据
  originalImageData = imageCanvasContext.getImageData(
    0,
    0,
    imageCanvas.width,
    imageCanvas.height
  );
});

window.addEventListener("mousemove", (event) => {
  requestAnimationFrame(() => {
    if (originalImageData) {
      // 复制原始数据,在副本上修改,不需要保留划过痕迹可去掉此逻辑直接修改全局数据
      const currentImageData = new ImageData(
        new Uint8ClampedArray(originalImageData.data),
        originalImageData.width,
        originalImageData.height
      );
      const { clientX: cx, clientY: cy } = event;

      // 仅遍历光标覆盖的正方形区域,不需要遍历全画布
      const startX = Math.max(0, Math.floor(cx - cursorRadius));
      const endX = Math.min(imageCanvas.width, Math.ceil(cx + cursorRadius));
      const startY = Math.max(0, Math.floor(cy - cursorRadius));
      const endY = Math.min(imageCanvas.height, Math.ceil(cy + cursorRadius));

      for (let y = startY; y < endY; y++) {
        for (let x = startX; x < endX; x++) {
          // 判断当前点是否在光标圆形范围内
          const dx = x - cx;
          const dy = y - cy;
          if (dx * dx + dy * dy <= cursorRadius * cursorRadius) {
            const index = (y * imageCanvas.width + x) * 4;
            currentImageData.data[index] = 40;
            currentImageData.data[index + 1] = 40;
            currentImageData.data[index + 2] = 40;
          }
        }
      }
      imageCanvasContext.putImageData(currentImageData, 0, 0);
    }
    // 绘制自定义光标
    imageCanvasContext.drawImage(
      cursorCanvas,
      event.clientX - cursorRadius,
      event.clientY - cursorRadius
    );
  });
});

function drawBackground() {
  imageCanvasContext.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
  if (image) {
    // 修正原图缩放逻辑,按最小比例适配画布,居中绘制不变形
    const ratio = Math.min(
      imageCanvas.width / image.width,
      imageCanvas.height / image.height
    );
    const drawWidth = image.width * ratio;
    const drawHeight = image.height * ratio;
    const offsetX = (imageCanvas.width - drawWidth) / 2;
    const offsetY = (imageCanvas.height - drawHeight) / 2;
    imageCanvasContext.drawImage(
      image,
      0,
      0,
      image.width,
      image.height,
      offsetX,
      offsetY,
      drawWidth,
      drawHeight
    );
  }
}

function drawCursor() {
  cursorCanvasContext.beginPath();
  cursorCanvasContext.arc(
    cursorCanvas.width / 2,
    cursorCanvas.width / 2,
    cursorCanvas.width / 2,
    0,
    2 * Math.PI
  );
  cursorCanvasContext.stroke();
  cursorCanvasContext.closePath();
}

CSS

html,
body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

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

img {
  visibility: hidden;
  position: absolute;
}

canvas {
  cursor: none;
}

HTML

<img src="https://i.imgur.com/vzGhITt.jpeg" />
<canvas />

内容的提问来源于stack exchange,提问作者Raphael Rafatpanah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 22:36:03