Please bookmark this page to avoid losing your image tool!

Image To Face Painting Style 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.
async function processImage(originalImg, radius = 4, intensityLevels = 25, saturationBoost = 20) {
    // Parse parameters to assure they behave correctly even if strings are passed
    const rInt = parseInt(radius, 10) || 4;
    const lvlInt = parseInt(intensityLevels, 10) || 25;
    const satBoost = parseInt(saturationBoost, 10) || 20;

    let width = originalImg.width;
    let height = originalImg.height;

    // Return an empty canvas if image dimensions are invalid
    if (!width || !height) return document.createElement('canvas');

    // Limit maximum dimensions avoiding long computational times & browser freezes
    const MAX_DIM = 800;
    if (width > MAX_DIM || height > MAX_DIM) {
        const ratio = Math.min(MAX_DIM / width, MAX_DIM / height);
        width = Math.floor(width * ratio);
        height = Math.floor(height * ratio);
    }

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    
    // willReadFrequently optimizes memory when we are directly plucking pixel data
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Apply saturation & contrast filters first to achieve vibrant "painted" colors
    ctx.filter = `saturate(${100 + satBoost}%) contrast(115%)`;
    ctx.drawImage(originalImg, 0, 0, width, height);
    
    // Reset filters
    ctx.filter = 'none';

    let imgData;
    try {
        imgData = ctx.getImageData(0, 0, width, height);
    } catch (e) {
        console.error("Canvas tainted by cross-origin data. Image must have a proper CORS policy.");
        return canvas;
    }

    const data = imgData.data;
    const outData = new Uint8ClampedArray(data.length);

    // Preallocate count arrays outside the loop to be reused for performance
    const intensityCount = new Int32Array(lvlInt + 1);
    const sumR = new Int32Array(lvlInt + 1);
    const sumG = new Int32Array(lvlInt + 1);
    const sumB = new Int32Array(lvlInt + 1);

    // Pre-calculate grayscale intensity mapping mapping to speed up the sliding window logic
    const intensityMap = new Int32Array(width * height);
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        const lum = r * 0.299 + g * 0.587 + b * 0.114;
        intensityMap[i / 4] = Math.round((lum * lvlInt) / 255);
    }

    // Yield back to the browser temporarily so UI doesn't lock completely before loop
    await new Promise(resolve => setTimeout(resolve, 0)); 

    // Apply the Oil Painting Algorithm (Kuwahara / Paint Filter variation)
    // Works extremely well for stylistic "Painted" looks by smoothing specific blocks and retaining edges
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            
            // Clear buckets for the current pixel
            intensityCount.fill(0);
            sumR.fill(0);
            sumG.fill(0);
            sumB.fill(0);

            let maxIntensityCount = 0;
            let maxCountIntensityVal = 0;

            const yMin = Math.max(0, y - rInt);
            const yMax = Math.min(height - 1, y + rInt);
            const xMin = Math.max(0, x - rInt);
            const xMax = Math.min(width - 1, x + rInt);

            for (let wy = yMin; wy <= yMax; wy++) {
                const rowOffset = wy * width;
                for (let wx = xMin; wx <= xMax; wx++) {
                    const pixelIdx = rowOffset + wx;
                    const curIntensity = intensityMap[pixelIdx];

                    const dataIdx = pixelIdx * 4;
                    
                    intensityCount[curIntensity]++;
                    sumR[curIntensity] += data[dataIdx];
                    sumG[curIntensity] += data[dataIdx + 1];
                    sumB[curIntensity] += data[dataIdx + 2];

                    if (intensityCount[curIntensity] > maxIntensityCount) {
                        maxIntensityCount = intensityCount[curIntensity];
                        maxCountIntensityVal = curIntensity;
                    }
                }
            }

            const outIdx = (y * width + x) * 4;
            // Record the average color of the most frequently occurring intensity around this pixel
            outData[outIdx] = sumR[maxCountIntensityVal] / maxIntensityCount;
            outData[outIdx + 1] = sumG[maxCountIntensityVal] / maxIntensityCount;
            outData[outIdx + 2] = sumB[maxCountIntensityVal] / maxIntensityCount;
            outData[outIdx + 3] = data[outIdx + 3]; // Retain original alpha
        }
    }

    // Apply newly formulated pixels back onto the canvas instance
    ctx.putImageData(new ImageData(outData, width, height), 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

This tool transforms standard photographs into a stylized artistic look reminiscent of face painting or oil paintings. By utilizing a Kuwahara-style smoothing algorithm, it blends colors within local regions while preserving important edges, creating a vibrant, painterly effect. Users can enhance the artistic quality by adjusting saturation and contrast to achieve more vivid tones. This tool is ideal for creating unique social media avatars, digital art assets, or adding a playful, creative flair to personal portraits.

Leave a Reply

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