Please bookmark this page to avoid losing your image tool!

Image Doodle 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 = 50, lineColorStr = "0,0,0", backgroundColorStr = "255,255,255", lineThickness = 1) {
    // Helper function to parse color strings (e.g., "255,0,0") into [R, G, B] arrays
    const parseColorVal = (colorStr, defaultArr) => {
        const parts = colorStr.split(',').map(c => parseInt(c, 10));
        if (parts.length === 3 && parts.every(num => !isNaN(num) && num >= 0 && num <= 255)) {
            return parts;
        }
        // If parsing fails, return the specified default color array
        return defaultArr; 
    };

    const lineColor = parseColorVal(lineColorStr, [0, 0, 0]); // Default to black if parsing fails
    const backgroundColor = parseColorVal(backgroundColorStr, [255, 255, 255]); // Default to white if parsing fails
    
    // Ensure lineThickness is an integer and at least 1
    const actualLineThickness = Math.max(1, Math.floor(lineThickness));

    const imgWidth = originalImg.width;
    const imgHeight = originalImg.height;

    // Handle cases where the image might not be loaded properly or is empty
    if (imgWidth === 0 || imgHeight === 0) {
        console.warn("Image has zero width or height.");
        const emptyCanvas = document.createElement('canvas');
        // Set canvas to a minimal 1x1 size if original dimensions are 0
        emptyCanvas.width = imgWidth || 1;
        emptyCanvas.height = imgHeight || 1;
        const emptyCtx = emptyCanvas.getContext('2d');
        if (emptyCtx) {
            emptyCtx.fillStyle = `rgb(${backgroundColor[0]},${backgroundColor[1]},${backgroundColor[2]})`;
            emptyCtx.fillRect(0,0,emptyCanvas.width, emptyCanvas.height);
        }
        return emptyCanvas;
    }
    
    // Create a temporary canvas to draw the image and get its pixel data
    const canvas = document.createElement('canvas');
    canvas.width = imgWidth;
    canvas.height = imgHeight;
    // Add willReadFrequently hint for potential performance optimization
    const ctx = canvas.getContext('2d', { willReadFrequently: true }); 

    ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);

    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        // Handle potential security errors if the image is cross-origin
        console.error("Could not get ImageData (e.g., cross-origin issue):", e);
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = imgWidth || 150; // Fallback size
        errorCanvas.height = imgHeight || 100; // Fallback size
        const errCtx = errorCanvas.getContext('2d');
        if (errCtx) {
            errCtx.fillStyle = `rgb(${backgroundColor[0]},${backgroundColor[1]},${backgroundColor[2]})`;
            errCtx.fillRect(0,0,errorCanvas.width, errorCanvas.height);
            errCtx.fillStyle = `rgb(${lineColor[0]},${lineColor[1]},${lineColor[2]})`;
            errCtx.font = "16px Arial";
            errCtx.textAlign = "center";
            errCtx.textBaseline = "middle";
            errCtx.fillText("Error: Cannot process image.", errorCanvas.width/2, errorCanvas.height/2);
        }
        return errorCanvas;
    }
    
    const data = imageData.data;
    const width = imgWidth; 
    const height = imgHeight;

    // 1. Convert image to grayscale
    const grayscaleData = new Uint8ClampedArray(width * height);
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        // Standard luminance formula for grayscale conversion
        const gray = 0.2989 * r + 0.5870 * g + 0.1140 * b; 
        grayscaleData[i / 4] = gray;
    }

    // Prepare output ImageData, initialized with the background color
    const outputImageData = ctx.createImageData(width, height);
    const outputData = outputImageData.data;
    for (let i = 0; i < outputData.length; i += 4) {
        outputData[i] = backgroundColor[0];
        outputData[i + 1] = backgroundColor[1];
        outputData[i + 2] = backgroundColor[2];
        outputData[i + 3] = 255; // Opaque alpha
    }
    
    // Sobel kernels for edge detection
    const sobelXKernel = [
        [-1, 0, 1],
        [-2, 0, 2],
        [-1, 0, 1]
    ];
    const sobelYKernel = [
        [-1, -2, -1],
        [ 0,  0,  0],
        [ 1,  2,  1]
    ];

    // Calculate offsets for line thickness application
    const thicknessStartOffset = -Math.floor((actualLineThickness - 1) / 2);
    const thicknessEndOffset = Math.ceil((actualLineThickness - 1) / 2);
    // Or simpler for centered square:
    // const halfThick = Math.floor(actualLineThickness / 2); // (actualLineThickness-1)/2 for odd, actualLineThickness/2 for even?
    // The startOffset/endOffset as define above are correct for N=1 (0,0), N=2 (0,1), N=3 (-1,1)


    // 2. Apply Sobel operator and thresholding
    // Iterate pixels, excluding a 1-pixel border where the kernel wouldn't fit
    for (let y = 1; y < height - 1; y++) { 
        for (let x = 1; x < width - 1; x++) {
            let gx = 0; // Gradient in X direction
            let gy = 0; // Gradient in Y direction

            // Apply Sobel kernels to the 3x3 neighborhood
            for (let ky = -1; ky <= 1; ky++) {
                for (let kx = -1; kx <= 1; kx++) {
                    const K_val_x = sobelXKernel[ky + 1][kx + 1];
                    const K_val_y = sobelYKernel[ky + 1][kx + 1];
                    
                    const pixelIndex = (y + ky) * width + (x + kx); // Index in 1D grayscale array
                    const grayVal = grayscaleData[pixelIndex];
                    
                    gx += grayVal * K_val_x;
                    gy += grayVal * K_val_y;
                }
            }

            // Calculate gradient magnitude
            const magnitude = Math.sqrt(gx * gx + gy * gy);
            
            // If magnitude exceeds threshold, it's an edge point
            if (magnitude > threshold) {
                // Draw the line pixel with specified thickness
                for (let dy = thicknessStartOffset; dy <= thicknessEndOffset; dy++) {
                    for (let dx = thicknessStartOffset; dx <= thicknessEndOffset; dx++) {
                        const currentX = x + dx;
                        const currentY = y + dy;

                        // Check bounds to ensure thickened line pixels are within the image
                        if (currentY >= 0 && currentY < height && currentX >= 0 && currentX < width) {
                            const outputPixelIndex = (currentY * width + currentX) * 4;
                            outputData[outputPixelIndex]     = lineColor[0]; // R
                            outputData[outputPixelIndex + 1] = lineColor[1]; // G
                            outputData[outputPixelIndex + 2] = lineColor[2]; // B
                            // Alpha (outputData[outputPixelIndex + 3]) is already 255
                        }
                    }
                }
            }
        }
    }
    
    // Create the final output canvas and put the processed image data onto it
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = width;
    outputCanvas.height = height;
    const outputCtx = outputCanvas.getContext('2d');
    outputCtx.putImageData(outputImageData, 0, 0);

    return outputCanvas;
}

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 Doodle Filter Application allows users to transform images into a stylized sketch-like representation by applying a doodle filter. This tool utilizes edge detection methods to outline prominent features in the image, enhancing artistic expression. Users can customize aspects like line thickness, line color, and background color, making it versatile for various creative projects. This application is ideal for artists, designers, or anyone looking to add a whimsical touch to their photos, turning them into unique pieces of art suitable for sharing on social media, creating posters, or enhancing personal projects.

Leave a Reply

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