Please bookmark this page to avoid losing your image tool!

Image Stipple 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, blackColorStr = "0,0,0", whiteColorStr = "255,255,255") {
    // Helper function to parse color strings like "r,g,b"
    function parseColor(colorStr, defaultColor) {
        // Ensure colorStr is a string before attempting to split
        if (typeof colorStr !== 'string') {
            return defaultColor;
        }
        const parts = colorStr.split(',').map(s => {
            const num = parseInt(s.trim(), 10);
            // Check if num is NaN, or outside the 0-255 range for color components
            return (isNaN(num) || num < 0 || num > 255) ? NaN : num;
        });
        // Check if we have exactly 3 parts and all are valid numbers
        if (parts.length === 3 && parts.every(p => !isNaN(p))) {
            return parts; // Returns [r, g, b]
        }
        return defaultColor; // Return default if parsing failed or input was invalid
    }

    const blackColor = parseColor(blackColorStr, [0, 0, 0]);
    const whiteColor = parseColor(whiteColorStr, [255, 255, 255]);

    // Use naturalWidth/Height if available, otherwise fallback to width/height
    // This ensures we get the original dimensions even if the image is styled differently on the page
    const width = originalImg.naturalWidth || originalImg.width;
    const height = originalImg.naturalHeight || originalImg.height;

    if (width === 0 || height === 0) {
        // Return an empty canvas if image dimensions are invalid
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = width;
        emptyCanvas.height = height;
        return emptyCanvas;
    }

    // Create a temporary canvas to draw the original image and get its pixel data
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempCtx = tempCanvas.getContext('2d');
    tempCtx.drawImage(originalImg, 0, 0, width, height);
    
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, width, height);
    } catch (e) {
        console.error("Error getting image data:", e);
        // Create a placeholder canvas indicating an error
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = width;
        errorCanvas.height = height;
        const errorCtx = errorCanvas.getContext('2d');
        if (errorCtx) {
            errorCtx.fillStyle = 'rgba(200, 200, 200, 0.5)';
            errorCtx.fillRect(0, 0, width, height);
            errorCtx.fillStyle = 'red';
            errorCtx.textAlign = 'center';
            errorCtx.textBaseline = 'middle';
            errorCtx.font = `${Math.min(width, height) / 10}px Arial`;
            errorCtx.fillText("Error processing image", width / 2, height / 2);
        }
        return errorCanvas;
    }
    const pixels = imageData.data;

    // Create a 2D array to store grayscale values. Using Float32Array for precision with error accumulation.
    const grayscaleGrid = new Array(height);
    for (let y = 0; y < height; y++) {
        grayscaleGrid[y] = new Float32Array(width);
        for (let x = 0; x < width; x++) {
            const idx = (y * width + x) * 4;
            const r = pixels[idx];
            const g = pixels[idx + 1];
            const b = pixels[idx + 2];
            // Standard luminance calculation for grayscale
            grayscaleGrid[y][x] = 0.299 * r + 0.587 * g + 0.114 * b;
        }
    }

    // Create the output canvas and its ImageData object
    const outCanvas = document.createElement('canvas');
    outCanvas.width = width;
    outCanvas.height = height;
    const outCtx = outCanvas.getContext('2d');
    const outputImageData = outCtx.createImageData(width, height);
    const outputPixels = outputImageData.data;

    // Apply Floyd-Steinberg dithering
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            // Get the current grayscale value (which includes diffused errors from previous pixels)
            const currentGrayValue = grayscaleGrid[y][x];
            // Clamp this value to the 0-255 range for thresholding
            const oldPixelClamped = Math.max(0, Math.min(255, currentGrayValue)); 
            
            let newPixelGrayscaleEquivalent; // This will be 0 for black, 255 for white after quantization
            const outputIdx = (y * width + x) * 4; // Index for the output pixel array

            if (oldPixelClamped < threshold) {
                outputPixels[outputIdx]     = blackColor[0]; // Red channel
                outputPixels[outputIdx + 1] = blackColor[1]; // Green channel
                outputPixels[outputIdx + 2] = blackColor[2]; // Blue channel
                outputPixels[outputIdx + 3] = 255;           // Alpha channel (opaque)
                newPixelGrayscaleEquivalent = 0; // Target grayscale value for black
            } else {
                outputPixels[outputIdx]     = whiteColor[0]; // Red channel
                outputPixels[outputIdx + 1] = whiteColor[1]; // Green channel
                outputPixels[outputIdx + 2] = whiteColor[2]; // Blue channel
                outputPixels[outputIdx + 3] = 255;           // Alpha channel (opaque)
                newPixelGrayscaleEquivalent = 255; // Target grayscale value for white
            }

            // Calculate the quantization error using the non-clamped currentGrayValue
            // This ensures accurate error distribution.
            const quantError = currentGrayValue - newPixelGrayscaleEquivalent;

            // Distribute the error to neighboring pixels according to Floyd-Steinberg
            // ( Affects pixels to the right, and in the next row )
            if (x + 1 < width) {
                grayscaleGrid[y][x + 1] += quantError * 7 / 16;
            }
            if (y + 1 < height) {
                if (x - 1 >= 0) {
                    grayscaleGrid[y + 1][x - 1] += quantError * 3 / 16;
                }
                grayscaleGrid[y + 1][x] += quantError * 5 / 16;
                if (x + 1 < width) {
                    grayscaleGrid[y + 1][x + 1] += quantError * 1 / 16;
                }
            }
        }
    }

    // Put the processed pixel data onto the output canvas
    outCtx.putImageData(outputImageData, 0, 0);
    return outCanvas;
}

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 Stipple Filter Application is a tool that transforms images into stippled artwork by applying a dithering effect. Users can adjust the threshold levels and customize the colors used for stippling. This tool is particularly useful for artists and designers looking to give images a unique, graphic quality or for creating art that mimics traditional printing techniques. Potential applications include enhancing visual content for graphic design, producing engaging social media posts, or creating distinctive prints and illustrations.

Leave a Reply

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