Canvas image processing (pixel manipulation, filters, etc.)
Canvas Image Processing Basics
HTML5 Canvas provides powerful image processing capabilities, allowing direct manipulation of pixel data to achieve various effects. The getImageData()
and putImageData()
methods of the CanvasRenderingContext2D interface enable developers to read and modify pixel data on the canvas. Each pixel consists of four RGBA components, each ranging from 0 to 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;
// Iterate through all pixels
for (let i = 0; i < data.length; i += 4) {
// data[i] = red
// data[i+1] = green
// data[i+2] = blue
// data[i+3] = alpha
}
Basic Pixel Operations
Grayscale Conversion
Converting a color image to grayscale is a fundamental image processing operation. Common algorithms include the average method and weighted average method.
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;
}
Invert Effect
The invert effect is achieved by inverting each color component.
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;
}
Advanced Filter Implementation
Convolution Filters
Convolution is a common technique in image processing, achieved by applying a kernel matrix to image pixels.
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;
}
Common Kernel Examples
Blur Effect
const blurKernel = [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
];
applyConvolution(imageData, blurKernel, 9);
Sharpen Effect
const sharpenKernel = [
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
];
applyConvolution(imageData, sharpenKernel);
Edge Detection
const edgeDetectKernel = [
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]
];
applyConvolution(imageData, edgeDetectKernel);
Color Adjustments
Brightness Adjustment
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;
}
Contrast Adjustment
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;
}
Hue/Saturation Adjustment
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 to 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;
}
// Adjust 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 to 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;
}
Performance Optimization Techniques
Using Web Workers
For large image processing tasks, use Web Workers to avoid blocking the UI thread.
// Main thread
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;
// Process image
self.postMessage(processedImageData, [processedImageData.data.buffer]);
};
Using TypedArray Optimization
Directly manipulating Uint8ClampedArray is faster than using the ImageData.data interface.
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;
}
Chunk Processing
For very large images, process in chunks to avoid memory issues.
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);
}
}
}
Advanced Image Processing Techniques
Histogram-Based Processing
Histogram Equalization
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;
// Calculate histogram
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]++;
}
// Calculate cumulative distribution
let sum = 0;
const cdf = hist.map(count => {
sum += count;
return sum;
});
// Normalize
const cdfMin = Math.min(...cdf.filter(v => v > 0));
const scale = 255 / (total - cdfMin);
// Apply transformation
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;
}
Threshold-Based Binarization
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;
}
Adaptive Threshold
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);
// Calculate integral image
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;
}
}
// Apply adaptive threshold
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]);
// Calculate region boundaries
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);
// Calculate region area
const area = (x2 - x1 + 1) * (y2 - y1 + 1);
// Use integral image to quickly calculate region sum
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;
}
Blend Modes and Compositing
Canvas supports various blend modes through the globalCompositeOperation
property.
// Normal mode
ctx.globalCompositeOperation = 'source-over';
// Common blend mode examples
const blendModes = [
'multiply', // Multiply
'screen', // Screen
'overlay', // Overlay
'darken', // Darken
'lighten', // Lighten
'color-dodge', // Color Dodge
'color-burn', // Color Burn
'hard-light', // Hard Light
'soft-light', // Soft Light
'difference', // Difference
'exclusion', // Exclusion
'hue', // Hue
'saturation', // Saturation
'color', // Color
'luminosity' // Luminosity
];
// Apply blend mode
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);
}
Image Warping and Distortion
Ripple Effect
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++) {
// Calculate ripple offset
const offsetX = Math.round(amplitude * Math.sin(2 * Math.PI * y / frequency));
const offsetY = Math.round(amplitude * Math.cos(2 * Math.PI * x / frequency));
// Boundary check
const srcX = Math.max(0, Math.min(width - 1, x + offsetX));
const srcY = Math.max(0, Math.min(height - 1, y + offsetY));
// Copy pixel
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;
}
Spherical Distortion
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