Please bookmark this page to avoid losing your image tool!

Image To Watercolor Painting 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, bleedIntensity = 15, saturation = 1.4, addPaperTexture = 1, addVignette = 1) {
    // Ensure accurate sizing from the original image element
    const width = originalImg.naturalWidth || originalImg.width;
    const height = originalImg.naturalHeight || originalImg.height;

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');

    // Scale effects proportionally to image size
    const sizeMax = Math.max(width, height);
    const baseFreq = 15 / sizeMax; 
    const blurLevel = 1.5 * (sizeMax / 1000); 
    const dispScale = Number(bleedIntensity) * (sizeMax / 1000);
    const sat = Number(saturation);

    // Generate unique ID for SVG filter to avoid collisions
    const filterId = "watercolor_" + Math.random().toString(36).substring(2, 9);

    // SVG Filter definition for watercolor spreading, bleeding, and enhanced color properties
    const svgStr = `
        <svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" style="position: absolute; width: 0; height: 0;">
            <filter id="${filterId}" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
                <!-- Soften image -->
                <feGaussianBlur in="SourceGraphic" stdDeviation="${blurLevel}" result="blur1" />
                
                <!-- Primary displacement for structural wobble (the watercolor wet bleed) -->
                <feTurbulence type="fractalNoise" baseFrequency="${baseFreq}" numOctaves="4" seed="5" result="turbulence1" />
                <feDisplacementMap in="blur1" in2="turbulence1" scale="${dispScale}" xChannelSelector="R" yChannelSelector="G" result="displaced1" />
                
                <!-- Secondary displacement for localized micro-texture irregularities -->
                <feTurbulence type="fractalNoise" baseFrequency="${baseFreq * 1.5}" numOctaves="2" seed="10" result="turbulence2" />
                <feDisplacementMap in="displaced1" in2="turbulence2" scale="${dispScale * 0.4}" xChannelSelector="R" yChannelSelector="B" result="displaced2" />

                <!-- Boost vibrancy/saturation -->
                <feColorMatrix type="saturate" values="${sat}" in="displaced2" result="saturated" />
                
                <!-- Slight brightness enhancement to mimic translucent watercolor pigment -->
                <feComponentTransfer in="saturated" result="final">
                    <feFuncR type="linear" slope="1.05"/>
                    <feFuncG type="linear" slope="1.05"/>
                    <feFuncB type="linear" slope="1.05"/>
                </feComponentTransfer>
            </filter>
        </svg>
    `;

    const container = document.createElement('div');
    container.innerHTML = svgStr;
    document.body.appendChild(container);

    try {
        // Yield to browser rendering cycle to register SVG filter in the DOM
        await new Promise(resolve => requestAnimationFrame(resolve));

        // 1. Establish the watercolor paper base tone (Off-white / Warm)
        ctx.fillStyle = "#F5F3EE";
        ctx.fillRect(0, 0, width, height);

        // 2. Draw image with native canvas filter linked to SVG ID
        ctx.filter = `url(#${filterId})`;
        ctx.drawImage(originalImg, 0, 0, width, height);

        // Clear out filter so overlays display cleanly
        ctx.filter = "none";
    } finally {
        // Safe DOM cleanup
        if (container.parentNode) {
            document.body.removeChild(container);
        }
    }

    // 3. Add procedural paper grain texture
    if (Number(addPaperTexture) === 1 || String(addPaperTexture).toLowerCase() === "true") {
        const tileSize = 512;
        const tCanvas = document.createElement('canvas');
        tCanvas.width = tileSize;
        tCanvas.height = tileSize;
        const tCtx = tCanvas.getContext('2d');
        const imgData = new ImageData(tileSize, tileSize);
        const data = imgData.data;

        // Generate organic paper monochrome variations mapped to warm subtle tints
        for (let i = 0; i < data.length; i += 4) {
            const val = 235 + (Math.random() * 40 - 20); // Fluctuates 215-255
            data[i]     = val;         // Red channel
            data[i + 1] = val - 1;     // Green channel (slight warm bias)
            data[i + 2] = val - 4;     // Blue channel (lowering blue creates warm cream/yellow)
            data[i + 3] = 255;         // Full alpha opacity
        }
        tCtx.putImageData(imgData, 0, 0);

        // Map tiling pattern so the rendering handles huge resolutions instantly
        const pattern = ctx.createPattern(tCanvas, 'repeat');
        
        ctx.globalCompositeOperation = "multiply"; 
        ctx.fillStyle = pattern;
        ctx.fillRect(0, 0, width, height);

        // Restore context operation
        ctx.globalCompositeOperation = "source-over";
    }

    // 4. Apply wash borders / vignette (Uneven fade to edge paper color)
    if (Number(addVignette) === 1 || String(addVignette).toLowerCase() === "true") {
        const cx = width / 2;
        const cy = height / 2;
        const radiusOut = sizeMax * 0.65;
        const radiusIn = Math.min(width, height) * 0.35;
        
        const grad = ctx.createRadialGradient(cx, cy, radiusIn, cx, cy, radiusOut);
        grad.addColorStop(0, "rgba(245, 243, 238, 0)");      // Fully transparent in focal center
        grad.addColorStop(0.65, "rgba(245, 243, 238, 0.4)"); // Starts fading out softly
        grad.addColorStop(1, "rgba(245, 243, 238, 1)");      // Merges solidly with outer paper

        ctx.fillStyle = grad;
        ctx.fillRect(0, 0, width, height);
    }

    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 artistic watercolor paintings by applying digital painting effects. It uses advanced algorithms to simulate the natural bleeding and spreading of pigments, enhances color saturation, and introduces organic textures. Users can achieve a realistic look by adding procedural paper grain and soft vignette borders that mimic the appearance of traditional watercolor on textured paper. This tool is ideal for creators looking to add an artistic flair to their photos for social media, digital scrapbooking, or unique greeting card designs.

Leave a Reply

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