Please bookmark this page to avoid losing your image tool!

Image Light Leaked Film Filter Effect Tool

(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, leakColor1 = "rgba(255, 80, 30, 0.3)", leakColor2 = "rgba(255, 200, 50, 0.25)", leakIntensity = 0.8, grainAmount = 30, desaturationAmount = 0.25, vignetteStrength = 0.4, tintColor = "rgba(230, 200, 170, 0.1)") {

    // Helper function to parse RGBA color strings
    // Returns an object {r, g, b, a}
    function parseRgbaString(rgbaStr) {
        if (!rgbaStr || typeof rgbaStr !== 'string') return { r: 0, g: 0, b: 0, a: 0 };
        // Regex to capture R, G, B, and optional Alpha from rgba() or rgb() string
        const match = rgbaStr.toLowerCase().match(/rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*(\d*(?:\.\d+)?))?\)/);
        if (!match) {
            // Fallback for simple color names or hex (not fully supported, basic handling)
            // For this tool, we expect RGBA strings mostly.
            // A robust color parser would be needed for full CSS color support.
            // console.warn("Invalid or unsupported color string format:", rgbaStr);
            return { r: 0, g: 0, b: 0, a: 0 }; // Default to transparent black if parsing fails
        }
        return {
            r: parseInt(match[1], 10),
            g: parseInt(match[2], 10),
            b: parseInt(match[3], 10),
            a: match[4] !== undefined ? parseFloat(match[4]) : 1 // Default alpha to 1 if not specified (for rgb)
        };
    }

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

    // 1. Draw original image
    ctx.drawImage(originalImg, 0, 0, w, h);

    // 2. Apply Desaturation and Tint (if any)
    // These are applied pixel by pixel for precise control
    const parsedTint = parseRgbaString(tintColor);
    if (desaturationAmount > 0 || (parsedTint && parsedTint.a > 0)) {
        const imageData = ctx.getImageData(0, 0, w, h);
        const data = imageData.data;

        for (let i = 0; i < data.length; i += 4) {
            let r = data[i];
            let g = data[i+1];
            let b = data[i+2];

            // Apply desaturation
            if (desaturationAmount > 0) {
                const gray = 0.299 * r + 0.587 * g + 0.114 * b;
                const sat = 1 - desaturationAmount;
                r = r * sat + gray * desaturationAmount;
                g = g * sat + gray * desaturationAmount;
                b = b * sat + gray * desaturationAmount;
            }

            // Apply tint (alpha blending)
            if (parsedTint && parsedTint.a > 0) {
                const tintA = parsedTint.a;
                r = r * (1 - tintA) + parsedTint.r * tintA;
                g = g * (1 - tintA) + parsedTint.g * tintA;
                b = b * (1 - tintA) + parsedTint.b * tintA;
            }
            
            data[i] = Math.max(0, Math.min(255, Math.round(r)));
            data[i+1] = Math.max(0, Math.min(255, Math.round(g)));
            data[i+2] = Math.max(0, Math.min(255, Math.round(b)));
        }
        ctx.putImageData(imageData, 0, 0);
    }

    // 3. Add Light Leaks
    if (leakIntensity > 0 && (leakColor1 || leakColor2)) {
        ctx.globalCompositeOperation = 'lighter'; // Blending mode for light effects
        const numLeaks = Math.floor(Math.random() * 3) + 2; // Generate 2 to 4 leaks

        for (let i = 0; i < numLeaks; i++) {
            const useColor1 = Math.random() < 0.6;
            const colorStr = useColor1 ? leakColor1 : (leakColor2 || leakColor1); // Fallback to leakColor1 if leakColor2 is null/empty
            
            const parsedLeakColor = parseRgbaString(colorStr);
            if (parsedLeakColor.a === 0) continue; // Skip if base color is transparent

            const r = parsedLeakColor.r;
            const g = parsedLeakColor.g;
            const b = parsedLeakColor.b;
            let a = parsedLeakColor.a * leakIntensity; // Modulate alpha by overall intensity

            if (a <= 0) continue; // Skip if effectively transparent after intensity modulation

            const currentLeakColorWithIntensity = `rgba(${r},${g},${b},${a})`;
            const transparentLeakColor = `rgba(${r},${g},${b},0)`;

            let cx, cy, rOuter;
            const edge = Math.floor(Math.random() * 4); // 0:top, 1:right, 2:bottom, 3:left
            const extentFactor = Math.random() * 0.6 + 0.5; // How far it extends (50%-110% of dimension)

            switch(edge) {
                case 0: // Top
                    cx = Math.random() * w;
                    cy = (Math.random() * 0.2 - 0.1) * h; // Centered around top edge
                    rOuter = (h * extentFactor) * (0.6 + Math.random() * 0.8); // Varying radius
                    break;
                case 1: // Right
                    cx = w * (1 - (Math.random() * 0.2 - 0.1)); // Centered around right edge
                    cy = Math.random() * h;
                    rOuter = (w * extentFactor) * (0.6 + Math.random() * 0.8);
                    break;
                case 2: // Bottom
                    cx = Math.random() * w;
                    cy = h * (1 - (Math.random() * 0.2 - 0.1)); // Centered around bottom edge
                    rOuter = (h * extentFactor) * (0.6 + Math.random() * 0.8);
                    break;
                case 3: // Left
                default:
                    cx = (Math.random() * 0.2 - 0.1) * w; // Centered around left edge
                    cy = Math.random() * h;
                    rOuter = (w * extentFactor) * (0.6 + Math.random() * 0.8);
                    break;
            }
            const rInnerRatio = Math.random() * 0.2 + 0.05; // Inner part of gradient (5%-25% of outer)

            const gradient = ctx.createRadialGradient(cx, cy, Math.max(0, rOuter * rInnerRatio), cx, cy, Math.max(1, rOuter));
            gradient.addColorStop(0, currentLeakColorWithIntensity);
            const midStop = Math.random() * 0.3 + 0.2; // Color holds for 20-50% of the gradient
            gradient.addColorStop(Math.min(1, midStop), currentLeakColorWithIntensity);
            gradient.addColorStop(1, transparentLeakColor);

            ctx.fillStyle = gradient;
            ctx.fillRect(0,0,w,h); // Apply gradient over the whole canvas
        }
        ctx.globalCompositeOperation = 'source-over'; // Reset composite operation
    }

    // 4. Apply Film Grain
    if (grainAmount > 0) {
        const imageData = ctx.getImageData(0, 0, w, h);
        const data = imageData.data;
        for (let i = 0; i < data.length; i += 4) {
            const noise = (Math.random() - 0.5) * grainAmount;
            // Apply monochrome noise to R, G, B channels
            data[i]   = Math.max(0, Math.min(255, Math.round(data[i]   + noise)));
            data[i+1] = Math.max(0, Math.min(255, Math.round(data[i+1] + noise)));
            data[i+2] = Math.max(0, Math.min(255, Math.round(data[i+2] + noise)));
            // Alpha channel (data[i+3]) is untouched
        }
        ctx.putImageData(imageData, 0, 0);
    }

    // 5. Apply Vignette
    if (vignetteStrength > 0) {
        const centerX = w / 2;
        const centerY = h / 2;
        const maxDim = Math.max(w,h);
        // Outer radius should typically cover the corners of the image
        const outerRadius = Math.sqrt(centerX*centerX + centerY*centerY);
        
        // r0: radius where the vignette effect starts (center is clear until this radius)
        // As vignetteStrength increases, r0 decreases, making the vignette encroach more.
        // The factor 0.7 controls how aggressively the vignette spreads with strength.
        // (1 - vignetteStrength * 0.7) means:
        // if strength=0, r0 = outerRadius (no vignette)
        // if strength=1, r0 = 0.3 * outerRadius (strong vignette)
        const r0 = outerRadius * (1 - Math.min(1, vignetteStrength) * 0.7); 
        
        const gradient = ctx.createRadialGradient(
            centerX, centerY, Math.max(0, r0), // Inner circle radius
            centerX, centerY, outerRadius        // Outer circle radius
        );
        
        gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Center of gradient is transparent
        // The darkness of the vignette edge is also controlled by vignetteStrength
        gradient.addColorStop(1, `rgba(0,0,0,${Math.min(1, vignetteStrength)})`);

        ctx.fillStyle = gradient;
        ctx.globalCompositeOperation = 'source-over'; // Apply on top
        ctx.fillRect(0, 0, w, h);
    }

    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 Light Leaked Film Filter Effect Tool allows users to enhance images by applying a vintage film aesthetic through light leaks, grain, vignette, and tint effects. This tool can be particularly useful for photographers and graphic designers looking to create artistic and nostalgic looks for their images. Use cases include enhancing personal photos for social media, creating unique visuals for marketing materials, or adding a creative touch to digital artwork.

Leave a Reply

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