阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Canvas图像处理(像素操作、滤镜等)

Canvas图像处理(像素操作、滤镜等)

作者:陈川 阅读数:57790人阅读 分类: HTML

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌