Please bookmark this page to avoid losing your image tool!

Image Oil Painting 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, radius = 4, intensityLevels = 20) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Ensure the image object has dimensions (i.e., it's loaded)
    const width = originalImg.width;
    const height = originalImg.height;

    if (width === 0 || height === 0) {
        // Image is not loaded or has no size. Return an empty canvas.
        canvas.width = 0;
        canvas.height = 0;
        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) {
        // This can happen due to cross-origin issues if the image is not served with CORS headers.
        console.error("Error getting image data for Oil Painting Filter: ", e);
        // In case of error, return the canvas with the original image drawn (or handle as per requirements).
        // For this tool, returning the canvas with the original image is a safe fallback.
        return canvas;
    }
    
    const data = imageData.data; // Uint8ClampedArray: [R, G, B, A, R, G, B, A, ...]

    // Create a new ImageData object to store the processed pixels
    // Using ctx.createImageData is efficient as it often reuses memory or is optimized by the browser.
    const outputImageData = ctx.createImageData(width, height);
    const outputData = outputImageData.data;

    // Sanitize parameters
    radius = Math.max(0, parseInt(String(radius), 10) || 0); // Ensure radius is a non-negative integer
    intensityLevels = Math.max(2, parseInt(String(intensityLevels), 10) || 2); // Ensure at least 2 levels

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            // Histograms for intensity bins
            const intensityBin = new Array(intensityLevels).fill(0);
            const rBin = new Array(intensityLevels).fill(0);
            const gBin = new Array(intensityLevels).fill(0);
            const bBin = new Array(intensityLevels).fill(0);

            // Iterate over the neighborhood defined by `radius`
            for (let neighborY = -radius; neighborY <= radius; neighborY++) {
                for (let neighborX = -radius; neighborX <= radius; neighborX++) {
                    const currentX = x + neighborX;
                    const currentY = y + neighborY;

                    // Check if the neighbor pixel is within image bounds
                    if (currentX >= 0 && currentX < width && currentY >= 0 && currentY < height) {
                        const offset = (currentY * width + currentX) * 4;
                        const r = data[offset];
                        const g = data[offset + 1];
                        const b = data[offset + 2];
                        // We ignore alpha of neighbors, output is opaque

                        // Calculate current pixel's intensity (luminance)
                        const intensity = 0.2126 * r + 0.7152 * g + 0.0722 * b;
                        
                        // Determine which bin this pixel's intensity falls into
                        // Max bin index is intensityLevels - 1
                        const binIndex = Math.min(Math.floor(intensity * intensityLevels / 256), intensityLevels - 1);

                        // Add to histograms
                        intensityBin[binIndex]++;
                        rBin[binIndex] += r;
                        gBin[binIndex] += g;
                        bBin[binIndex] += b;
                    }
                }
            }

            // Find the dominant intensity bin (the one with the most pixels)
            let maxCount = 0;
            let dominantBinIndex = 0;
            for (let i = 0; i < intensityLevels; i++) {
                if (intensityBin[i] > maxCount) {
                    maxCount = intensityBin[i];
                    dominantBinIndex = i;
                }
            }

            const outputOffset = (y * width + x) * 4;
            if (maxCount > 0) {
                // Set the output pixel to the average color of the dominant bin
                outputData[outputOffset]     = rBin[dominantBinIndex] / maxCount;
                outputData[outputOffset + 1] = gBin[dominantBinIndex] / maxCount;
                outputData[outputOffset + 2] = bBin[dominantBinIndex] / maxCount;
            } else {
                // Fallback: if maxCount is 0 (e.g., radius 0 and pixel is outside, though unlikely with checks)
                // Copy the original pixel color to the output
                const originalOffset = (y * width + x) * 4;
                outputData[outputOffset]     = data[originalOffset];
                outputData[outputOffset + 1] = data[originalOffset + 1];
                outputData[outputOffset + 2] = data[originalOffset + 2];
            }
            outputData[outputOffset + 3] = 255; // Set alpha to fully opaque for the "paint"
        }
    }

    // Put the processed image data back onto the canvas
    ctx.putImageData(outputImageData, 0, 0);

    return canvas; // Return the canvas with the oil painting effect
}

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 Oil Painting Filter is a web-based tool that applies an artistic oil painting effect to your images. By adjusting parameters such as the brush radius and the number of intensity levels, users can create a unique painted appearance that mimics traditional oil paints. This tool is useful for artists, designers, and hobbyists looking to enhance their images with a creative touch, suitable for social media posts, personalized gifts, or digital art projects.

Leave a Reply

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