Please bookmark this page to avoid losing your image tool!

Image Ink Drawing Filter

(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, blurRadius = 1, threshold = 200) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Ensure originalImg dimensions are valid
    const width = originalImg.naturalWidth || originalImg.width;
    const height = originalImg.naturalHeight || originalImg.height;

    if (width === 0 || height === 0) {
        console.error("Image has zero width or height. Ensure the image is loaded and has valid dimensions.");
        // Return an empty or minimal canvas to avoid further errors
        canvas.width = 1; // Avoid 0x0 canvas if possible
        canvas.height = 1;
        if (ctx) { // ctx might be null if canvas creation failed in extreme environments
            ctx.clearRect(0,0,1,1);
        }
        return canvas;
    }

    canvas.width = width;
    canvas.height = height;

    ctx.drawImage(originalImg, 0, 0, width, height);
    
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, width, height);
    } catch (e) {
        console.error("Could not get image data. This might be due to cross-origin restrictions if the image is hosted on a different domain.", e);
        // Return the canvas with the original image drawn, as processing failed.
        // Or indicate error on canvas:
        ctx.clearRect(0,0,width,height); 
        ctx.font = "16px Arial";
        ctx.fillStyle = "red";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText("Error: Could not process image data.", width / 2, height / 2);
        return canvas;
    }
    
    const data = imageData.data; // This is a Uint8ClampedArray [R,G,B,A, R,G,B,A, ...]

    // 1. Create Grayscale version
    // We store grayscale values in a separate array of appropriate size (width * height)
    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 luminosity formula for grayscale conversion
        const gray = 0.299 * r + 0.587 * g + 0.114 * b;
        grayscaleData[i / 4] = gray;
    }

    // 2. Create Inverted Grayscale version
    const invertedGrayscaleData = new Uint8ClampedArray(width * height);
    for (let i = 0; i < grayscaleData.length; i++) {
        invertedGrayscaleData[i] = 255 - grayscaleData[i];
    }

    // 3. Blur the Inverted Grayscale version (Box Blur)
    // A blurRadius of 0 will cause the Color Dodge to make the image mostly white.
    // A small positive blurRadius (e.g., 1-3) is recommended for this effect.
    const radius = Math.max(0, Math.floor(blurRadius)); 
    const blurredInvertedGrayscaleData = new Uint8ClampedArray(width * height);

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            let sum = 0;
            let count = 0;
            // Iterate over the blur kernel
            for (let ky = -radius; ky <= radius; ky++) {
                for (let kx = -radius; kx <= radius; kx++) {
                    const currentY = y + ky;
                    const currentX = x + kx;
                    // Check if the neighboring pixel is within image bounds
                    if (currentX >= 0 && currentX < width && currentY >= 0 && currentY < height) {
                        const index = currentY * width + currentX;
                        sum += invertedGrayscaleData[index];
                        count++;
                    }
                }
            }
            if (count > 0) {
                blurredInvertedGrayscaleData[y * width + x] = sum / count;
            } else {
                // This case should ideally not be hit if image dimensions are > 0
                // and radius is reasonable. Fallback to the non-blurred value.
                blurredInvertedGrayscaleData[y * width + x] = invertedGrayscaleData[y * width + x];
            }
        }
    }

    // 4. Blend Grayscale (bottom layer) with Blurred Inverted Grayscale (top layer)
    //    using the Color Dodge blend mode. Then, apply a threshold to create a
    //    binary black and white "ink drawing" effect.
    const finalData = imageData.data; // Modify imageData.data in place for efficiency

    for (let i = 0; i < grayscaleData.length; i++) {
        const bottomPixelVal = grayscaleData[i];         // Original grayscale
        const topPixelVal = blurredInvertedGrayscaleData[i]; // Blurred inverted grayscale
        
        let dodgedValue;
        if (topPixelVal === 255) { // If blend layer (top) is white, result is white
            dodgedValue = 255;
        } else if (topPixelVal === 0) { // If blend layer is black, result is the source (bottom)
             dodgedValue = bottomPixelVal; // This preserves detail in very dark inverted areas
        } else {
            // Color Dodge formula: Result = Bottom / (1 - Top/255)
            // Scaled to 0-255 range: Result = (Bottom * 255) / (255 - Top)
            dodgedValue = (bottomPixelVal * 255.0) / (255.0 - topPixelVal);
        }
        
        // Clamp the dodged value to the valid [0, 255] range
        dodgedValue = Math.min(255, Math.max(0, dodgedValue));

        // Apply threshold: pixels with intensity below threshold become black (0),
        // others (at or above threshold) become white (255).
        const outputValue = dodgedValue < threshold ? 0 : 255;

        const dataIndex = i * 4; // Each pixel has 4 components (R,G,B,A)
        finalData[dataIndex]     = outputValue; // Red channel
        finalData[dataIndex + 1] = outputValue; // Green channel
        finalData[dataIndex + 2] = outputValue; // Blue channel
        finalData[dataIndex + 3] = 255;         // Alpha channel (fully opaque for ink on paper)
    }

    // Put the modified image data back onto the canvas
    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 Ink Drawing Filter transforms images into a stylized ink drawing effect. It converts input images into grayscale, applies an inversion and blurring effect, and then merges the results to create a high-contrast black and white representation. This tool can be used for artistic purposes, such as converting photos into illustrations or enhancing designs for print media. It is especially useful for artists, designers, or anyone looking to add a unique visual flair to images.

Leave a Reply

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