Please bookmark this page to avoid losing your image tool!

Image Acrylic Paint Filter Effect

(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.
async function processImage(originalImg, levelsInput = 6, brushSizeInput = 5) {
    // Parse and validate levelsInput
    let numLevels = parseInt(levelsInput); // parseInt can handle numbers and strings gracefully
    numLevels = isNaN(numLevels) ? 6 : Math.max(2, numLevels); // Default to 6 if NaN, ensure minimum 2 levels

    // Parse and validate brushSizeInput
    let currentBrushSize = parseInt(brushSizeInput);
    currentBrushSize = isNaN(currentBrushSize) ? 5 : Math.max(1, currentBrushSize); // Default to 5 if NaN, ensure minimum brush size of 1

    const canvas = document.createElement('canvas');
    // Use { willReadFrequently: true } for potential performance gains with getImageData/putImageData
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        console.error("Image has zero dimensions. Cannot process.");
        // Return a minimal 1x1 canvas as per requirements to return a displayable element
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = 1;
        emptyCanvas.height = 1;
        return emptyCanvas;
    }

    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    const srcImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const srcData = srcImageData.data;

    let dataForPosterization; // This will hold the data (either original or median-filtered) before posterization

    // Step 1: Apply Median Filter (simulates paint smudging/flattening)
    // Only apply if brushSize creates a radius greater than 0 pixels
    if (currentBrushSize > 1) {
        const radius = Math.floor(currentBrushSize / 2);
        const medianFilteredImageData = ctx.createImageData(canvas.width, canvas.height);
        const medianFilteredData = medianFilteredImageData.data;

        // Helper function for median calculation. Sorts the input array directly.
        const getMedian = (arr) => {
            arr.sort((a, b) => a - b);
            const mid = Math.floor(arr.length / 2);
            if (arr.length % 2 !== 0) {
                return arr[mid];
            }
            // For even length, average the two middle elements
            return (arr[mid - 1] + arr[mid]) / 2;
        };

        for (let y = 0; y < canvas.height; y++) {
            for (let x = 0; x < canvas.width; x++) {
                const rValues = [];
                const gValues = [];
                const bValues = [];

                // Collect pixel values from the neighborhood
                for (let ky = -radius; ky <= radius; ky++) {
                    for (let kx = -radius; kx <= radius; kx++) {
                        const pixelX = Math.min(canvas.width - 1, Math.max(0, x + kx)); // Clamp X
                        const pixelY = Math.min(canvas.height - 1, Math.max(0, y + ky)); // Clamp Y
                        const neighborIndex = (pixelY * canvas.width + pixelX) * 4;

                        rValues.push(srcData[neighborIndex]);
                        gValues.push(srcData[neighborIndex + 1]);
                        bValues.push(srcData[neighborIndex + 2]);
                    }
                }

                const dstIndex = (y * canvas.width + x) * 4;
                medianFilteredData[dstIndex]     = getMedian(rValues);
                medianFilteredData[dstIndex + 1] = getMedian(gValues);
                medianFilteredData[dstIndex + 2] = getMedian(bValues);
                medianFilteredData[dstIndex + 3] = srcData[dstIndex + 3]; // Preserve original alpha
            }
        }
        dataForPosterization = medianFilteredData;
    } else {
        // If brushSize is 1, no median filter is applied. Posterize the original image data.
        dataForPosterization = srcData;
    }

    // Step 2: Apply Posterization (reduces color palette)
    const resultImageData = ctx.createImageData(canvas.width, canvas.height);
    const resultData = resultImageData.data;
    
    // numLevels is guaranteed to be at least 2, so (numLevels - 1) is at least 1.
    const factor = 255 / (numLevels - 1); 

    for (let i = 0; i < dataForPosterization.length; i += 4) {
        resultData[i]     = Math.round(Math.round(dataForPosterization[i] / factor) * factor);
        resultData[i + 1] = Math.round(Math.round(dataForPosterization[i + 1] / factor) * factor);
        resultData[i + 2] = Math.round(Math.round(dataForPosterization[i + 2] / factor) * factor);
        resultData[i + 3] = dataForPosterization[i + 3]; // Preserve alpha from previous step
    }

    ctx.putImageData(resultImageData, 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 Acrylic Paint Filter Effect tool transforms your images by applying an acrylic paint-like filter. Users can adjust parameters such as the number of color levels and brush size to create unique artistic effects. This tool is ideal for artists, designers, and hobbyists looking to convert standard photos into stylized pieces of artwork, suitable for digital projects, social media posts, or personal portfolios.

Leave a Reply

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