You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, levels = 5, edgeThreshold = 80) {
// Validate and parse parameters
let numLevels = parseInt(levels, 10) || 5;
numLevels = Math.max(2, Math.min(255, numLevels)); // Clamp levels between 2 and 255
let threshold = parseInt(edgeThreshold, 10) || 80;
threshold = Math.max(0, Math.min(1000, threshold)); // Clamp threshold to a reasonable range
// 1. Setup Canvas and Context
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0);
const originalImageData = ctx.getImageData(0, 0, width, height);
const originalData = originalImageData.data;
// Create a new data array for the posterized image
const posterizedData = new Uint8ClampedArray(originalData.length);
// 2. Posterization Pass
// This reduces the number of colors in the image.
const levelStep = 255 / (numLevels - 1);
for (let i = 0; i < originalData.length; i += 4) {
posterizedData[i] = Math.round(Math.round(originalData[i] / levelStep) * levelStep);
posterizedData[i + 1] = Math.round(Math.round(originalData[i + 1] / levelStep) * levelStep);
posterizedData[i + 2] = Math.round(Math.round(originalData[i + 2] / levelStep) * levelStep);
posterizedData[i + 3] = originalData[i + 3];
}
// 3. Grayscale Conversion Pass
// Convert the posterized image to grayscale for edge detection.
const grayscaleData = new Uint8ClampedArray(width * height);
for (let i = 0; i < posterizedData.length; i += 4) {
const r = posterizedData[i];
const g = posterizedData[i + 1];
const b = posterizedData[i + 2];
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
grayscaleData[i / 4] = luminance;
}
// Create the final image data array.
const finalImageData = ctx.createImageData(width, height);
const finalData = finalImageData.data;
// 4. Sobel Edge Detection Pass
// This detects edges in the grayscale image to create the ink lines.
const sobelX = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1],
];
const sobelY = [
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1],
];
// Helper to get a grayscale pixel, clamping at the edges.
const getGrayPixel = (x, y) => {
const clampedX = Math.max(0, Math.min(x, width - 1));
const clampedY = Math.max(0, Math.min(y, height - 1));
return grayscaleData[clampedY * width + clampedX];
};
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let gx = 0;
let gy = 0;
// Apply 3x3 convolution kernels
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const gray = getGrayPixel(x + kx, y + ky);
gx += gray * sobelX[ky + 1][kx + 1];
gy += gray * sobelY[ky + 1][kx + 1];
}
}
const magnitude = Math.sqrt(gx * gx + gy * gy);
const i = (y * width + x) * 4;
// 5. Final Composition
// If the edge magnitude is above the threshold, draw a black pixel.
// Otherwise, use the posterized color.
if (magnitude > threshold) {
finalData[i] = 0; // R
finalData[i + 1] = 0; // G
finalData[i + 2] = 0; // B
finalData[i + 3] = posterizedData[i + 3]; // Alpha
} else {
finalData[i] = posterizedData[i];
finalData[i + 1] = posterizedData[i + 1];
finalData[i + 2] = posterizedData[i + 2];
finalData[i + 3] = posterizedData[i + 3];
}
}
}
// 6. Draw the result to the canvas
ctx.putImageData(finalImageData, 0, 0);
return canvas;
}
Apply Changes