Please bookmark this page to avoid losing your image tool!

Photo Festive Filter Application

(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,
    filterType = "christmas", // "christmas", "newYear", "winter", "party"
    overlayDensity = 0.3,     // 0 (none) to 1 (dense)
    greetingText = "",
    greetingColor = "#FFFFFF",
    greetingFontFamily = "Arial", // Default to a web-safe font. Examples for festive: "Mountains of Christmas", "Pacifico", "Lobster"
    greetingFontSizeFactor = 0.08, // Factor of image height
    vignetteStrength = 0.3      // 0 (none) to 1 (strong)
) {
    // --- Helper Functions ---

    function hexToRgba(hex, alpha) {
        if (typeof hex !== 'string' || !hex || hex.charAt(0) !== '#') {
            // console.warn("Invalid hex color for hexToRgba:", hex, "- using black as default.");
            hex = '#000000'; // Default to black if hex is invalid
        }
        const r = parseInt(hex.slice(1, 3), 16) || 0;
        const g = parseInt(hex.slice(3, 5), 16) || 0;
        const b = parseInt(hex.slice(5, 7), 16) || 0;
        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    }

    function drawSimpleSnowflake(ctx, x, y, size, color) {
        ctx.beginPath();
        ctx.strokeStyle = color;
        ctx.lineWidth = Math.max(1, size / 7); // Adjust line width based on size

        const numSpokes = 6;
        for (let i = 0; i < numSpokes; i++) {
            const angle = (Math.PI * 2 / numSpokes) * i;
            
            // Main spoke
            ctx.moveTo(x, y);
            ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size);

            // Add "V" branches at points along the spoke (e.g. 60% and 85% of length)
            for (const R of [0.6, 0.85]) { // Relative positions on spoke for branches
                const branchSourceX = x + Math.cos(angle) * size * R;
                const branchSourceY = y + Math.sin(angle) * size * R;
                const branchLength = size * 0.20; // Length of each arm of the "V"
                const branchAngleOffset = Math.PI / 4; // Angle of arms relative to the spoke (45 degrees)

                // Branch 1 of V (points outwards)
                ctx.moveTo(branchSourceX, branchSourceY);
                ctx.lineTo(branchSourceX + Math.cos(angle + branchAngleOffset) * branchLength,
                           branchSourceY + Math.sin(angle + branchAngleOffset) * branchLength);
                
                // Branch 2 of V (points outwards)
                ctx.moveTo(branchSourceX, branchSourceY);
                ctx.lineTo(branchSourceX + Math.cos(angle - branchAngleOffset) * branchLength,
                           branchSourceY + Math.sin(angle - branchAngleOffset) * branchLength);
            }
        }
        ctx.stroke();
    }

    function drawSparkle(ctx, x, y, size, baseColorHex) {
       ctx.beginPath();
       const gradient = ctx.createRadialGradient(x, y, 0, x, y, size);
       gradient.addColorStop(0, hexToRgba(baseColorHex, 0.9)); 
       gradient.addColorStop(0.5, hexToRgba(baseColorHex, 0.7));
       gradient.addColorStop(1, hexToRgba(baseColorHex, 0));    // Fading edge
       ctx.fillStyle = gradient;
       ctx.arc(x, y, size, 0, 2 * Math.PI);
       ctx.fill();

       // Smaller, whiter core for extra "pop"
       const coreSize = size * Math.max(0.15, Math.random() * 0.35); 
       ctx.beginPath();
       const coreGradient = ctx.createRadialGradient(x, y, 0, x, y, coreSize);
       coreGradient.addColorStop(0, hexToRgba("#FFFFFF", 0.95));
       coreGradient.addColorStop(1, hexToRgba(baseColorHex, 0.3));
       ctx.fillStyle = coreGradient;
       ctx.arc(x, y, coreSize, 0, 2 * Math.PI);
       ctx.fill();
    }

    function drawConfetti(ctx, x, y, size, colorHex) {
        ctx.fillStyle = colorHex; // Full opacity for confetti
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate(Math.random() * Math.PI * 2); // Random rotation
        
        const aspect = 0.4 + Math.random() * 0.6; // from thin rectangle to almost square
        const confettoWidth = size * (0.7 + Math.random() * 0.6); // Vary width
        const confettoHeight = confettoWidth * aspect; // Vary height based on aspect
        
        ctx.fillRect(-confettoWidth / 2, -confettoHeight / 2, confettoWidth, confettoHeight);
        ctx.restore();
    }

    const googleFontMap = {
        "Mountains of Christmas": "url(https://fonts.gstatic.com/s/mountainsofchristmas/v19/3y9dx4hYx1gqIVMwdLsUSURA0upUmKcnVoH2cHXhfMQ.woff2)",
        "Pacifico": "url(https://fonts.gstatic.com/s/pacifico/v22/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2)",
        "Dancing Script": "url(https://fonts.gstatic.com/s/dancingscript/v24/If2cXTr6YS-zF4S-kcSWSVi_sxjsohD9F50Ruu7BMSo3Sup8.woff2)",
        "Lobster": "url(https://fonts.gstatic.com/s/lobster/v28/neILzCirqoswsqX9zo-mM5Ez.woff2)",
    };

    // --- Main Logic ---

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

    if (!ctx) {
        console.error("Canvas 2D context is not available.");
        // Create a fallback element if canvas is not supported
        const p = document.createElement('p');
        p.textContent = "Canvas not supported. Please use a modern browser.";
        return p;
    }

    // 1. Apply base image filter and draw image
    let imageFilterStyle = 'none';
    switch (filterType) {
        case "christmas":
            imageFilterStyle = 'saturate(1.1) sepia(0.08) brightness(1.02)';
            break;
        case "newYear":
            imageFilterStyle = 'saturate(1.2) contrast(1.05) brightness(1.03)';
            break;
        case "winter":
            imageFilterStyle = 'brightness(1.05) contrast(1.05)';
            break;
        case "party":
            imageFilterStyle = 'saturate(1.15) brightness(1.05) contrast(1.03)';
            break;
    }
    
    if (imageFilterStyle !== 'none' && typeof ctx.filter !== 'undefined') {
        ctx.filter = imageFilterStyle;
    }
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    if (typeof ctx.filter !== 'undefined') {
        ctx.filter = 'none'; // Reset filter for subsequent drawings
    }

    // 2. Apply specific color overlays (e.g., cool tint for Winter)
    if (filterType === "winter") {
        ctx.globalCompositeOperation = 'overlay'; // 'soft-light' or 'multiply' could also work
        ctx.fillStyle = 'rgba(200, 220, 255, 0.07)'; // Very subtle cool blue
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalCompositeOperation = 'source-over'; // Reset composite operation
    }

    // 3. Draw overlay particles
    if (overlayDensity > 0) {
        // Adjust particle count based on image area and density.
        // Denser for winter (more snowflakes), less for others.
        const baseParticleAreaDivisor = filterType === 'winter' ? 15000 : 25000;
        const numParticles = Math.floor((canvas.width * canvas.height / baseParticleAreaDivisor) * overlayDensity * 10);
        
        // Base particle size scales with average image dimension. min size 3px.
        const baseParticleSize = Math.max(3, (canvas.width + canvas.height) / 300); 

        const confettiColors = ["#FF6B6B", "#FFD93D", "#6BCB77", "#4D96FF", "#F7C8E0", "#B0E0E6", "#FFC0CB"];

        for (let i = 0; i < numParticles; i++) {
            const x = Math.random() * canvas.width;
            const y = Math.random() * canvas.height;
            const particleSize = baseParticleSize * (0.6 + Math.random() * 0.8); // Randomize size a bit

            switch (filterType) {
                case "christmas":
                    if (Math.random() < 0.8) { // 80% snowflakes
                        drawSimpleSnowflake(ctx, x, y, particleSize * 1.2, `rgba(255, 255, 255, ${0.7 + Math.random()*0.25})`);
                    } else { // 20% red/green sparkles
                        const sparkleColor = Math.random() < 0.5 ? "#E74C3C" : "#2ECC71"; // Festive red/green
                        drawSparkle(ctx, x, y, particleSize * 0.7, sparkleColor);
                    }
                    break;
                case "newYear":
                    if (Math.random() < 0.6) { // 60% confetti
                        drawConfetti(ctx, x, y, particleSize, confettiColors[Math.floor(Math.random() * confettiColors.length)]);
                    } else { // 40% gold/silver sparkles
                        const sparkleColor = Math.random() < 0.6 ? "#FFD700" : "#C0C0C0"; // Gold/Silver
                        drawSparkle(ctx, x, y, particleSize * 0.8, sparkleColor);
                    }
                    break;
                case "winter":
                    drawSimpleSnowflake(ctx, x, y, particleSize * 1.3, `rgba(255, 255, 255, ${0.55 + Math.random()*0.35})`); // Vary opacity
                    break;
                case "party":
                     if (Math.random() < 0.7) { // 70% confetti
                        drawConfetti(ctx, x, y, particleSize, confettiColors[Math.floor(Math.random() * confettiColors.length)]);
                    } else { // 30% multi-color sparkles
                        drawSparkle(ctx, x, y, particleSize * 0.7, confettiColors[Math.floor(Math.random() * confettiColors.length)]);
                    }
                    break;
                default: // Generic white sparkles for unknown/default filter types
                    drawSparkle(ctx, x, y, particleSize * 0.6, "#FFFFFF");
                    break;
            }
        }
    }

    // 4. Apply vignette
    if (vignetteStrength > 0) {
        const centerX = canvas.width / 2;
        const centerY = canvas.height / 2;
        // Inner radius of vignette area (clear area)
        const innerRadiusClearFactor = 1 - Math.min(0.95, vignetteStrength); // Smaller clear area for stronger vignette
        const innerRadius = Math.min(canvas.width, canvas.height) / 1.8 * innerRadiusClearFactor;
        const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY); // Extends to corners
        
        const gradient = ctx.createRadialGradient(centerX, centerY, innerRadius, centerX, centerY, outerRadius);
        gradient.addColorStop(0, `rgba(0,0,0,0)`); // Center is transparent
        gradient.addColorStop(1, `rgba(0,0,0, ${Math.min(1, vignetteStrength * 1.2)})`); // Edge is dark, cap alpha

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

    // 5. Load font and draw greeting text
    if (greetingText && greetingText.trim() !== "") {
        const selectedFontFamily = greetingFontFamily || "Arial"; 
        const fontUrl = googleFontMap[selectedFontFamily];

        if (fontUrl) { 
            // Check if font needs loading. Using 10px as common test size.
            if (!document.fonts.check(`10px "${selectedFontFamily}"`)) {
                try {
                    const font = new FontFace(selectedFontFamily, fontUrl);
                    await font.load();
                    document.fonts.add(font);
                } catch (e) {
                    console.warn(`Failed to load Google Font "${selectedFontFamily}":`, e);
                    // Fallback (Arial, sans-serif below) will apply.
                }
            }
        }
        // If fontUrl is null, it's assumed to be a system/web-safe font.

        const actualFontSize = Math.max(16, Math.floor(canvas.height * greetingFontSizeFactor)); // Min 16px font
        ctx.font = `${actualFontSize}px "${selectedFontFamily}", Arial, sans-serif`; // CSS font string with fallbacks
        ctx.fillStyle = greetingColor;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'bottom'; // Align text bottom to y-coordinate

        // Add shadow for better readability
        ctx.shadowColor = 'rgba(0,0,0,0.65)';
        ctx.shadowBlur = Math.max(2, actualFontSize * 0.07); 
        ctx.shadowOffsetX = Math.max(1, actualFontSize * 0.03);
        ctx.shadowOffsetY = Math.max(1, actualFontSize * 0.03);

        // Position text near bottom center. Adjust vertical position slightly.
        const textMarginBottom = Math.max(10, canvas.height * 0.05); // 5% margin from bottom or 10px
        const textY = canvas.height - textMarginBottom;
        ctx.fillText(greetingText, canvas.width / 2, textY);

        // Reset shadow properties
        ctx.shadowColor = 'transparent'; // Or `rgba(0,0,0,0)`
        ctx.shadowBlur = 0;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 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

The Photo Festive Filter Application allows users to enhance their images by applying a variety of festive filters, such as ‘Christmas’, ‘New Year’, ‘Winter’, and ‘Party’. Users can also overlay custom greeting text with adjustable font properties and add decorative elements like snowflakes, confetti, and sparkles to create celebratory-themed images. This tool is ideal for crafting personalized holiday cards, social media posts, or digital invitations to festive events.

Leave a Reply

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