Canvas 图片绘制与像素操作:drawImage、getImageData 到简单滤镜

图形和文本之外,图片是 Canvas 里最常见的第三种元素:图标、背景、贴图、截图,都离不开 drawImage
再往下一层,还可以通过像素操作实现灰度、反色等简单滤镜。
这一篇围绕这两个方向展开:如何灵活使用 drawImage 绘制和裁剪图片,以及如何用 getImageData / putImageData 做基础的像素级处理。

drawImage 是 Canvas 中用于绘制图片的核心方法,它有三种常见调用形式:

  • drawImage(image, dx, dy)
  • drawImage(image, dx, dy, dWidth, dHeight)
  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

其中:

  • image:可以是 <img><canvas>ImageBitmap 等;
  • sx, sy, sWidth, sHeight:源图像中的裁剪区域;
  • dx, dy, dWidth, dHeight:在目标画布上的位置和尺寸。

常见用法包括:

  • 绘制整张图片:只用前两种形式;
  • 从雪碧图中裁剪单个图块:使用第三种形式指定源区域;
  • 实现九宫格拉伸或复杂 UI 元素:多次从源图中裁剪不同区域绘制到目标位置。

需要注意:

  • 图片需要在 onload 后再绘制,或者使用 ImageBitmap/资源预加载机制,避免绘制时图像尚未准备好;
  • 对缩放后的图片,可以通过插值设置与 imageSmoothing 控制清晰度与锯齿表现。

drawImagetranslate / rotate / scale 等变换可以组合使用:

  • 通过先平移、旋转坐标系,再调用 drawImage,实现图片的旋转与缩放;
  • 适合做旋转图标、缩放预览、动画效果等。

典型结构:

1ctx.save();
2ctx.translate(cx, cy);
3ctx.rotate(angle);
4ctx.drawImage(image, -w / 2, -h / 2, w, h);
5ctx.restore();

这样可以以 (cx, cy) 为中心旋转/缩放图片,而无需手动计算每个角的坐标。

要对图片或画布内容做更细粒度的处理,可以使用:

  • getImageData(sx, sy, sw, sh):获取指定区域的像素数据;
  • putImageData(imageData, dx, dy):把像素数据写回画布。

getImageData 返回的是一个 ImageData 对象,其中:

  • width / height:区域尺寸;
  • data:一个 Uint8ClampedArray,按 RGBA 顺序存储像素值,每个像素 4 个字节。

遍历像素时常见模式是:

 1const imageData = ctx.getImageData(x, y, w, h);
 2const data = imageData.data;
 3
 4for (let i = 0; i < data.length; i += 4) {
 5  const r = data[i];
 6  const g = data[i + 1];
 7  const b = data[i + 2];
 8  const a = data[i + 3];
 9
10  // 更新 data[i..i+3]
11}
12
13ctx.putImageData(imageData, x, y);

需要注意:

  • 像素操作一般是 CPU 密集型任务,对大区域频繁处理会影响性能;
  • 如果处理整幅大图,适当使用 Web Worker 等方式可以减轻渲染线程压力。

灰度化的基本思路是,把 RGB 转成同一个亮度值:

1for (let i = 0; i < data.length; i += 4) {
2  const r = data[i];
3  const g = data[i + 1];
4  const b = data[i + 2];
5  const gray = 0.299 * r + 0.587 * g + 0.114 * b;
6  data[i] = data[i + 1] = data[i + 2] = gray;
7}

这样可以实现简单的黑白效果,适用于预处理或预览。

反色则是对 RGB 分量做 255 - value

1for (let i = 0; i < data.length; i += 4) {
2  data[i] = 255 - data[i];       // R
3  data[i + 1] = 255 - data[i+1]; // G
4  data[i + 2] = 255 - data[i+2]; // B
5}

在 UI 中可以作为一个简单的「高亮」或样式切换效果。

在使用像素操作和图片绘制时,工程上需要注意几个点:

  • 跨域图片
    • 如果从不同源加载图片,Canvas 默认会被「污染」,之后的 getImageData 会抛出安全错误;
    • 可以通过服务器配置 CORS 或在加载时设置 crossOrigin 来解决,但需要服务端支持。
  • 绘制区域大小
    • 对小区域做像素处理通常没问题;
    • 对整幅大图或频繁处理的区域,需要评估 CPU 开销,必要时分块处理或降低频率。
  • 避免在主动画循环中做重型像素计算
    • 如果必须在动画中应用滤镜,考虑提前预处理多份图像或降低分辨率;
    • 对于连续变换的滤镜,可以考虑使用 WebGL 或相关库。

这一篇可以压缩为几条关键点:

  • drawImage 提供了灵活的绘制与裁剪能力,可以从单一图片资源中构造多种 UI 元素;
  • getImageData / putImageData 让可以在像素层面对画布内容做自由处理,但需要对性能保持敏感;
  • 简单的灰度、反色等滤镜可以作为理解像素操作的入口,为后续更复杂的图像处理打基础。

理解了这一层之后,Canvas 不再只是「画矢量图形和文字」,而是可以接住各种图像资源,并在必要时直接动手改每一个像素。