Canvas图像处理(像素操作、滤镜等)
Canvas图像处理基础
HTML5 Canvas提供了强大的图像处理能力,可以直接操作像素数据实现各种效果。CanvasRenderingContext2D接口的getImageData()和putImageData()方法允许开发者读取和修改画布上的像素数据。每个像素由RGBA四个分量组成,取值范围都是0-255。
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 遍历所有像素
for (let i = 0; i < data.length; i += 4) {
// data[i] = red
// data[i+1] = green
// data[i+2] = blue
// data[i+3] = alpha
}
基本像素操作
灰度化处理
将彩色图像转换为灰度图像是基本的图像处理操作,常见算法包括平均值法、加权平均值法等。
function grayscale(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
return imageData;
}
反色效果
反色效果通过将每个颜色分量取反值实现。
function invert(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // red
data[i + 1] = 255 - data[i + 1]; // green
data[i + 2] = 255 - data[i + 2]; // blue
}
return imageData;
}
高级滤镜实现
卷积滤镜
卷积是图像处理中常用的技术,通过核矩阵与图像像素的卷积运算实现各种效果。
function applyConvolution(imageData, kernel, divisor = 1, offset = 0) {
const width = imageData.width;
const height = imageData.height;
const srcData = new Uint8ClampedArray(imageData.data);
const dstData = imageData.data;
const kRows = kernel.length;
const kCols = kernel[0].length;
const halfKRows = Math.floor(kRows / 2);
const halfKCols = Math.floor(kCols / 2);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0;
for (let ky = 0; ky < kRows; ky++) {
for (let kx = 0; kx < kCols; kx++) {
const px = Math.min(width - 1, Math.max(0, x + kx - halfKCols));
const py = Math.min(height - 1, Math.max(0, y + ky - halfKRows));
const pos = (py * width + px) * 4;
const weight = kernel[ky][kx];
r += srcData[pos] * weight;
g += srcData[pos + 1] * weight;
b += srcData[pos + 2] * weight;
}
}
const pos = (y * width + x) * 4;
dstData[pos] = (r / divisor + offset) | 0;
dstData[pos + 1] = (g / divisor + offset) | 0;
dstData[pos + 2] = (b / divisor + offset) | 0;
}
}
return imageData;
}
常见卷积核示例
模糊效果
const blurKernel = [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
];
applyConvolution(imageData, blurKernel, 9);
锐化效果
const sharpenKernel = [
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
];
applyConvolution(imageData, sharpenKernel);
边缘检测
const edgeDetectKernel = [
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]
];
applyConvolution(imageData, edgeDetectKernel);
颜色调整
亮度调整
function adjustBrightness(imageData, value) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] += value; // red
data[i + 1] += value; // green
data[i + 2] += value; // blue
}
return imageData;
}
对比度调整
function adjustContrast(imageData, contrast) {
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
const data = imageData.data;
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;
}
色相/饱和度调整
function adjustHSB(imageData, hueDelta, saturationFactor, brightnessFactor) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
let r = data[i] / 255;
let g = data[i + 1] / 255;
let b = data[i + 2] / 255;
// RGB转HSV
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, v = max;
const d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0;
} else {
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
// 调整HSV
h = (h + hueDelta / 360) % 1;
if (h < 0) h += 1;
s = Math.min(1, Math.max(0, s * saturationFactor));
v = Math.min(1, Math.max(0, v * brightnessFactor));
// HSV转RGB
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
let nr, ng, nb;
switch (i % 6) {
case 0: nr = v; ng = t; nb = p; break;
case 1: nr = q; ng = v; nb = p; break;
case 2: nr = p; ng = v; nb = t; break;
case 3: nr = p; ng = q; nb = v; break;
case 4: nr = t; ng = p; nb = v; break;
case 5: nr = v; ng = p; nb = q; break;
}
data[i] = nr * 255;
data[i + 1] = ng * 255;
data[i + 2] = nb * 255;
}
return imageData;
}
性能优化技巧
使用Web Workers
对于大型图像处理,可以使用Web Workers避免阻塞UI线程。
// 主线程
const worker = new Worker('image-worker.js');
worker.postMessage({imageData, operation: 'grayscale'});
worker.onmessage = function(e) {
ctx.putImageData(e.data, 0, 0);
};
// image-worker.js
self.onmessage = function(e) {
const imageData = e.data.imageData;
// 处理图像
self.postMessage(processedImageData, [processedImageData.data.buffer]);
};
使用TypedArray优化
直接操作Uint8ClampedArray比使用ImageData.data接口更快。
function fastGrayscale(imageData) {
const data = new Uint8Array(imageData.data.buffer);
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = avg;
}
return imageData;
}
分块处理
对于超大图像,可以分块处理避免内存问题。
function processInChunks(ctx, width, height, processFn, chunkSize = 256) {
for (let y = 0; y < height; y += chunkSize) {
for (let x = 0; x < width; x += chunkSize) {
const w = Math.min(chunkSize, width - x);
const h = Math.min(chunkSize, height - y);
const imageData = ctx.getImageData(x, y, w, h);
const processed = processFn(imageData);
ctx.putImageData(processed, x, y);
}
}
}
高级图像处理技术
基于直方图的处理
直方图均衡化
function histogramEqualization(imageData) {
const data = imageData.data;
const hist = new Array(256).fill(0);
const width = imageData.width;
const height = imageData.height;
const total = width * height;
// 计算直方图
for (let i = 0; i < data.length; i += 4) {
const gray = Math.round(0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]);
hist[gray]++;
}
// 计算累积分布
let sum = 0;
const cdf = hist.map(count => {
sum += count;
return sum;
});
// 归一化
const cdfMin = Math.min(...cdf.filter(v => v > 0));
const scale = 255 / (total - cdfMin);
// 应用变换
for (let i = 0; i < data.length; i += 4) {
const gray = Math.round(0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]);
const equalized = Math.round((cdf[gray] - cdfMin) * scale);
data[i] = data[i + 1] = data[i + 2] = equalized;
}
return imageData;
}
基于阈值的二值化
function threshold(imageData, thresholdValue) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
const value = gray > thresholdValue ? 255 : 0;
data[i] = data[i + 1] = data[i + 2] = value;
}
return imageData;
}
自适应阈值
function adaptiveThreshold(imageData, blockSize = 15, C = 5) {
const width = imageData.width;
const height = imageData.height;
const integral = new Array(width * height).fill(0);
const data = new Uint8Array(imageData.data.buffer);
// 计算积分图
for (let y = 0; y < height; y++) {
let sum = 0;
for (let x = 0; x < width; x++) {
const pos = y * width + x;
const gray = Math.round(0.299 * data[pos * 4] + 0.587 * data[pos * 4 + 1] + 0.114 * data[pos * 4 + 2]);
sum += gray;
integral[pos] = (y > 0 ? integral[pos - width] : 0) + sum;
}
}
// 应用自适应阈值
const halfBlock = Math.floor(blockSize / 2);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pos = y * width + x;
const gray = Math.round(0.299 * data[pos * 4] + 0.587 * data[pos * 4 + 1] + 0.114 * data[pos * 4 + 2]);
// 计算区域边界
const x1 = Math.max(0, x - halfBlock);
const y1 = Math.max(0, y - halfBlock);
const x2 = Math.min(width - 1, x + halfBlock);
const y2 = Math.min(height - 1, y + halfBlock);
// 计算区域面积
const area = (x2 - x1 + 1) * (y2 - y1 + 1);
// 使用积分图快速计算区域总和
const a = y1 > 0 && x1 > 0 ? integral[(y1 - 1) * width + (x1 - 1)] : 0;
const b = y1 > 0 ? integral[(y1 - 1) * width + x2] : 0;
const c = x1 > 0 ? integral[y2 * width + (x1 - 1)] : 0;
const d = integral[y2 * width + x2];
const sum = d - b - c + a;
const mean = sum / area;
data[pos * 4] = data[pos * 4 + 1] = data[pos * 4 + 2] = gray > (mean - C) ? 255 : 0;
}
}
return imageData;
}
混合模式与合成
Canvas支持多种混合模式,可以通过globalCompositeOperation属性设置。
// 正常模式
ctx.globalCompositeOperation = 'source-over';
// 常用混合模式示例
const blendModes = [
'multiply', // 正片叠底
'screen', // 滤色
'overlay', // 叠加
'darken', // 变暗
'lighten', // 变亮
'color-dodge', // 颜色减淡
'color-burn', // 颜色加深
'hard-light', // 强光
'soft-light', // 柔光
'difference', // 差值
'exclusion', // 排除
'hue', // 色相
'saturation', // 饱和度
'color', // 颜色
'luminosity' // 亮度
];
// 应用混合模式
function applyBlendMode(ctx, imageData, mode) {
ctx.putImageData(imageData, 0, 0);
ctx.globalCompositeOperation = mode;
ctx.fillStyle = 'rgba(200, 150, 100, 0.5)';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
图像变形与扭曲
波纹效果
function rippleEffect(imageData, amplitude, frequency) {
const width = imageData.width;
const height = imageData.height;
const srcData = new Uint8ClampedArray(imageData.data);
const dstData = imageData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// 计算波纹偏移
const offsetX = Math.round(amplitude * Math.sin(2 * Math.PI * y / frequency));
const offsetY = Math.round(amplitude * Math.cos(2 * Math.PI * x / frequency));
// 边界检查
const srcX = Math.max(0, Math.min(width - 1, x + offsetX));
const srcY = Math.max(0, Math.min(height - 1, y + offsetY));
// 复制像素
const srcPos = (srcY * width + srcX) * 4;
const dstPos = (y * width + x) * 4;
dstData[dstPos] = srcData[srcPos];
dstData[dstPos + 1] = srcData[srcPos + 1];
dstData[dstPos + 2] = srcData[srcPos + 2];
dstData[dstPos + 3] = srcData[srcPos + 3];
}
}
return imageData;
}
球面扭曲
function sphereDistortion(imageData, radius, centerX, centerY) {
const width = imageData.width;
const height = imageData.height;
const srcData = new Uint8ClampedArray(imageData.data);
const dstData = imageData.data;
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn