Canvas 图片绘制与像素操作:drawImage、getImageData 到简单滤镜
图形和文本之外,图片是 Canvas 里最常见的第三种元素:图标、背景、贴图、截图,都离不开
drawImage。
再往下一层,还可以通过像素操作实现灰度、反色等简单滤镜。
这一篇围绕这两个方向展开:如何灵活使用drawImage绘制和裁剪图片,以及如何用getImageData/putImageData做基础的像素级处理。
1. drawImage:从简单绘制到九宫格裁剪
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 控制清晰度与锯齿表现。
2. 图片与坐标系、变换配合使用
drawImage 与 translate / 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) 为中心旋转/缩放图片,而无需手动计算每个角的坐标。
3. 像素操作基础:getImageData 与 putImageData
要对图片或画布内容做更细粒度的处理,可以使用:
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 等方式可以减轻渲染线程压力。
4. 简单滤镜示例:灰度与反色
4.1 灰度处理
灰度化的基本思路是,把 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}
这样可以实现简单的黑白效果,适用于预处理或预览。
4.2 反色处理
反色则是对 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 中可以作为一个简单的「高亮」或样式切换效果。
5. 性能与边界注意事项
在使用像素操作和图片绘制时,工程上需要注意几个点:
- 跨域图片
- 如果从不同源加载图片,Canvas 默认会被「污染」,之后的
getImageData会抛出安全错误; - 可以通过服务器配置 CORS 或在加载时设置
crossOrigin来解决,但需要服务端支持。
- 如果从不同源加载图片,Canvas 默认会被「污染」,之后的
- 绘制区域大小
- 对小区域做像素处理通常没问题;
- 对整幅大图或频繁处理的区域,需要评估 CPU 开销,必要时分块处理或降低频率。
- 避免在主动画循环中做重型像素计算
- 如果必须在动画中应用滤镜,考虑提前预处理多份图像或降低分辨率;
- 对于连续变换的滤镜,可以考虑使用 WebGL 或相关库。
6. 小结:把图片看成「可以被重组的像素源」
这一篇可以压缩为几条关键点:
drawImage提供了灵活的绘制与裁剪能力,可以从单一图片资源中构造多种 UI 元素;getImageData/putImageData让可以在像素层面对画布内容做自由处理,但需要对性能保持敏感;- 简单的灰度、反色等滤镜可以作为理解像素操作的入口,为后续更复杂的图像处理打基础。
理解了这一层之后,Canvas 不再只是「画矢量图形和文字」,而是可以接住各种图像资源,并在必要时直接动手改每一个像素。