Please bookmark this page to avoid losing your image tool!

Image To Paint 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, radius = 5, levels = 20) {
    // Parse parameters
    radius = parseInt(radius, 10);
    levels = parseInt(levels, 10);

    // Fallbacks for invalid inputs
    if (isNaN(radius) || radius < 1) radius = 5;
    if (isNaN(levels) || levels < 2) levels = 20;

    // Cap the resolution to prevent the browser from freezing on massive images
    const maxScalePixels = 1200 * 1200; 
    let width = originalImg.width;
    let height = originalImg.height;

    if (width * height > maxScalePixels) {
        const scale = Math.sqrt(maxScalePixels / (width * height));
        width = Math.floor(width * scale);
        height = Math.floor(height * scale);
    }

    // Set up canvas
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');
    
    // Draw and extract pixel data
    ctx.drawImage(originalImg, 0, 0, width, height);
    const imgData = ctx.getImageData(0, 0, width, height);
    const src = imgData.data;
    const outData = ctx.createImageData(width, height);
    const dst = outData.data;

    // Precalculate brightness intensities for faster processing
    const intensities = new Int32Array(width * height);
    for (let i = 0, j = 0; i < width * height; i++, j += 4) {
        const r = src[j], g = src[j + 1], b = src[j + 2];
        let intensity = Math.floor(((r + g + b) / 3) * levels / 256);
        if (intensity < 0) intensity = 0;
        if (intensity >= levels) intensity = levels - 1;
        intensities[i] = intensity;
    }

    return new Promise(resolve => {
        // Shared buffers to count local pixel densities
        const intensityCount = new Int32Array(levels);
        const sumR = new Int32Array(levels);
        const sumG = new Int32Array(levels);
        const sumB = new Int32Array(levels);

        let y = 0;
        
        // Chunk configuration to avoid blocking the main UI thread
        const pixelsPerChunk = 15000;
        const chunkHeight = Math.max(1, Math.floor(pixelsPerChunk / width));

        function processChunk() {
            const yEnd = Math.min(y + chunkHeight, height);

            for (; y < yEnd; y++) {
                for (let x = 0; x < width; x++) {
                    // Reset accumulators
                    intensityCount.fill(0);
                    sumR.fill(0);
                    sumG.fill(0);
                    sumB.fill(0);

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

                    let maxIntensityCount = 0;
                    let maxIdx = 0;

                    // Examine neighboring pixels
                    for (let ny = yMin; ny <= yMax; ny++) {
                        const rowOffset = ny * width;
                        for (let nx = xMin; nx <= xMax; nx++) {
                            const pixelIdx = rowOffset + nx;
                            const intensity = intensities[pixelIdx];

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

                            // Track the most frequent intensity grouping dynamically
                            if (intensityCount[intensity] > maxIntensityCount) {
                                maxIntensityCount = intensityCount[intensity];
                                maxIdx = intensity;
                            }
                        }
                    }

                    // Apply the average color of the most prominent intensity level
                    const outIdx = (y * width + x) * 4;
                    dst[outIdx] = sumR[maxIdx] / maxIntensityCount;
                    dst[outIdx + 1] = sumG[maxIdx] / maxIntensityCount;
                    dst[outIdx + 2] = sumB[maxIdx] / maxIntensityCount;
                    dst[outIdx + 3] = src[outIdx + 3]; // Preserve original opacity
                }
            }

            if (y < height) {
                // Yield execution briefly to keep UI responsive
                setTimeout(processChunk, 0);
            } else {
                // Complete
                ctx.putImageData(outData, 0, 0);
                resolve(canvas);
            }
        }

        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 photographs into artistic, paint-like images by applying a stylized smoothing effect. By analyzing local pixel intensities and color distributions within a specified radius, the converter replaces pixel colors with averaged tones to create a painterly texture. It is ideal for users looking to create artistic avatars, stylized social media posts, or unique digital art from their regular photos.

Leave a Reply

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