Please bookmark this page to avoid losing your image tool!

Photo To Hand Painted Effect 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, brushSize = 4, smoothness = 20) {
    // Parse the parameters, falling back to defaults if invalid
    let radius = parseInt(brushSize, 10) || 4;
    let intensityLevels = parseInt(smoothness, 10) || 20;

    // Cap values to prevent unreasonable processing times or array out-of-bounds
    radius = Math.max(1, Math.min(radius, 15));
    intensityLevels = Math.max(1, Math.min(intensityLevels, 255));

    const canvas = document.createElement('canvas');
    const width = originalImg.width;
    const height = originalImg.height;
    canvas.width = width;
    canvas.height = height;
    
    const ctx = canvas.getContext('2d');
    
    // Increase saturation and contrast slightly before applying the effect.
    // This makes the final oil-painting effect colors pop beautifully like fresh paint.
    ctx.filter = 'saturate(130%) contrast(110%)';
    ctx.drawImage(originalImg, 0, 0, width, height);
    ctx.filter = 'none';

    // Retrieve the raw image data to apply the Oil Painting (Kuwahara/Intensity Binning) algorithm
    const imgData = ctx.getImageData(0, 0, width, height);
    const data = imgData.data;
    const outData = new Uint8ClampedArray(data.length);

    // Pre-allocate intensity buckets for performance
    const intensityCount = new Int32Array(intensityLevels + 1);
    const intensityR = new Int32Array(intensityLevels + 1);
    const intensityG = new Int32Array(intensityLevels + 1);
    const intensityB = new Int32Array(intensityLevels + 1);

    // Wrapping in a Promise to allow async yielding
    return new Promise((resolve) => {
        let y = 0;
        
        // Chunk sizing adaptive to the radius.
        // A larger brush radius requires exponentially more CPU cycles per pixel.
        // Processing in chunks keeps the browser's UI thread from freezing.
        const chunkHeight = Math.max(2, Math.floor(150 / radius));

        function processChunk() {
            const endY = Math.min(height, y + chunkHeight);
            
            for (; y < endY; y++) {
                let destIdx = y * width * 4;
                
                for (let x = 0; x < width; x++) {
                    
                    // Reset intensity buckets for the current pixel
                    for (let c = 0; c <= intensityLevels; c++) {
                        intensityCount[c] = 0;
                        intensityR[c] = 0;
                        intensityG[c] = 0;
                        intensityB[c] = 0;
                    }

                    let maxIntensityCount = 0;
                    let maxIntensity = 0;

                    const startY = Math.max(0, y - radius);
                    const ey = Math.min(height - 1, y + radius);
                    const startX = Math.max(0, x - radius);
                    const ex = Math.min(width - 1, x + radius);

                    // Scan the local neighborhood for this pixel
                    for (let ny = startY; ny <= ey; ny++) {
                        let idx = (ny * width + startX) * 4;
                        for (let nx = startX; nx <= ex; nx++) {
                            const r = data[idx];
                            const g = data[idx + 1];
                            const b = data[idx + 2];
                            
                            // Calculate the brightness/intensity level (0 to intensityLevels)
                            const curIntensity = Math.floor((((r + g + b) / 3) * intensityLevels) / 255);
                            
                            intensityCount[curIntensity]++;
                            intensityR[curIntensity] += r;
                            intensityG[curIntensity] += g;
                            intensityB[curIntensity] += b;

                            // Keep track of the bin with the highest count
                            if (intensityCount[curIntensity] > maxIntensityCount) {
                                maxIntensityCount = intensityCount[curIntensity];
                                maxIntensity = curIntensity;
                            }
                            idx += 4;
                        }
                    }

                    // Set the output pixel color to the average color of the most dominant intensity bin
                    outData[destIdx] = intensityR[maxIntensity] / maxIntensityCount;
                    outData[destIdx + 1] = intensityG[maxIntensity] / maxIntensityCount;
                    outData[destIdx + 2] = intensityB[maxIntensity] / maxIntensityCount;
                    // Preserve the original alpha channel
                    outData[destIdx + 3] = data[destIdx + 3];
                    
                    destIdx += 4;
                }
            }

            if (y < height) {
                // If there are still rows left, yield to the browser and process the next chunk
                if (window.requestAnimationFrame) {
                    window.requestAnimationFrame(processChunk);
                } else {
                    setTimeout(processChunk, 0);
                }
            } else {
                // Done processing: draw the newly painted pixel data back to the canvas
                const outImgData = new ImageData(outData, width, height);
                ctx.putImageData(outImgData, 0, 0);
                resolve(canvas);
            }
        }

        // Start processing the first chunk
        processChunk();
    });
}

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 digital photographs into artistic images that mimic the appearance of hand-painted oil paintings. By applying a specialized smoothing algorithm and adjusting color saturation and contrast, it creates a painterly texture characterized by visible brushstroke-like effects. This tool is ideal for artists looking for inspiration, social media users wanting to create stylized profile pictures, or anyone interested in turning personal memories into unique digital art pieces.

Leave a Reply

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