Please bookmark this page to avoid losing your image tool!

Image To Oil Paint Art Converter

(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.
/**
 * Converts an image to an oil painting style artwork.
 * This effect is achieved by using a Kuwahara-like filter, which reduces noise
 * and detail while preserving edges, mimicking the appearance of brush strokes.
 * An optional color quantization step is also applied to create larger, flatter color areas.
 *
 * @param {HTMLImageElement} originalImg The original image element to process.
 * @param {number} [radius=4] The radius of the brush stroke effect. Larger values create more abstract, larger strokes.
 * @param {number} [intensity=50] The number of color levels per channel. Lower values create a more posterized, stylized look with fewer colors. A value of 256 effectively disables this step.
 * @returns {HTMLCanvasElement} A new canvas element displaying the oil painted image.
 */
function processImage(originalImg, radius = 4, intensity = 50) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    const width = originalImg.naturalWidth;
    const height = originalImg.naturalHeight;
    canvas.width = width;
    canvas.height = height;

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

    // Create a copy for processing to have a non-quantized source for the filter
    const quantizedData = new Uint8ClampedArray(srcData);

    // 1. Pre-processing: Color Quantization based on intensity
    // A higher intensity means more color levels.
    // Clamp intensity to a reasonable range [2, 256].
    const numLevels = Math.max(2, Math.min(256, Math.floor(intensity)));
    if (numLevels < 256) { // No need to quantize if 256 levels are requested
        const step = 255 / (numLevels - 1);
        for (let i = 0; i < quantizedData.length; i += 4) {
            quantizedData[i] = Math.round(quantizedData[i] / step) * step; // R
            quantizedData[i + 1] = Math.round(quantizedData[i + 1] / step) * step; // G
            quantizedData[i + 2] = Math.round(quantizedData[i + 2] / step) * step; // B
        }
    }


    // 2. Main Filter (Kuwahara-like effect)
    const dstImageData = ctx.createImageData(width, height);
    const dstData = dstImageData.data;
    const r = Math.floor(radius);

    const getIndex = (x, y) => (y * width + x) * 4;

    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const i = getIndex(x, y);

            // Quadrant data format: [sumR, sumG, sumB, sumLuminanceSq, count]
            const quadrants = [
                [0, 0, 0, 0, 0], // Top-left
                [0, 0, 0, 0, 0], // Top-right
                [0, 0, 0, 0, 0], // Bottom-left
                [0, 0, 0, 0, 0]  // Bottom-right
            ];
            const sumsLuminance = [0, 0, 0, 0];

            // Loop through the neighborhood window
            for (let j = -r; j <= r; j++) {
                for (let k = -r; k <= r; k++) {
                    const neighborY = y + j;
                    const neighborX = x + k;

                    // Check image bounds
                    if (neighborY >= 0 && neighborY < height && neighborX >= 0 && neighborX < width) {
                        const neighborIndex = getIndex(neighborX, neighborY);
                        const rVal = quantizedData[neighborIndex];
                        const gVal = quantizedData[neighborIndex + 1];
                        const bVal = quantizedData[neighborIndex + 2];
                        const luminance = 0.299 * rVal + 0.587 * gVal + 0.114 * bVal;

                        // Determine which quadrant the neighbor pixel falls into
                        let quadrantIndex;
                        if (j <= 0 && k <= 0) quadrantIndex = 0; // Top-left
                        else if (j <= 0 && k > 0) quadrantIndex = 1; // Top-right
                        else if (j > 0 && k <= 0) quadrantIndex = 2; // Bottom-left
                        else quadrantIndex = 3; // Bottom-right

                        quadrants[quadrantIndex][0] += rVal;
                        quadrants[quadrantIndex][1] += gVal;
                        quadrants[quadrantIndex][2] += bVal;
                        quadrants[quadrantIndex][3] += luminance * luminance;
                        quadrants[quadrantIndex][4]++;
                        sumsLuminance[quadrantIndex] += luminance;
                    }
                }
            }

            // Calculate variance and find the quadrant with the minimum variance
            let minVariance = -1;
            let finalR = 0, finalG = 0, finalB = 0;

            for (let q = 0; q < 4; q++) {
                const count = quadrants[q][4];
                if (count === 0) continue;

                const meanR = quadrants[q][0] / count;
                const meanG = quadrants[q][1] / count;
                const meanB = quadrants[q][2] / count;
                const meanLuminance = sumsLuminance[q] / count;
                
                // Variance = E[X^2] - (E[X])^2
                const variance = (quadrants[q][3] / count) - (meanLuminance * meanLuminance);

                if (minVariance === -1 || variance < minVariance) {
                    minVariance = variance;
                    finalR = meanR;
                    finalG = meanG;
                    finalB = meanB;
                }
            }

            // Set the destination pixel color to the mean color of the smoothest quadrant
            dstData[i] = finalR;
            dstData[i + 1] = finalG;
            dstData[i + 2] = finalB;
            dstData[i + 3] = 255; // Alpha
        }
    }

    // 3. Put the processed data back onto the canvas
    ctx.putImageData(dstImageData, 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 To Oil Paint Art Converter transforms images into artworks that mimic the style of oil paintings. This tool utilizes a Kuwahara-like filter to reduce noise and detail while preserving edges, giving images the appearance of brush strokes. Users can customize the effect by adjusting the radius of brush strokes for more abstract or defined results and controlling the number of color levels to enhance or simplify the color palette. This tool is ideal for artists, graphic designers, or anyone looking to create artistic representations of their photos for prints, social media, or personal projects.

Leave a Reply

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