You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, quantizationLevels = 4, edgeThreshold = 80, edgeColor = 'black') {
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(originalImg, 0, 0, width, height);
/**
* Helper function to parse a CSS color string into an RGB object.
* @param {string} colorStr - The color string (e.g., 'black', '#FF0000', 'rgb(255,0,0)').
* @returns {{r: number, g: number, b: number}}
*/
const parseColor = (colorStr) => {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = tempCanvas.height = 1;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.fillStyle = colorStr;
tempCtx.fillRect(0, 0, 1, 1);
const [r, g, b] = tempCtx.getImageData(0, 0, 1, 1).data;
return { r, g, b };
};
const edgeRgb = parseColor(edgeColor);
// Ensure quantization levels are valid to avoid division by zero.
if (quantizationLevels < 2) {
quantizationLevels = 2;
}
const step = 255 / (quantizationLevels - 1);
const originalImageData = ctx.getImageData(0, 0, width, height);
const originalData = originalImageData.data;
const finalImageData = ctx.createImageData(width, height);
const finalData = finalImageData.data;
// 1. Create a grayscale representation of the image for edge detection.
const grayData = new Uint8ClampedArray(width * height);
for (let i = 0; i < originalData.length; i += 4) {
const r = originalData[i];
const g = originalData[i + 1];
const b = originalData[i + 2];
// Using luminance formula for better-perceived brightness.
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
grayData[i / 4] = gray;
}
// Sobel kernels for edge detection
const kernelX = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
];
const kernelY = [
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]
];
// 2. Iterate through each pixel, apply Sobel filter for edges,
// and quantize colors for non-edge pixels.
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x);
const dataIndex = i * 4;
let isEdge = false;
// Skip border pixels for Sobel operator
if (y > 0 && y < height - 1 && x > 0 && x < width - 1) {
let gx = 0;
let gy = 0;
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const neighborY = y + ky;
const neighborX = x + kx;
const neighborIndex = neighborY * width + neighborX;
const grayValue = grayData[neighborIndex];
gx += grayValue * kernelX[ky + 1][kx + 1];
gy += grayValue * kernelY[ky + 1][kx + 1];
}
}
const magnitude = Math.sqrt(gx * gx + gy * gy);
if (magnitude > edgeThreshold) {
isEdge = true;
}
}
if (isEdge) {
finalData[dataIndex] = edgeRgb.r;
finalData[dataIndex + 1] = edgeRgb.g;
finalData[dataIndex + 2] = edgeRgb.b;
} else {
// Not an edge, so apply color quantization (posterization)
const r = originalData[dataIndex];
const g = originalData[dataIndex + 1];
const b = originalData[dataIndex + 2];
finalData[dataIndex] = Math.round(r / step) * step;
finalData[dataIndex + 1] = Math.round(g / step) * step;
finalData[dataIndex + 2] = Math.round(b / step) * step;
}
finalData[dataIndex + 3] = 255; // Set alpha to fully opaque
}
}
// 3. Put the processed image data back onto the canvas
ctx.putImageData(finalImageData, 0, 0);
return canvas;
}
Apply Changes