Please bookmark this page to avoid losing your image tool!

Image Impressionist Painting Frame Creator

(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,
    brushSize = 10,        // Size of the brush stroke (diameter/width)
    strokeType = "circle",   // "circle", "rectangle", "ellipse"
    strokeAngleVariation = 30, // Max random angle for rect/ellipse strokes (degrees)
    gridDensityFactor = 0.8, // Grid step = brushSize * gridDensityFactor. Lower = denser strokes.
    jitterFactor = 0.4,      // 0 (no jitter) to 1 (max jitter relative to gridStep/2)
    frameTotalWidth = 50,    // Total width of one side of the frame
    frameOuterRatio = 0.6,   // Proportion of frameTotalWidth for the outer "wood" part
    frameColorOuter = "#A0522D",     // Main color for the outer "wood" frame (Sienna)
    frameColorOuterShade = "#8B4513",// Darker shade for wood frame (SaddleBrown)
    frameColorMat = "#F5F5DC"        // Color for the inner mat (Beige)
) {

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

    // Ensure parameters are numbers where expected
    brushSize = Number(brushSize) || 10;
    strokeAngleVariation = Number(strokeAngleVariation) || 30;
    gridDensityFactor = Number(gridDensityFactor) || 0.8;
    jitterFactor = Number(jitterFactor) || 0.4;
    frameTotalWidth = Number(frameTotalWidth) || 0; // Default to 0 if not framing
    frameOuterRatio = Number(frameOuterRatio) || 0.6;


    const outerFrameActualWidth = Math.max(0, frameTotalWidth * frameOuterRatio);
    const matActualWidth = Math.max(0, frameTotalWidth * (1 - frameOuterRatio));

    canvas.width = originalImg.width + 2 * frameTotalWidth;
    canvas.height = originalImg.height + 2 * frameTotalWidth;

    // --- 1. Draw the Frame ---
    if (frameTotalWidth > 0) {
        // 1.A. Draw Outer "Wood" Part
        if (outerFrameActualWidth > 0) {
            ctx.fillStyle = frameColorOuter;
            // Fill the areas for the outer frame
            ctx.fillRect(0, 0, canvas.width, outerFrameActualWidth); // Top strip
            ctx.fillRect(0, canvas.height - outerFrameActualWidth, canvas.width, outerFrameActualWidth); // Bottom strip
            ctx.fillRect(0, outerFrameActualWidth, outerFrameActualWidth, canvas.height - 2 * outerFrameActualWidth); // Left strip
            ctx.fillRect(canvas.width - outerFrameActualWidth, outerFrameActualWidth, outerFrameActualWidth, canvas.height - 2 * outerFrameActualWidth); // Right strip

            // Shading for Outer "Wood" Part to give a slight 3D effect
            const outerEdgeShadeWidth = Math.max(1, Math.floor(outerFrameActualWidth * 0.08)); // For outermost boundary
            const innerEdgeShadeWidth = Math.max(1, Math.floor(outerFrameActualWidth * 0.05)); // For edge bordering the mat

            if (outerEdgeShadeWidth > 0) {
                ctx.fillStyle = frameColorOuterShade; // Darker shade
                // Darker bottom and right edges of the outermost frame boundary
                ctx.fillRect(0, canvas.height - outerEdgeShadeWidth, canvas.width, outerEdgeShadeWidth); // Bottom boundary
                ctx.fillRect(canvas.width - outerEdgeShadeWidth, 0, outerEdgeShadeWidth, canvas.height); // Right boundary
            }
            
            if (innerEdgeShadeWidth > 0) { // Shading on the inner edge of the outer frame
                ctx.fillStyle = frameColorOuterShade; // Darker shade implies shadow onto mat
                 // Top inner edge of outer frame
                ctx.fillRect(outerFrameActualWidth, outerFrameActualWidth, canvas.width - 2 * outerFrameActualWidth, innerEdgeShadeWidth);
                // Left inner edge of outer frame
                ctx.fillRect(outerFrameActualWidth, outerFrameActualWidth, innerEdgeShadeWidth, canvas.height - 2 * outerFrameActualWidth);
            }
        }

        // 1.B. Draw Mat Part
        if (matActualWidth > 0) {
            const matX = outerFrameActualWidth;
            const matY = outerFrameActualWidth;
            // Mat covers the area inside the outer frame up to the image
            const matOverallWidth = originalImg.width + 2 * matActualWidth;
            const matOverallHeight = originalImg.height + 2 * matActualWidth;

            ctx.fillStyle = frameColorMat;
            ctx.fillRect(matX, matY, matOverallWidth, matOverallHeight);

            // Inner shadow from Mat onto Image Area (simple version)
            // Shadow depth relative to mat width, capped for small mats
            const innerShadowDepth = Math.max(1, Math.floor(Math.min(matActualWidth * 0.15, 5))); 
            if (innerShadowDepth > 0) {
                ctx.fillStyle = 'rgba(0,0,0,0.25)'; // Slightly darker shadow
                 // Shadow cast by top edge of mat on image area (image starts at frameTotalWidth)
                ctx.fillRect(frameTotalWidth, frameTotalWidth, originalImg.width, innerShadowDepth);
                // Shadow cast by left edge of mat on image area
                ctx.fillRect(frameTotalWidth, frameTotalWidth, innerShadowDepth, originalImg.height);
            }
        }
    }
    
    // --- 2. Create Impressionistic Image ---
    const impressionisticCanvas = document.createElement('canvas');
    impressionisticCanvas.width = originalImg.width;
    impressionisticCanvas.height = originalImg.height;
    const impCtx = impressionisticCanvas.getContext('2d', { willReadFrequently: true });

    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = originalImg.width;
    tempCanvas.height = originalImg.height;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
    tempCtx.drawImage(originalImg, 0, 0);
    const imgData = tempCtx.getImageData(0, 0, originalImg.width, originalImg.height);
    const data = imgData.data;

    function getAverageColor(imgPixelData, startX, startY, sampleW, sampleH, sourceImgWidth, sourceImgHeight) {
        let r = 0, g = 0, b = 0, aSum = 0, count = 0;
        // Clamp sample coordinates and dimensions to image bounds
        const sX = Math.max(0, Math.floor(startX));
        const sY = Math.max(0, Math.floor(startY));
        const eX = Math.min(Math.floor(startX + sampleW), sourceImgWidth);
        const eY = Math.min(Math.floor(startY + sampleH), sourceImgHeight);

        for (let y = sY; y < eY; y++) {
            for (let x = sX; x < eX; x++) {
                const index = (y * sourceImgWidth + x) * 4;
                r += imgPixelData[index];
                g += imgPixelData[index + 1];
                b += imgPixelData[index + 2];
                aSum += imgPixelData[index + 3];
                count++;
            }
        }
        return count > 0 ? {
            r: Math.round(r / count),
            g: Math.round(g / count),
            b: Math.round(b / count),
            a: Math.round(aSum / count)
        } : { r: 0, g: 0, b: 0, a: 0 }; // Default to black transparent if no pixels sampled
    }

    const gridStep = Math.max(1, brushSize * gridDensityFactor);

    for (let y = 0; y < originalImg.height; y += gridStep) {
        for (let x = 0; x < originalImg.width; x += gridStep) {
            const currentGridCellCenterX = x + gridStep / 2;
            const currentGridCellCenterY = y + gridStep / 2;

            const avgColor = getAverageColor(data,
                currentGridCellCenterX - brushSize / 2,
                currentGridCellCenterY - brushSize / 2,
                brushSize, brushSize,
                originalImg.width, originalImg.height
            );

            if (avgColor.a < 10) continue; // Skip nearly transparent areas

            impCtx.fillStyle = `rgba(${avgColor.r},${avgColor.g},${avgColor.b},${avgColor.a / 255})`;
            
            const jitterMax = gridStep / 2 * jitterFactor;
            const jitterX = (Math.random() - 0.5) * 2 * jitterMax;
            const jitterY = (Math.random() - 0.5) * 2 * jitterMax;
            
            const strokeX = currentGridCellCenterX + jitterX;
            const strokeY = currentGridCellCenterY + jitterY;

            impCtx.save();
            impCtx.translate(strokeX, strokeY);

            if (strokeType === "rectangle" || strokeType === "ellipse") {
                const angle = (Math.random() - 0.5) * 2 * strokeAngleVariation * (Math.PI / 180);
                impCtx.rotate(angle);
            }

            const halfBrushSize = brushSize / 2;
            if (strokeType === "circle") {
                impCtx.beginPath();
                impCtx.arc(0, 0, halfBrushSize, 0, 2 * Math.PI);
                impCtx.fill();
            } else if (strokeType === "rectangle") {
                impCtx.fillRect(-halfBrushSize, -halfBrushSize / 1.5, brushSize, brushSize * 2 / 1.5);
            } else if (strokeType === "ellipse") {
                impCtx.beginPath();
                impCtx.ellipse(0, 0, halfBrushSize, halfBrushSize / 1.5, 0, 0, 2 * Math.PI);
                impCtx.fill();
            }
            impCtx.restore();
        }
    }

    // --- 3. Draw the Impressionistic Image onto the Main Canvas ---
    ctx.drawImage(impressionisticCanvas, frameTotalWidth, frameTotalWidth);
   
    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

The Image Impressionist Painting Frame Creator is an online tool that transforms standard images into impressionistic artwork, framed with a customizable design. Users can adjust parameters such as brush size, stroke type, and frame dimensions, allowing for a personalized artistic creation. This tool is ideal for artists, designers, or anyone looking to add an artistic flair to their images, whether for social media, presentations, or personal projects.

Leave a Reply

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