You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, threshold = 128, levels = 2, invert = "false", sharpen = "false") {
// Ensure originalImg is a usable HTMLImageElement and is loaded
if (!originalImg || !(originalImg instanceof HTMLImageElement) || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
// console.error("Image Woodcut Filter: Invalid or unloaded image provided.");
// Create and return a minimal, empty canvas or throw an error
// For now, returning a small black canvas to indicate an issue without breaking flow
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 1;
errorCanvas.height = 1;
const errorCtx = errorCanvas.getContext('2d');
if (errorCtx) {
errorCtx.fillStyle = 'black';
errorCtx.fillRect(0, 0, 1, 1);
}
return errorCanvas;
}
// Parse and validate parameters
let parsedThresholdNum = Number(threshold);
if (isNaN(parsedThresholdNum)) {
parsedThresholdNum = 128; // Default if parsing fails for threshold
}
const pThreshold = Math.min(255, Math.max(0, parsedThresholdNum));
let parsedLevelsNum = parseInt(String(levels), 10);
if (isNaN(parsedLevelsNum) || parsedLevelsNum <= 0) {
parsedLevelsNum = 2; // Default if parsing fails or invalid number
}
const pLevels = Math.max(2, parsedLevelsNum); // Ensure at least 2 levels
const pInvert = String(invert).toLowerCase() === "true";
const pSharpen = String(sharpen).toLowerCase() === "true";
const canvas = document.createElement('canvas');
// Add willReadFrequently hint for potential performance improvement
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Optional Sharpening Pass
if (pSharpen) {
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const srcDataForSharpen = new Uint8ClampedArray(imageData.data); // Copy for safe reading
const dataForSharpen = imageData.data; // Reference for writing
const width = canvas.width;
const height = canvas.height;
// Basic 3x3 Sharpening Kernel
const kernel = [
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
];
const kernelSize = 3;
const halfKernel = Math.floor(kernelSize / 2);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let rSum = 0, gSum = 0, bSum = 0;
for (let ky = 0; ky < kernelSize; ky++) {
for (let kx = 0; kx < kernelSize; kx++) {
const Rky = ky - halfKernel;
const Rkx = kx - halfKernel;
const pixelY = y + Rky;
const pixelX = x + Rkx;
const weight = kernel[ky][kx];
if (weight === 0) continue; // Optimization
// Handle image boundaries by clamping
const currentPixelX = Math.min(width - 1, Math.max(0, pixelX));
const currentPixelY = Math.min(height - 1, Math.max(0, pixelY));
const srcIdx = (currentPixelY * width + currentPixelX) * 4;
rSum += srcDataForSharpen[srcIdx] * weight;
gSum += srcDataForSharpen[srcIdx + 1] * weight;
bSum += srcDataForSharpen[srcIdx + 2] * weight;
}
}
const dstIdx = (y * width + x) * 4;
dataForSharpen[dstIdx] = Math.min(255, Math.max(0, rSum));
dataForSharpen[dstIdx + 1] = Math.min(255, Math.max(0, gSum));
dataForSharpen[dstIdx + 2] = Math.min(255, Math.max(0, bSum));
// Alpha (dataForSharpen[dstIdx + 3]) remains unchanged from original
}
}
ctx.putImageData(imageData, 0, 0); // Put sharpened image back to canvas
}
// Main Woodcut Filter Processing (Grayscale + Thresholding/Posterization)
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Grayscale conversion using luminance formula
let gray = 0.299 * r + 0.587 * g + 0.114 * b;
let finalGray;
if (pLevels === 2) {
// Binary thresholding for classic woodcut B&W look
finalGray = gray < pThreshold ? 0 : 255;
} else {
// Posterization for multi-tone woodcut effect
const step = 255 / (pLevels - 1);
finalGray = Math.round(gray / step) * step;
finalGray = Math.min(255, Math.max(0, finalGray)); // Clamp to [0, 255]
}
if (pInvert) {
finalGray = 255 - finalGray;
}
data[i] = finalGray; // Red
data[i + 1] = finalGray; // Green
data[i + 2] = finalGray; // Blue
// Alpha (data[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes