You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, posterLevels = 4, saturation = 1.2, edgeThreshold = 80, initialBlurRadius = 0, outlineColorRGB = "0,0,0") {
// Parameter validation and normalization
posterLevels = Math.max(2, Math.floor(posterLevels));
saturation = Math.max(0, saturation);
edgeThreshold = Math.max(0, Math.min(255, edgeThreshold));
initialBlurRadius = Math.max(0, initialBlurRadius);
let parsedOutlineColor = outlineColorRGB.split(',').map(Number);
if (parsedOutlineColor.length !== 3 || parsedOutlineColor.some(val => isNaN(val) || val < 0 || val > 255)) {
console.warn("Invalid outlineColorRGB format or values. Defaulting to black [0,0,0]. Expected 'r,g,b' with values 0-255.");
parsedOutlineColor = [0, 0, 0];
} else {
parsedOutlineColor = parsedOutlineColor.map(val => Math.floor(Math.max(0, Math.min(255, val))));
}
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
const outputCanvas = document.createElement('canvas');
outputCanvas.width = width;
outputCanvas.height = height;
const ctx = outputCanvas.getContext('2d', { willReadFrequently: true });
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
// 1. Draw original image to tempCanvas, applying initial blur if specified
if (initialBlurRadius > 0) {
tempCtx.filter = `blur(${initialBlurRadius}px)`;
}
tempCtx.drawImage(originalImg, 0, 0, width, height);
if (initialBlurRadius > 0) {
tempCtx.filter = 'none';
}
const sourceImageData = tempCtx.getImageData(0, 0, width, height);
const posterizedAndSaturatedData = tempCtx.createImageData(width, height);
// 2. Posterize and Saturate pass
const posterizationFactor = posterLevels - 1;
for (let i = 0; i < sourceImageData.data.length; i += 4) {
let r = sourceImageData.data[i];
let g = sourceImageData.data[i+1];
let b = sourceImageData.data[i+2];
const a = sourceImageData.data[i+3];
// Posterize
if (posterizationFactor > 0) { // posterLevels >= 2
r = Math.round((r / 255) * posterizationFactor) * (255 / posterizationFactor);
g = Math.round((g / 255) * posterizationFactor) * (255 / posterizationFactor);
b = Math.round((b / 255) * posterizationFactor) * (255 / posterizationFactor);
}
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// Saturate
if (saturation !== 1.0) {
const lum = 0.299 * r + 0.587 * g + 0.114 * b;
r = lum + saturation * (r - lum);
g = lum + saturation * (g - lum);
b = lum + saturation * (b - lum);
r = Math.max(0, Math.min(255, Math.round(r)));
g = Math.max(0, Math.min(255, Math.round(g)));
b = Math.max(0, Math.min(255, Math.round(b)));
}
posterizedAndSaturatedData.data[i] = r;
posterizedAndSaturatedData.data[i+1] = g;
posterizedAndSaturatedData.data[i+2] = b;
posterizedAndSaturatedData.data[i+3] = a;
}
ctx.putImageData(posterizedAndSaturatedData, 0, 0);
// 3. Edge Detection (Sobel)
const grayscaleData = tempCtx.createImageData(width, height);
for (let i = 0; i < sourceImageData.data.length; i += 4) {
const r_src = sourceImageData.data[i];
const g_src = sourceImageData.data[i+1];
const b_src = sourceImageData.data[i+2];
const gray = Math.round(0.299 * r_src + 0.587 * g_src + 0.114 * b_src);
grayscaleData.data[i] = gray;
grayscaleData.data[i+1] = gray;
grayscaleData.data[i+2] = gray;
grayscaleData.data[i+3] = sourceImageData.data[i+3];
}
const edgeImageData = tempCtx.createImageData(width, height);
const Gx = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]];
const Gy = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];
const MAX_SOBEL_MAGNITUDE_APPROX = 1020 * Math.sqrt(2);
const normalizationFactor = 255.0 / MAX_SOBEL_MAGNITUDE_APPROX;
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let sumX = 0;
let sumY = 0;
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const pixelIndex = ((y + ky) * width + (x + kx)) * 4;
const grayVal = grayscaleData.data[pixelIndex];
sumX += grayVal * Gx[ky+1][kx+1];
sumY += grayVal * Gy[ky+1][kx+1];
}
}
const magnitude = Math.sqrt(sumX * sumX + sumY * sumY);
const normalizedMagnitude = magnitude * normalizationFactor;
const outputIndex = (y * width + x) * 4;
if (normalizedMagnitude > edgeThreshold) {
edgeImageData.data[outputIndex] = parsedOutlineColor[0];
edgeImageData.data[outputIndex+1] = parsedOutlineColor[1];
edgeImageData.data[outputIndex+2] = parsedOutlineColor[2];
edgeImageData.data[outputIndex+3] = 255;
} else {
edgeImageData.data[outputIndex + 3] = 0; // Transparent for non-edges
}
}
}
tempCtx.putImageData(edgeImageData, 0, 0);
ctx.drawImage(tempCanvas, 0, 0);
return outputCanvas;
}
Apply Changes