Please bookmark this page to avoid losing your image tool!

Photo Polaroid Frame Filter

(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,
    frameColor = "#FEFEFE",
    imagePaddingParam = "15",
    bottomBarHeightParam = "70",
    captionText = "",
    captionFont = "24px 'Caveat', cursive",
    captionColor = "#333333",
    shadowOpacityParam = "0.3",
    shadowBlurParam = "10",
    shadowOffsetXParam = "2",
    shadowOffsetYParam = "4"
) {
    let canvas = document.createElement('canvas');
    let ctx = canvas.getContext('2d');

    const errorCanvasPlaceholder = (width = 200, height = 100, message = "Error.") => {
        canvas.width = width;
        canvas.height = height;
        if (ctx) {
            ctx.fillStyle = 'lightcoral';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = 'black';
            ctx.font = '12px Arial';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(message, canvas.width / 2, canvas.height / 2, canvas.width - 20);
        }
        return canvas;
    };

    if (!originalImg) {
        console.error("Original image is null or undefined.");
        return errorCanvasPlaceholder(200, 100, "Original image not provided.");
    }

    if (!ctx) {
        console.error("Could not get 2D context from canvas.");
        const p = document.createElement('p');
        p.textContent = 'Canvas Context2D not supported. Cannot process image.';
        return p; // Fallback to a paragraph element if canvas context fails
    }
    
    const imagePadding = Number(imagePaddingParam);
    const bottomBarHeight = Number(bottomBarHeightParam);
    const shadowOpacity = Number(shadowOpacityParam);
    const shadowBlur = Number(shadowBlurParam);
    const shadowOffsetX = Number(shadowOffsetXParam);
    const shadowOffsetY = Number(shadowOffsetYParam);

    const numParams = [imagePadding, bottomBarHeight, shadowOpacity, shadowBlur, shadowOffsetX, shadowOffsetY];
    if (numParams.some(isNaN)) {
        console.error("Invalid numerical parameter provided. Check values for padding, height, or shadow properties.");
        return errorCanvasPlaceholder(originalImg.naturalWidth || 200, (originalImg.naturalHeight || 100) + 50, "Invalid numeric param for Polaroid.");
    }

    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    if (!originalImg.complete || imgWidth === 0 || imgHeight === 0) {
        console.error("Image is not fully loaded, or its dimensions are zero.");
        return errorCanvasPlaceholder(200, 100, "Image not loaded or has zero dimensions.");
    }
    
    // Font loading for caption
    let finalCaptionFont = captionFont;
    if (captionText && captionText.trim() !== "") {
        let fontFamily = "sans-serif"; // Default fallback
        const fontParts = captionFont.match(/^\s*(?:(?:(italic|oblique|normal)\s+)?(?:(bold|bolder|lighter|[1-9]00)\s+)?([\d\.]+px(?:\/[\d\.]+px)?)\s+)?(?:'([^']+)'|"([^"]+)"|([a-zA-Z\s-]+))(?:\s*,.*)?$/i);
        
        if (fontParts) {
            fontFamily = (fontParts[4] || fontParts[5] || fontParts[6] || "sans-serif").trim();
        } else {
            // Fallback for very simple font strings like "Arial"
            const simpleMatch = captionFont.split(',')[0].trim().match(/[^0-9\s"'][a-zA-Z\s-]+/);
            if (simpleMatch) fontFamily = simpleMatch[0].trim();
        }
        
        const webSafeFonts = ["arial", "verdana", "helvetica", "tahoma", "trebuchet ms", "times new roman", "georgia", "garamond", "courier new", "brush script mt", "cursive", "sans-serif", "serif", "monospace", "fantasy", "system-ui"];

        if (!webSafeFonts.includes(fontFamily.toLowerCase()) && !document.fonts.check(`12px "${fontFamily}"`)) {
            if (fontFamily.toLowerCase() === 'caveat') { // Specific handling for 'Caveat' font
                const GFONT_FAMILY_PARAM = encodeURIComponent(fontFamily).replace(/%20/g, '+');
                const GFONT_URL = `https://fonts.googleapis.com/css2?family=${GFONT_FAMILY_PARAM}&display=swap`;
                
                if (!document.querySelector(`link[href="${GFONT_URL}"]`)) {
                    const link = document.createElement('link');
                    link.href = GFONT_URL;
                    link.rel = 'stylesheet';
                    document.head.appendChild(link);
                    await new Promise((resolve) => { // Wait for CSS to load
                        link.onload = resolve;
                        link.onerror = () => { console.warn(`Failed to load stylesheet for ${fontFamily}.`); resolve(); };
                    });
                }
            }
            // Attempt to load the font for canvas regardless of specific handling
            try {
                await document.fonts.load(`12px "${fontFamily}"`);
            } catch (e) {
                console.warn(`Font "${fontFamily}" could not be loaded by document.fonts.load. Attempting to use fallback. Error:`, e);
                // Modify finalCaptionFont to use a fallback
                const currentFontFamilyRegex = new RegExp(`(['"]?)${fontFamily.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\1`, 'gi');
                finalCaptionFont = captionFont.replace(currentFontFamilyRegex, "sans-serif");
                if (!finalCaptionFont.toLowerCase().includes("sans-serif")) {
                     finalCaptionFont = captionFont.split(",")[0].trim() + ", sans-serif"; // More robust fallback
                }
            }
        }
    }

    canvas.width = imgWidth + 2 * imagePadding;
    canvas.height = imgHeight + imagePadding + bottomBarHeight;

    // 1. Draw the frame background
    ctx.fillStyle = frameColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 2. Set up shadow for the image part
    if (shadowOpacity > 0 && shadowBlur > 0) {
        ctx.shadowColor = `rgba(0, 0, 0, ${shadowOpacity})`;
        ctx.shadowBlur = shadowBlur;
        ctx.shadowOffsetX = shadowOffsetX;
        ctx.shadowOffsetY = shadowOffsetY;
    }

    // 3. Draw the image
    ctx.drawImage(originalImg, imagePadding, imagePadding, imgWidth, imgHeight);

    // 4. Clear shadow properties (so they don't affect subsequent drawings like text)
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;

    // 5. Draw caption text
    if (captionText && captionText.trim() !== "") {
        ctx.fillStyle = captionColor;
        ctx.font = finalCaptionFont; // Use the (potentially modified) font string
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle'; 
        
        const textY = (imagePadding + imgHeight) + (bottomBarHeight / 2);
        // Provide a maximum width for the text, slightly less than the available bottom bar width
        const maxTextWidth = canvas.width - imagePadding; // Simple max width
        ctx.fillText(captionText, canvas.width / 2, textY, maxTextWidth);
    }

    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 Photo Polaroid Frame Filter is an online tool that allows users to enhance their images by applying a classic Polaroid-style frame. Users can customize various parameters such as frame color, image padding, and bottom bar height, as well as add a caption with specific font and color options. This tool is perfect for those looking to create nostalgic photo prints, improve social media posts, or design personalized gifts. With features like shadow effects and customizable text, users can add a unique touch to their images with ease.

Leave a Reply

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