You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, algorithm = 'Laplacian', strength = 5) {
/**
* Applies a convolution kernel to image data.
* Used for the Laplacian filter.
* @param {Uint8ClampedArray} src - Source pixel data.
* @param {Uint8ClampedArray} dst - Destination pixel data array.
* @param {number} width - Image width.
* @param {number} height - Image height.
* @param {number[]} kernel - The convolution kernel (e.g., a 3x3 matrix as a 9-element array).
*/
const applyLaplacianConvolution = (src, dst, width, height, kernel) => {
const kernelSize = Math.sqrt(kernel.length);
const halfKernelSize = Math.floor(kernelSize / 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 < kernelSize; ky++) {
for (let kx = 0; kx < kernelSize; kx++) {
const imgY = y + ky - halfKernelSize;
const imgX = x + kx - halfKernelSize;
// Clamp to edge
const clampedY = Math.max(0, Math.min(height - 1, imgY));
const clampedX = Math.max(0, Math.min(width - 1, imgX));
const offset = (clampedY * width + clampedX) * 4;
const weight = kernel[ky * kernelSize + kx];
r += src[offset] * weight;
g += src[offset + 1] * weight;
b += src[offset + 2] * weight;
}
}
const dstOffset = (y * width + x) * 4;
// The result of a Laplacian can be negative, so we take the absolute
// value and convert to grayscale to create a visible edge map.
const gray = Math.abs(r * 0.299 + g * 0.587 + b * 0.114);
dst[dstOffset] = gray;
dst[dstOffset + 1] = gray;
dst[dstOffset + 2] = gray;
dst[dstOffset + 3] = 255; // Opaque
}
}
};
/**
* Applies a Bilateral Filter for edge-preserving smoothing.
* @param {Uint8ClampedArray} src - Source pixel data.
* @param {Uint8ClampedArray} dst - Destination pixel data array.
* @param {number} width - Image width.
* @param {number} height - Image height.
* @param {number} sigmaSpace - The spatial Gaussian standard deviation. Controls blur radius.
* @param {number} sigmaColor - The color/intensity Gaussian standard deviation. Controls edge preservation.
*/
const applyBilateralFilter = (src, dst, width, height, sigmaSpace, sigmaColor) => {
const radius = Math.floor(Math.max(1, strength));
const twoSigmaSpaceSq = 2 * sigmaSpace * sigmaSpace;
const twoSigmaColorSq = 2 * sigmaColor * sigmaColor;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const centerIndex = (y * width + x) * 4;
const cr = src[centerIndex];
const cg = src[centerIndex + 1];
const cb = src[centerIndex + 2];
let sumR = 0, sumG = 0, sumB = 0;
let totalWeight = 0;
for (let ky = -radius; ky <= radius; ky++) {
for (let kx = -radius; kx <= radius; kx++) {
const ny = y + ky;
const nx = x + kx;
if (ny >= 0 && ny < height && nx >= 0 && nx < width) {
const spatialDistSq = kx * kx + ky * ky;
if (spatialDistSq > radius * radius) continue;
const neighborIndex = (ny * width + nx) * 4;
const nr = src[neighborIndex];
const ng = src[neighborIndex + 1];
const nb = src[neighborIndex + 2];
const colorDistSq = (cr - nr)**2 + (cg - ng)**2 + (cb - nb)**2;
const wSpace = Math.exp(-spatialDistSq / twoSigmaSpaceSq);
const wColor = Math.exp(-colorDistSq / twoSigmaColorSq);
const weight = wSpace * wColor;
sumR += nr * weight;
sumG += ng * weight;
sumB += nb * weight;
totalWeight += weight;
}
}
}
dst[centerIndex] = sumR / totalWeight;
dst[centerIndex + 1] = sumG / totalWeight;
dst[centerIndex + 2] = sumB / totalWeight;
dst[centerIndex + 3] = src[centerIndex + 3]; // Preserve alpha
}
}
};
const canvas = document.createElement('canvas');
// Using { willReadFrequently: true } is a performance hint for the browser
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0);
const nonApplicableAlgos = ['yape06', 'fastcorners', 'lloyd', 'polygon vertex'];
const algoKey = algorithm.toLowerCase();
if (nonApplicableAlgos.includes(algoKey)) {
console.warn(`Algorithm "${algorithm}" is not a raster image smoothing filter. It likely relates to mesh or vector graphics processing and is not implemented. Returning the original image.`);
return canvas;
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const srcData = imageData.data;
const dstData = new Uint8ClampedArray(srcData.length);
switch (algoKey) {
case 'laplacian':
const laplacianKernel = [
0, -1, 0,
-1, 4, -1,
0, -1, 0
];
applyLaplacianConvolution(srcData, dstData, canvas.width, canvas.height, laplacianKernel);
break;
case 'contrast weighted':
const sigma = Math.max(1, strength);
// The second parameter (sigmaColor) is scaled for a more intuitive "strength" effect.
applyBilateralFilter(srcData, dstData, canvas.width, canvas.height, sigma, sigma * 10);
break;
default:
console.warn(`Unknown or unimplemented algorithm: "${algorithm}". Returning original image.`);
dstData.set(srcData); // Copy original data to destination
break;
}
const newImageData = new ImageData(dstData, canvas.width, canvas.height);
ctx.putImageData(newImageData, 0, 0);
return canvas;
}
Apply Changes