Please bookmark this page to avoid losing your image tool!

Image Woodcut Filter Application

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Woodcut Filter Application allows users to transform images into a woodcut-style art by applying a grayscale conversion and thresholding technique. Users can customize parameters such as the threshold value, the number of tonal levels, and whether to invert the colors or apply a sharpening effect. This tool is ideal for artists, designers, or anyone looking to create stylized artwork from photos or images, making it useful for graphic design projects, printing, or digital art creation.

Leave a Reply

Your email address will not be published. Required fields are marked *