Please bookmark this page to avoid losing your image tool!

Image Pointillism 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 to apply a pointillism filter to an image.
// originalImg: JavaScript Image object (HTMLImageElement)
// dotSize: Diameter of the dots (default: 10 pixels)
// spacingFactor: Multiplier for dotSize to determine grid spacing. 
//                1.0 means grid cells are dotSize x dotSize. 
//                <1 means more overlap potential, >1 means more space between dot centers. (default: 1.0)
// fillDensity: Probability (0-1) that a grid cell will actually contain a dot. (default: 0.8)
// colorJitter: Maximum random change to R,G,B color components (0-255). (default: 0)
// positionJitter: How much dots can deviate from their grid cell centers (0-1). 
//                 0 means dots are perfectly centered on their grid positions.
//                 1 means dots can be anywhere within their grid cell. (default: 0.5)
// backgroundColor: Background color for the canvas (e.g., "white", "black", "transparent"). (default: "white")
async function processImage(
    originalImg, 
    dotSize = 10, 
    spacingFactor = 1.0, 
    fillDensity = 0.8, 
    colorJitter = 0, 
    positionJitter = 0.5, 
    backgroundColor = "white"
) {
    // Ensure parameters are of correct type and within reasonable bounds
    dotSize = Number(dotSize);
    spacingFactor = Number(spacingFactor);
    fillDensity = Number(fillDensity);
    colorJitter = Number(colorJitter);
    positionJitter = Number(positionJitter);
    backgroundColor = String(backgroundColor); // Ensure backgroundColor is a string

    // Sanitize numeric inputs
    dotSize = Math.max(1, dotSize); // Ensure dotSize is at least 1px
    spacingFactor = Math.max(0.1, spacingFactor); // Avoid division by zero or extremely small steps for gridStep
    fillDensity = Math.max(0, Math.min(1, fillDensity)); // Clamp between 0 and 1
    colorJitter = Math.max(0, Math.min(255, colorJitter)); // Clamp between 0 and 255
    positionJitter = Math.max(0, Math.min(1, positionJitter)); // Clamp between 0 and 1

    const canvasWidth = originalImg.naturalWidth || originalImg.width;
    const canvasHeight = originalImg.naturalHeight || originalImg.height;

    const targetCanvas = document.createElement('canvas');
    targetCanvas.width = canvasWidth;
    targetCanvas.height = canvasHeight;
    const ctx = targetCanvas.getContext('2d');

    if (canvasWidth === 0 || canvasHeight === 0) {
        console.warn("Image has zero width or height. Returning empty canvas.");
        // If the image source exists but dimensions are 0, it might not have loaded.
        // The prompt assumes originalImg is a usable Image object.
        return targetCanvas; // Returns an empty canvas (possibly 0x0)
    }

    // Create a source canvas to get pixel data from the original image
    // This is helpful to avoid issues with tainted canvases if originalImg is cross-origin,
    // and provides a reliable way to get pixel data.
    const sourceCanvas = document.createElement('canvas');
    sourceCanvas.width = canvasWidth;
    sourceCanvas.height = canvasHeight;
    // Add { willReadFrequently: true } for potential performance optimization if supported
    const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true }); 
    
    try {
        sourceCtx.drawImage(originalImg, 0, 0, canvasWidth, canvasHeight);
    } catch (e) {
        console.error("Error drawing original image to source canvas: ", e);
        // Fallback: draw an error message on the target canvas
        ctx.fillStyle = "rgba(200, 200, 200, 1)"; // Light gray background
        ctx.fillRect(0, 0, canvasWidth, canvasHeight);
        ctx.fillStyle = "red";
        ctx.textAlign = "center";
        ctx.font = "16px Arial";
        ctx.fillText("Error: Could not draw source image.", canvasWidth / 2, canvasHeight / 2);
        return targetCanvas;
    }
    
    let imgData;
    try {
        imgData = sourceCtx.getImageData(0, 0, canvasWidth, canvasHeight);
    } catch (e) {
        console.error("Could not getImageData from source canvas (CORS issue or other security restriction?): ", e);
        // Fallback: draw original image on target canvas with an error message overlay
        // This might still fail if originalImg itself is tainted and cannot be drawn.
        try {
            ctx.drawImage(originalImg, 0, 0, canvasWidth, canvasHeight); 
        } catch (drawError) {
            console.error("Also could not draw original image to target canvas: ", drawError);
            ctx.fillStyle = "rgba(200, 200, 200, 1)"; // Light gray background
            ctx.fillRect(0, 0, canvasWidth, canvasHeight);
        }
        ctx.fillStyle = "rgba(255, 0, 0, 0.7)"; // Semi-transparent red overlay
        ctx.fillRect(0, 0, canvasWidth, canvasHeight);
        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.font = "16px Arial";
        ctx.fillText("Error: Cannot process image pixels.", canvasWidth / 2, canvasHeight / 2 - 10);
        ctx.fillText("(Likely a CORS security restriction)", canvasWidth / 2, canvasHeight / 2 + 10);
        return targetCanvas;
    }
    const data = imgData.data;

    // Set background for the target canvas
    if (backgroundColor && backgroundColor.toLowerCase() !== 'transparent') {
        ctx.fillStyle = backgroundColor;
        ctx.fillRect(0, 0, canvasWidth, canvasHeight);
    } else {
        // If 'transparent', ensure canvas is clear (it's a new canvas, so it is, but explicit)
        ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    }

    const dotRadius = dotSize / 2;
    // Ensure gridStep is at least 1 to prevent infinite loops or extreme behavior
    const gridStep = Math.max(1, dotSize * spacingFactor); 

    for (let y = 0; y < canvasHeight; y += gridStep) {
        for (let x = 0; x < canvasWidth; x += gridStep) {
            if (Math.random() < fillDensity) {
                // Calculate the theoretical center of the current grid cell
                const currentGridCellCenterX = x + gridStep / 2;
                const currentGridCellCenterY = y + gridStep / 2;

                // Apply position jitter relative to the grid cell center
                // (Math.random() - 0.5) gives a range from -0.5 to 0.5
                // Multiplied by gridStep scales it to the cell dimensions
                // Multiplied by positionJitter scales the randomness
                const offsetX = (Math.random() - 0.5) * gridStep * positionJitter;
                const offsetY = (Math.random() - 0.5) * gridStep * positionJitter;

                const dotX = currentGridCellCenterX + offsetX;
                const dotY = currentGridCellCenterY + offsetY;

                // Determine color sampling position (use the dot's actual center for color)
                // Clamp to be within image bounds for safe pixel array access
                const sampleX = Math.max(0, Math.min(canvasWidth - 1, Math.floor(dotX)));
                const sampleY = Math.max(0, Math.min(canvasHeight - 1, Math.floor(dotY)));
                
                const pixelIndex = (sampleY * canvasWidth + sampleX) * 4;
                
                // This check is mostly redundant if clamping is correct and image non-empty, but safe.
                if (pixelIndex < 0 || pixelIndex + 3 >= data.length) {
                    continue; 
                }

                let r = data[pixelIndex];
                let g = data[pixelIndex + 1];
                let b = data[pixelIndex + 2];
                let a = data[pixelIndex + 3];

                if (colorJitter > 0) {
                    const jitter = () => Math.floor((Math.random() - 0.5) * 2 * colorJitter);
                    r = Math.max(0, Math.min(255, r + jitter()));
                    g = Math.max(0, Math.min(255, g + jitter()));
                    b = Math.max(0, Math.min(255, b + jitter()));
                }

                ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a / 255})`;
                
                ctx.beginPath();
                // Ensure dotRadius is positive and coordinates are finite numbers before drawing
                if (dotRadius > 0 && isFinite(dotX) && isFinite(dotY)) {
                     ctx.arc(dotX, dotY, dotRadius, 0, 2 * Math.PI, false);
                     ctx.fill();
                }
            }
        }
    }

    return targetCanvas;
}

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 Pointillism Filter Application allows users to transform their images into pointillism-style artwork. By adjusting parameters such as dot size, spacing between dots, fill density, and color variation, users can create unique artistic renditions of their photos. This tool is ideal for artists looking to experiment with digital artworks, educators exploring creative techniques, or anyone interested in generating visually appealing images with a distinct pointillism effect.

Leave a Reply

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