Canvas 图像处理
本文档详细介绍 Canvas 的图像处理功能,包括图像绘制、裁剪、缩放、像素操作和滤镜效果。
图像绘制
Canvas 提供了 drawImage() 方法来绘制图像、视频或其他 Canvas。
方法签名
javascript
// 基本绘制
ctx.drawImage(image, dx, dy)
// 缩放绘制
ctx.drawImage(image, dx, dy, dWidth, dHeight)
// 裁剪绘制
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
image | HTMLImageElement/HTMLCanvasElement/HTMLVideoElement | 绘制源 |
sx | Number | 源图像裁剪起点 X |
sy | Number | 源图像裁剪起点 Y |
sWidth | Number | 源图像裁剪宽度 |
sHeight | Number | 源图像裁剪高度 |
dx | Number | 目标位置 X |
dy | Number | 目标位置 Y |
dWidth | Number | 目标宽度 |
dHeight | Number | 目标高度 |
基本绘制
javascript
const img = new Image()
img.src = 'image.jpg'
img.onload = function() {
// 在指定位置绘制原图
ctx.drawImage(img, 50, 50)
}缩放绘制
javascript
img.onload = function() {
// 绘制到指定尺寸
ctx.drawImage(img, 50, 50, 300, 200)
}裁剪绘制
javascript
img.onload = function() {
// 从源图像 (100, 100) 位置裁剪 200x150 区域
// 绘制到 Canvas 的 (50, 50) 位置,尺寸为 300x225
ctx.drawImage(
img,
100, 100, 200, 150, // 源图像裁剪区域
50, 50, 300, 225 // 目标位置和尺寸
)
}绘制 Canvas
javascript
const sourceCanvas = document.getElementById('sourceCanvas')
const targetCanvas = document.getElementById('targetCanvas')
const ctx = targetCanvas.getContext('2d')
// 将一个 Canvas 绘制到另一个 Canvas
ctx.drawImage(sourceCanvas, 0, 0)绘制视频帧
html
<video id="video" src="video.mp4" style="display:none"></video>
<canvas id="canvas"></canvas>
<script>
const video = document.getElementById('video')
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
video.addEventListener('play', function() {
function drawFrame() {
if (video.paused || video.ended) return
ctx.drawImage(video, 0, 0)
requestAnimationFrame(drawFrame)
}
drawFrame()
})
</script>图像裁剪与缩放
图像缩放
javascript
function drawScaled(img, scale) {
const width = img.width * scale
const height = img.height * scale
ctx.drawImage(img, 0, 0, width, height)
}
// 放大 2 倍
drawScaled(img, 2)
// 缩小到 50%
drawScaled(img, 0.5)保持比例缩放
javascript
function drawFitToContainer(img, containerWidth, containerHeight) {
const imgRatio = img.width / img.height
const containerRatio = containerWidth / containerHeight
let drawWidth, drawHeight, offsetX, offsetY
if (imgRatio > containerRatio) {
// 图像更宽,以宽度为准
drawWidth = containerWidth
drawHeight = containerWidth / imgRatio
offsetX = 0
offsetY = (containerHeight - drawHeight) / 2
} else {
// 图像更高,以高度为准
drawHeight = containerHeight
drawWidth = containerHeight * imgRatio
offsetX = (containerWidth - drawWidth) / 2
offsetY = 0
}
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight)
}图像裁剪
javascript
// 裁剪圆形头像
function cropCircle(img, x, y, radius) {
ctx.save()
ctx.beginPath()
ctx.arc(x + radius, y + radius, radius, 0, Math.PI * 2)
ctx.clip()
ctx.drawImage(img, x, y, radius * 2, radius * 2)
ctx.restore()
}
cropCircle(img, 50, 50, 60)图像翻转
javascript
// 水平翻转
function flipHorizontal(img, x, y) {
ctx.save()
ctx.scale(-1, 1)
ctx.drawImage(img, -x - img.width, y)
ctx.restore()
}
// 垂直翻转
function flipVertical(img, x, y) {
ctx.save()
ctx.scale(1, -1)
ctx.drawImage(img, x, -y - img.height)
ctx.restore()
}像素操作
Canvas 提供了三个方法来操作像素数据。
方法概览
| 方法 | 说明 |
|---|---|
createImageData(width, height) | 创建空白 ImageData |
getImageData(x, y, width, height) | 获取指定区域的像素数据 |
putImageData(imageData, x, y) | 将像素数据绘制到 Canvas |
ImageData 对象
javascript
{
width: 100, // 宽度
height: 100, // 高度
data: Uint8ClampedArray // 像素数据数组
}像素数据格式: [r, g, b, a, r, g, b, a, ...](每个像素 4 个值,范围 0-255)
获取像素数据
javascript
const imageData = ctx.getImageData(0, 0, 100, 100)
const data = imageData.data
console.log('宽度:', imageData.width)
console.log('高度:', imageData.height)
console.log('总像素数:', data.length / 4)访问单个像素
javascript
function getPixel(imageData, x, y) {
const index = (y * imageData.width + x) * 4
return {
r: imageData.data[index],
g: imageData.data[index + 1],
b: imageData.data[index + 2],
a: imageData.data[index + 3]
}
}
function setPixel(imageData, x, y, r, g, b, a) {
const index = (y * imageData.width + x) * 4
imageData.data[index] = r
imageData.data[index + 1] = g
imageData.data[index + 2] = b
imageData.data[index + 3] = a
}遍历所有像素
javascript
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
const data = imageData.data
for (let i = 0; i < data.length; i += 4) {
const r = data[i]
const g = data[i + 1]
const b = data[i + 2]
const a = data[i + 3]
// 处理像素...
}
ctx.putImageData(imageData, 0, 0)图像滤镜
灰度滤镜
javascript
function grayscale(imageData) {
const data = imageData.data
for (let i = 0; i < data.length; i += 4) {
const r = data[i]
const g = data[i + 1]
const b = data[i + 2]
// 使用加权平均
const gray = r * 0.299 + g * 0.587 + b * 0.114
data[i] = gray
data[i + 1] = gray
data[i + 2] = gray
}
return imageData
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
ctx.putImageData(grayscale(imageData), 0, 0)反色滤镜
javascript
function invert(imageData) {
const data = imageData.data
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i] // R
data[i + 1] = 255 - data[i + 1] // G
data[i + 2] = 255 - data[i + 2] // B
}
return imageData
}亮度调整
javascript
function brightness(imageData, value) {
const data = imageData.data
for (let i = 0; i < data.length; i += 4) {
data[i] += value // R
data[i + 1] += value // G
data[i + 2] += value // B
}
return imageData
}
// 增加亮度
brightness(imageData, 50)
// 降低亮度
brightness(imageData, -50)对比度调整
javascript
function contrast(imageData, value) {
const data = imageData.data
const factor = (259 * (value + 255)) / (255 * (259 - value))
for (let i = 0; i < data.length; i += 4) {
data[i] = factor * (data[i] - 128) + 128
data[i + 1] = factor * (data[i + 1] - 128) + 128
data[i + 2] = factor * (data[i + 2] - 128) + 128
}
return imageData
}模糊效果
javascript
function blur(imageData, radius) {
const data = imageData.data
const width = imageData.width
const height = imageData.height
const copy = new Uint8ClampedArray(data)
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0, count = 0
// 遍历周围像素
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
const nx = x + dx
const ny = y + dy
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
const i = (ny * width + nx) * 4
r += copy[i]
g += copy[i + 1]
b += copy[i + 2]
count++
}
}
}
const i = (y * width + x) * 4
data[i] = r / count
data[i + 1] = g / count
data[i + 2] = b / count
}
}
return imageData
}锐化效果
javascript
function sharpen(imageData) {
const data = imageData.data
const width = imageData.width
const height = imageData.height
const copy = new Uint8ClampedArray(data)
// 锐化卷积核
const kernel = [
0, -1, 0,
-1, 5, -1,
0, -1, 0
]
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
for (let c = 0; c < 3; c++) {
let sum = 0
let ki = 0
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
const i = ((y + dy) * width + (x + dx)) * 4 + c
sum += copy[i] * kernel[ki++]
}
}
data[(y * width + x) * 4 + c] = sum
}
}
}
return imageData
}浮雕效果
javascript
function emboss(imageData) {
const data = imageData.data
const width = imageData.width
const height = imageData.height
const copy = new Uint8ClampedArray(data)
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const i = (y * width + x) * 4
const iPrev = ((y - 1) * width + (x - 1)) * 4
for (let c = 0; c < 3; c++) {
const val = copy[i + c] - copy[iPrev + c] + 128
data[i + c] = val
}
}
}
return imageData
}视频处理
实时视频滤镜
html
<video id="video" autoplay></video>
<canvas id="canvas"></canvas>
<script>
const video = document.getElementById('video')
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 获取摄像头
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
video.srcObject = stream
})
video.addEventListener('play', function() {
canvas.width = video.videoWidth
canvas.height = video.videoHeight
function processFrame() {
ctx.drawImage(video, 0, 0)
// 应用灰度滤镜
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
grayscale(imageData)
ctx.putImageData(imageData, 0, 0)
requestAnimationFrame(processFrame)
}
processFrame()
})
</script>视频截图
javascript
function captureFrame(video) {
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
const ctx = canvas.getContext('2d')
ctx.drawImage(video, 0, 0)
// 返回 base64 图像
return canvas.toDataURL('image/png')
}
// 使用示例
const screenshot = captureFrame(video)
const img = new Image()
img.src = screenshot综合示例
图像编辑器
javascript
class ImageEditor {
constructor(canvas) {
this.canvas = canvas
this.ctx = canvas.getContext('2d')
this.originalImage = null
this.currentImageData = null
}
loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
this.originalImage = img
this.canvas.width = img.width
this.canvas.height = img.height
this.ctx.drawImage(img, 0, 0)
this.currentImageData = this.ctx.getImageData(0, 0, img.width, img.height)
resolve()
}
img.onerror = reject
img.src = src
})
}
applyFilter(filterFn, ...args) {
const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
filterFn(imageData, ...args)
this.ctx.putImageData(imageData, 0, 0)
}
reset() {
if (this.originalImage) {
this.ctx.drawImage(this.originalImage, 0, 0)
}
}
save() {
return this.canvas.toDataURL('image/png')
}
}
// 使用示例
const editor = new ImageEditor(canvas)
await editor.loadImage('photo.jpg')
editor.applyFilter(grayscale)
editor.applyFilter(brightness, 30)
editor.applyFilter(contrast, 20)常见问题
1. 跨域问题
问题: 图像来自不同域时,getImageData() 抛出安全错误。
解决方案:
javascript
const img = new Image()
img.crossOrigin = 'anonymous' // 需要服务器支持 CORS
img.src = 'https://example.com/image.jpg'2. 性能优化
问题: 像素操作速度慢。
解决方案:
- 使用 Web Worker 进行离屏处理
- 使用 Typed Array 操作
- 避免频繁调用
getImageData和putImageData
3. 内存泄漏
问题: 大量 ImageData 对象导致内存泄漏。
解决方案:
javascript
// 及时释放引用
imageData = null
// 使用对象池复用 ImageData
const imagePool = []
function getImageData(width, height) {
if (imagePool.length > 0) {
return imagePool.pop()
}
return ctx.createImageData(width, height)
}
function releaseImageData(imageData) {
imagePool.push(imageData)
}下一步学习
返回:Canvas 教程目录 | 上一篇:文本绘制