6.3 图像处理与像素操作
6.3 图像处理与像素操作
Canvas不仅能够绘制矢量图形,还提供了强大的图像处理和像素级操作能力。本节将深入探讨图像加载、操作、合成以及直接像素处理等高级技术。
图像加载与基本操作
1. 图像加载与绘制
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
// 创建Image对象
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域图像
img.src = 'example.jpg';
img.onload = function() {
// 基本绘制
ctx.drawImage(img, 0, 0);
// 缩放绘制
ctx.drawImage(img, 0, 0, img.width/2, img.height/2);
// 切片绘制
ctx.drawImage(
img,
50, 50, 100, 100, // 源图像切片区域
200, 200, 150, 150 // 画布上的目标区域
);
};
img.onerror = function() {
console.error('图像加载失败');
};
2. 图像变形与变换
// 保存当前状态
ctx.save();
// 设置变换中心点
ctx.translate(150, 150);
// 旋转45度
ctx.rotate(Math.PI/4);
// 缩放图像
ctx.scale(0.8, 0.8);
// 绘制图像(中心对齐)
ctx.drawImage(img, -img.width/2, -img.height/2);
// 恢复状态
ctx.restore();
图像合成与混合模式
1. 全局合成操作
// 绘制背景
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 300, 150);
// 设置合成模式
ctx.globalCompositeOperation = 'lighter';
// 绘制重叠图形
ctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
ctx.fillRect(50, 50, 200, 100);
常用合成模式:
source-over(默认):新图形覆盖原有内容destination-over:新图形在原有内容下方multiply:像素值相乘,产生变暗效果screen:像素值反相相乘再反相,产生变亮效果overlay:结合multiply和screenlighter:颜色值相加
2. 裁剪路径与图像遮罩
// 创建圆形裁剪路径
ctx.beginPath();
ctx.arc(150, 150, 100, 0, Math.PI*2);
ctx.clip();
// 绘制图像(将被裁剪为圆形)
ctx.drawImage(img, 0, 0, 300, 300);
像素级操作
1. 获取与修改像素数据
// 获取ImageData对象
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // Uint8ClampedArray
// 遍历并修改像素(灰度化)
for(let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i+1] + data[i+2]) / 3;
data[i] = avg; // R
data[i+1] = avg; // G
data[i+2] = avg; // B
// data[i+3]保持原Alpha值
}
// 将修改后的数据放回画布
ctx.putImageData(imageData, 0, 0);
2. 常见图像滤镜实现
// 反色滤镜
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;
}
// 怀旧滤镜
function sepia(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];
data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
data[i+1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
data[i+2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
}
return imageData;
}
3. 卷积滤镜与边缘检测
// 通用卷积函数
function convolute(imageData, kernel) {
const width = imageData.width;
const height = imageData.height;
const data = imageData.data;
const newData = new Uint8ClampedArray(data.length);
const kernelSize = Math.sqrt(kernel.length);
const half = Math.floor(kernelSize / 2);
for(let y = 0; y < height; y++) {
for(let x = 0; x < width; x++) {
const px = (y * width + x) * 4;
let r = 0, g = 0, b = 0;
for(let ky = 0; ky < kernelSize; ky++) {
for(let kx = 0; kx < kernelSize; kx++) {
const cpx = ((y + ky - half) * width + (x + kx - half)) * 4;
if(data[cpx] !== undefined) {
const weight = kernel[ky * kernelSize + kx];
r += data[cpx] * weight;
g += data[cpx+1] * weight;
b += data[cpx+2] * weight;
}
}
}
newData[px] = r;
newData[px+1] = g;
newData[px+2] = b;
newData[px+3] = data[px+3]; // Alpha保持不变
}
}
return new ImageData(newData, width, height);
}
// Sobel边缘检测
function sobelEdgeDetection(imageData) {
const kernelX = [
-1, 0, 1,
-2, 0, 2,
-1, 0, 1
];
const kernelY = [
-1, -2, -1,
0, 0, 0,
1, 2, 1
];
const gx = convolute(imageData, kernelX);
const gy = convolute(imageData, kernelY);
const combined = new Uint8ClampedArray(imageData.data.length);
for(let i = 0; i < imageData.data.length; i += 4) {
// 计算梯度幅值
combined[i] = Math.sqrt(
gx.data[i] * gx.data[i] +
gy.data[i] * gy.data[i]
);
combined[i+1] = combined[i];
combined[i+2] = combined[i];
combined[i+3] = 255; // Alpha
}
return new ImageData(combined, imageData.width, imageData.height);
}
高级图像处理技术
1. 图像直方图分析
function getImageHistogram(imageData) {
const histogram = {
r: new Array(256).fill(0),
g: new Array(256).fill(0),
b: new Array(256).fill(0)
};
const data = imageData.data;
for(let i = 0; i < data.length; i += 4) {
histogram.r[data[i]]++;
histogram.g[data[i+1]]++;
histogram.b[data[i+2]]++;
}
return histogram;
}
// 直方图均衡化
function histogramEqualization(imageData) {
const histogram = getImageHistogram(imageData);
const data = imageData.data;
const totalPixels = imageData.width * imageData.height;
// 计算累积分布函数
const cdf = { r: [], g: [], b: [] };
for(let i = 0; i < 256; i++) {
cdf.r[i] = (i === 0 ? 0 : cdf.r[i-1]) + histogram.r[i];
cdf.g[i] = (i === 0 ? 0 : cdf.g[i-1]) + histogram.g[i];
cdf.b[i] = (i === 0 ? 0 : cdf.b[i-1]) + histogram.b[i];
}
// 应用均衡化
for(let i = 0; i < data.length; i += 4) {
data[i] = Math.round(255 * cdf.r[data[i]] / totalPixels); // R
data[i+1] = Math.round(255 * cdf.g[data[i+1]] / totalPixels); // G
data[i+2] = Math.round(255 * cdf.b[data[i+2]] / totalPixels); // B
}
return imageData;
}
2. 图像变形与液化效果
// 图像液化效果
function liquify(imageData, centerX, centerY, radius, strength) {
const newData = new Uint8ClampedArray(imageData.data.length);
const width = imageData.width;
const height = imageData.height;
for(let y = 0; y < height; y++) {
for(let x = 0; x < width; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx*dx + dy*dy);
if(distance < radius) {
const amount = 1 - (distance / radius);
const displacement = amount * amount * strength;
const srcX = x - dx * displacement;
const srcY = y - dy * displacement;
// 双线性插值
if(srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1) {
const x1 = Math.floor(srcX);
const x2 = Math.ceil(srcX);
const y1 = Math.floor(srcY);
const y2 = Math.ceil(srcY);
const tx = srcX - x1;
const ty = srcY - y1;
for(let c = 0; c < 4; c++) {
const idx = (y * width + x) * 4 + c;
const p1 = imageData.data[(y1 * width + x1) * 4 + c];
const p2 = imageData.data[(y1 * width + x2) * 4 + c];
const p3 = imageData.data[(y2 * width + x1) * 4 + c];
const p4 = imageData.data[(y2 * width + x2) * 4 + c];
newData[idx] =
p1 * (1-tx) * (1-ty) +
p2 * tx * (1-ty) +
p3 * (1-tx) * ty +
p4 * tx * ty;
}
}
} else {
// 复制原始像素
for(let c = 0; c < 4; c++) {
newData[(y * width + x) * 4 + c] = imageData.data[(y * width + x) * 4 + c];
}
}
}
}
return new ImageData(newData, width, height);
}
性能优化与实践建议
-
离屏Canvas:
// 创建离屏Canvas处理复杂操作 const offscreenCanvas = document.createElement('canvas'); offscreenCanvas.width = 300; offscreenCanvas.height = 300; const offscreenCtx = offscreenCanvas.getContext('2d'); // 在离屏Canvas上处理图像 offscreenCtx.drawImage(img, 0, 0); const processedImage = offscreenCtx.getImageData(0, 0, 300, 300); // 将结果绘制到主Canvas ctx.putImageData(processedImage, 0, 0); -
Web Worker处理:
// 主线程 const worker = new Worker('image-processor.js'); worker.postMessage({ imageData: ctx.getImageData(0, 0, canvas.width, canvas.height), operation: 'invert' }); worker.onmessage = function(e) { ctx.putImageData(e.data, 0, 0); }; // image-processor.js self.onmessage = function(e) { const imageData = e.data.imageData; // 处理图像... self.postMessage(processedImageData, [processedImageData.data.buffer]); }; -
性能敏感操作提示:
- 大尺寸图像处理考虑分块进行
- 频繁操作使用requestAnimationFrame节流
- 复杂滤镜考虑使用WebGL实现
实战案例:图片编辑器核心功能
<div class="image-editor">
<canvas id="editorCanvas"></canvas>
<div class="controls">
<button id="btnGrayscale">灰度化</button>
<button id="btnInvert">反色</button>
<button id="btnBlur">模糊</button>
<input type="range" id="rangeBrightness" min="-100" max="100" value="0">
<label for="rangeBrightness">亮度</label>
</div>
</div>
<script>
const editorCanvas = document.getElementById('editorCanvas');
const editorCtx = editorCanvas.getContext('2d');
let originalImageData = null;
// 加载图像
const img = new Image();
img.onload = function() {
editorCanvas.width = img.width;
editorCanvas.height = img.height;
editorCtx.drawImage(img, 0, 0);
originalImageData = editorCtx.getImageData(0, 0, editorCanvas.width, editorCanvas.height);
};
img.src = 'photo.jpg';
// 应用滤镜
document.getElementById('btnGrayscale').addEventListener('click', function() {
const imageData = editorCtx.getImageData(0, 0, editorCanvas.width, editorCanvas.height);
editorCtx.putImageData(grayscaleFilter(imageData), 0, 0);
});
// 亮度调整
document.getElementById('rangeBrightness').addEventListener('input', function() {
const value = parseInt(this.value);
const imageData = cloneImageData(originalImageData);
adjustBrightness(imageData, value);
editorCtx.putImageData(imageData, 0, 0);
});
// 辅助函数
function cloneImageData(imageData) {
return new ImageData(
new Uint8ClampedArray(imageData.data),
imageData.width,
imageData.height
);
}
function adjustBrightness(imageData, value) {
const data = imageData.data;
for(let i = 0; i < data.length; i += 4) {
data[i] = clamp(data[i] + value, 0, 255); // R
data[i+1] = clamp(data[i+1] + value, 0, 255); // G
data[i+2] = clamp(data[i+2] + value, 0, 255); // B
}
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
</script>
通过本节学习,您已经掌握了Canvas图像处理的核心技术,从基本操作到高级像素处理,这些技能可以应用于照片编辑、计算机视觉、特效生成等多个领域。在实际应用中,请根据需求选择合适的处理方式,并始终关注性能优化。
#前端开发
分享于 2025-05-20
上一篇:6.2 Canvas 路径与形状
下一篇:6.4 SVG 与 Canvas 对比