Please bookmark this page to avoid losing your image tool!

Image To Ken Sugimori Pokemon Watercolor 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.
function processImage(originalImg, lineColor = "#3e3838", paperTexture = 1, highlightThreshold = 200, saturation = 0.8) {
    const width = originalImg.width;
    const height = originalImg.height;
    
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');
    
    // Parse line color
    const hexToRgb = hex => {
        let c;
        if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){
            c = hex.substring(1).split('');
            if(c.length === 3){
                c = [c[0], c[0], c[1], c[1], c[2], c[2]];
            }
            c = '0x' + c.join('');
            return [(c >> 16) & 255, (c >> 8) & 255, c & 255];
        }
        return [62, 56, 56]; // fallback slightly dark brown
    };
    const lcRgb = hexToRgb(lineColor);
    
    highlightThreshold = Number(highlightThreshold);
    saturation = Number(saturation);
    paperTexture = Number(paperTexture);
    
    // 1. Base Colors (Blur, Desaturate, Contrast)
    const baseCanvas = document.createElement('canvas');
    baseCanvas.width = width;
    baseCanvas.height = height;
    const baseCtx = baseCanvas.getContext('2d');
    
    // Apply filters for color bleeding and saturation tweaks
    baseCtx.filter = `blur(1.5px) saturate(${saturation}) contrast(1.1)`;
    baseCtx.drawImage(originalImg, 0, 0);
    
    const baseData = baseCtx.getImageData(0, 0, width, height);
    const bPixels = baseData.data;
    
    for(let i = 0; i < bPixels.length; i += 4) {
        const a = bPixels[i+3];
        if (a === 0) continue; 
        
        const r = bPixels[i];
        const g = bPixels[i+1];
        const b = bPixels[i+2];
        const lum = 0.299 * r + 0.587 * g + 0.114 * b;
        
        if (lum > highlightThreshold) {
            // Emulate Ken Sugimori's signature large white painted highlights
            const factor = Math.min((lum - highlightThreshold) / 15, 1);
            bPixels[i] = r + (255 - r) * factor;
            bPixels[i+1] = g + (255 - g) * factor;
            bPixels[i+2] = b + (255 - b) * factor;
        } else {
            // Soft posterize to give distinct flat painted watercolor patches
            const levels = 5;
            const pr = Math.floor(r / (256/levels)) * (256/levels) + (256/levels)/2;
            const pg = Math.floor(g / (256/levels)) * (256/levels) + (256/levels)/2;
            const pb = Math.floor(b / (256/levels)) * (256/levels) + (256/levels)/2;
            
            bPixels[i] = r * 0.4 + pr * 0.6;
            bPixels[i+1] = g * 0.4 + pg * 0.6;
            bPixels[i+2] = b * 0.4 + pb * 0.6;
        }
    }
    baseCtx.putImageData(baseData, 0, 0);
    
    // 2. Paper Texture Generation
    if (paperTexture === 1) {
        const noiseCanvas = document.createElement('canvas');
        noiseCanvas.width = width;
        noiseCanvas.height = height;
        const nCtx = noiseCanvas.getContext('2d');
        
        // Low-frequency watercolor blotches
        const smallW = Math.max(1, Math.ceil(width / 4));
        const smallH = Math.max(1, Math.ceil(height / 4));
        const sCanvas = document.createElement('canvas');
        sCanvas.width = smallW;
        sCanvas.height = smallH;
        const sCtx = sCanvas.getContext('2d');
        const sData = sCtx.createImageData(smallW, smallH);
        for(let i = 0; i < sData.data.length; i += 4) {
            const val = 180 + Math.random() * 75;
            sData.data[i] = val;
            sData.data[i+1] = val;
            sData.data[i+2] = val;
            sData.data[i+3] = 255;
        }
        sCtx.putImageData(sData, 0, 0);
        
        nCtx.filter = 'blur(4px)';
        nCtx.drawImage(sCanvas, 0, 0, smallW, smallH, 0, 0, width, height);
        
        // High-frequency paper grain
        const gCanvas = document.createElement('canvas');
        gCanvas.width = width;
        gCanvas.height = height;
        const gCtx = gCanvas.getContext('2d');
        const gData = gCtx.createImageData(width, height);
        for(let i = 0; i < gData.data.length; i += 4) {
            const val = 230 + Math.random() * 25; 
            gData.data[i] = val;
            gData.data[i+1] = val;
            gData.data[i+2] = val;
            gData.data[i+3] = 255;
        }
        gCtx.putImageData(gData, 0, 0);
        
        nCtx.globalCompositeOperation = 'multiply';
        nCtx.filter = 'none';
        nCtx.drawImage(gCanvas, 0, 0);
        
        // Mask Noise Canvas using BaseCanvas alpha to not texture fully transparent regions
        const maskedNoiseCanvas = document.createElement('canvas');
        maskedNoiseCanvas.width = width;
        maskedNoiseCanvas.height = height;
        const mnCtx = maskedNoiseCanvas.getContext('2d');
        mnCtx.drawImage(noiseCanvas, 0, 0);
        mnCtx.globalCompositeOperation = 'destination-in';
        mnCtx.drawImage(baseCanvas, 0, 0);
        
        // Apply the masked texture over the base layer
        baseCtx.globalCompositeOperation = 'multiply';
        baseCtx.drawImage(maskedNoiseCanvas, 0, 0);
        baseCtx.globalCompositeOperation = 'source-over'; // Reset
    }
    
    // 3. Edge Detection (Custom Multi-Channel Sobel)
    const edgeCanvas = document.createElement('canvas');
    edgeCanvas.width = width;
    edgeCanvas.height = height;
    const edgeCtx = edgeCanvas.getContext('2d');
    
    // Blur slightly so final detected edges have thickness, resembling ink strokes
    edgeCtx.filter = 'blur(1.5px)';
    edgeCtx.drawImage(originalImg, 0, 0);
    const edgeImgData = edgeCtx.getImageData(0, 0, width, height);
    const edges = new Uint8Array(width * height);
    const gray = new Uint8Array(width * height);
    const alphaMap = new Uint8Array(width * height);
    
    for (let i = 0; i < width * height; i++) {
        const idx = i * 4;
        // Grayscale map
        gray[i] = 0.299 * edgeImgData.data[idx] + 0.587 * edgeImgData.data[idx + 1] + 0.114 * edgeImgData.data[idx + 2];
        // Alpha map
        alphaMap[i] = edgeImgData.data[idx + 3];
    }
    
    const kernelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
    const kernelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
    
    for (let y = 1; y < height - 1; y++) {
        for (let x = 1; x < width - 1; x++) {
            let pxC = 0, pyC = 0;
            let pxA = 0, pyA = 0;
            for (let cy = -1; cy <= 1; cy++) {
                for (let cx = -1; cx <= 1; cx++) {
                    const idx = ((y + cy) * width + (x + cx));
                    const wX = kernelX[(cy + 1) * 3 + (cx + 1)];
                    const wY = kernelY[(cy + 1) * 3 + (cx + 1)];
                    pxC += gray[idx] * wX;
                    pyC += gray[idx] * wY;
                    pxA += alphaMap[idx] * wX;
                    pyA += alphaMap[idx] * wY;
                }
            }
            const magC = Math.sqrt(pxC * pxC + pyC * pyC);
            const magA = Math.sqrt(pxA * pxA + pyA * pyA);
            // Allow outlining colors or transparency transitions (guarantees sticker-like outline)
            const mag = Math.max(magC, magA);
            edges[y * width + x] = mag > 25 ? mag : 0;
        }
    }
    
    const finalEdgeData = edgeCtx.createImageData(width, height);
    for (let i = 0; i < width * height; i++) {
        const mag = edges[i];
        const idx = i * 4;
        let alpha = (mag - 25) / (80 - 25);
        if (alpha > 1) alpha = 1;
        if (alpha < 0) alpha = 0;
        
        finalEdgeData.data[idx] = lcRgb[0];
        finalEdgeData.data[idx+1] = lcRgb[1];
        finalEdgeData.data[idx+2] = lcRgb[2];
        finalEdgeData.data[idx+3] = alpha * 255;
    }
    edgeCtx.putImageData(finalEdgeData, 0, 0);
    
    // Filter edge canvas again to make the lines visually flow smoothly
    const smoothEdgeCanvas = document.createElement('canvas');
    smoothEdgeCanvas.width = width;
    smoothEdgeCanvas.height = height;
    const seCtx = smoothEdgeCanvas.getContext('2d');
    seCtx.filter = 'blur(0.5px)';
    seCtx.drawImage(edgeCanvas, 0, 0);
    // Draw twice for punchy lineart that emulates slightly erratic inking
    seCtx.drawImage(edgeCanvas, 0, 0);
    
    // 4. Composite Everything Together
    ctx.drawImage(baseCanvas, 0, 0);
    ctx.drawImage(smoothEdgeCanvas, 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 your images into a stylized watercolor illustration reminiscent of classic Pokémon character art. It applies a series of artistic filters including color desaturation, soft posterization for a painted look, and the addition of signature bright white highlights. The process also introduces a paper texture effect and organic-looking ink outlines to emulate hand-drawn watercolor sketches. This tool is ideal for fans of retro anime aesthetics, digital artists looking for inspiration, or anyone wanting to turn personal photos and character designs into nostalgic, stylized artwork.

Leave a Reply

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