Please bookmark this page to avoid losing your image tool!

Image Light Leak 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.
function processImage(originalImg, leakColor1Str = "255,80,30,0.45", leakColor2Str = "255,200,50,0.35", leakPosition = "random", leakSizePercent = 70, numLeaksParam = 3, blendModeParam = "lighter") {
    // 1. Create canvas and context
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    // Ensure originalImg dimensions are available
    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    if (imgWidth === 0 || imgHeight === 0) {
        // console.error("Image has zero dimensions. It might not be loaded properly.");
        // Return a small, empty, or specific error-indicating canvas
        canvas.width = 1; 
        canvas.height = 1;
        return canvas;
    }

    canvas.width = imgWidth;
    canvas.height = imgHeight;

    // 2. Helper to parse color strings "r,g,b,a" to "rgba(r,g,b,a)"
    function parseRgbaColor(colorStr, defaultColor) {
        const parts = colorStr.split(',').map(s => s.trim());
        if (parts.length !== 4) {
            // console.warn(`Invalid color string format: "${colorStr}". Using default: ${defaultColor}`);
            return defaultColor;
        }
        const r = parseInt(parts[0], 10);
        const g = parseInt(parts[1], 10);
        const b = parseInt(parts[2], 10);
        const a = parseFloat(parts[3]);

        if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a)) {
            // console.warn(`Invalid color values in: "${colorStr}". Using default: ${defaultColor}`);
            return defaultColor;
        }
        
        const clampedR = Math.max(0, Math.min(255, r));
        const clampedG = Math.max(0, Math.min(255, g));
        const clampedB = Math.max(0, Math.min(255, b));
        const clampedA = Math.max(0, Math.min(1, a));
        
        return `rgba(${clampedR},${clampedG},${clampedB},${clampedA})`;
    }

    const parsedLeakColor1 = parseRgbaColor(leakColor1Str, "rgba(255,80,30,0.45)");
    const parsedLeakColor2 = parseRgbaColor(leakColor2Str, "rgba(255,200,50,0.35)");

    // 3. Draw original image
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    // 4. Prepare for applying light leaks
    const numLeaks = Math.max(0, Math.floor(numLeaksParam));
    if (numLeaks === 0) { // If no leaks, return canvas with original image
        return canvas;
    }

    // Validate and set blend mode
    const finalBlendMode = blendModeParam.toLowerCase();
    // Common and effective blend modes for light effects
    const allowedBlendModes = ["lighter", "screen", "overlay", "color-dodge", "add"]; 
    // Note: "add" is not standard Canvas GCO, but "lighter" is often similar to "add" or "linear dodge (add)"
    if (allowedBlendModes.includes(finalBlendMode)) {
      ctx.globalCompositeOperation = finalBlendMode;
    } else {
      // console.warn(`Invalid or less common blendMode "${blendModeParam}". Defaulting to "lighter".`);
      ctx.globalCompositeOperation = "lighter";
    }
    
    const w = canvas.width;
    const h = canvas.height;
    const diagonal = Math.sqrt(w * w + h * h);
    const baseRadius = (Math.max(0, Math.min(150, leakSizePercent)) / 100) * diagonal; // Allow up to 150% for very large leaks

    const validPositions = ["top", "bottom", "left", "right", "top-left", "top-right", "bottom-left", "bottom-right"];
    let effectiveLeakPositionStrategy = leakPosition.toLowerCase();

    if (effectiveLeakPositionStrategy !== "random" && !validPositions.includes(effectiveLeakPositionStrategy)) {
        // console.warn(`Invalid leakPosition "${leakPosition}" provided. Defaulting to "random" strategy for leaks.`);
        effectiveLeakPositionStrategy = "random";
    }

    for (let i = 0; i < numLeaks; i++) {
        let currentActualLeakPos;
        if (effectiveLeakPositionStrategy === "random") {
            currentActualLeakPos = validPositions[Math.floor(Math.random() * validPositions.length)];
        } else {
            currentActualLeakPos = effectiveLeakPositionStrategy;
        }

        // Randomize size and shape of each leak
        const outerRadius = baseRadius * (Math.random() * 0.7 + 0.3); // 30% to 100% of baseRadius
        const innerRadius = outerRadius * (Math.random() * 0.4);    // 0% to 40% of outerRadius (smaller innerRadius = harder edge)

        let cx, cy;
        // offScreenFactor determines how far off-canvas the center of the leak can be (as a fraction of its radius)
        // A value of 0 means center is on edge, >0 means center is off-canvas.
        const offScreenFactor = Math.random() * 0.4; 

        switch (currentActualLeakPos) {
            case "top":
                cx = w * Math.random();
                cy = -outerRadius * offScreenFactor;
                break;
            case "bottom":
                cx = w * Math.random();
                cy = h + outerRadius * offScreenFactor;
                break;
            case "left":
                cx = -outerRadius * offScreenFactor;
                cy = h * Math.random();
                break;
            case "right":
                cx = w + outerRadius * offScreenFactor;
                cy = h * Math.random();
                break;
            case "top-left":
                cx = -outerRadius * offScreenFactor;
                cy = -outerRadius * offScreenFactor;
                break;
            case "top-right":
                cx = w + outerRadius * offScreenFactor;
                cy = -outerRadius * offScreenFactor;
                break;
            case "bottom-left":
                cx = -outerRadius * offScreenFactor;
                cy = h + outerRadius * offScreenFactor;
                break;
            case "bottom-right":
                cx = w + outerRadius * offScreenFactor;
                cy = h + outerRadius * offScreenFactor;
                break;
        }
        
        const gradient = ctx.createRadialGradient(cx, cy, innerRadius, cx, cy, outerRadius);
        
        // Alternate primary/secondary colors for leaks, or randomize
        const c1 = (Math.random() < 0.5) ? parsedLeakColor1 : parsedLeakColor2;
        const c2 = (c1 === parsedLeakColor1) ? parsedLeakColor2 : parsedLeakColor1;

        gradient.addColorStop(0, c1); 
        // Mid color stop position, making leaks varied (e.g. soft or more defined)
        gradient.addColorStop(Math.max(0.1, Math.min(0.7, Math.random() * 0.5 + 0.1)), c2); 
        gradient.addColorStop(1, "rgba(0,0,0,0)"); // Fade to fully transparent

        ctx.fillStyle = gradient;
        
        // Draw a circle filled with the gradient. The gradient is defined relative to this circle.
        ctx.beginPath();
        ctx.arc(cx, cy, outerRadius, 0, 2 * Math.PI);
        ctx.fill();
    }

    // 5. Reset composite operation to default
    ctx.globalCompositeOperation = 'source-over';

    // 6. Return the canvas
    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 Leak Filter Application allows users to apply artistic light leak effects to their images. This tool simulates natural light leaks, enhancing photographs by adding random bursts of color and light that can give a vintage or dreamy aesthetic. Users can customize the leak colors, positions, sizes, and blending modes to achieve the desired artistic effect. This tool is particularly useful for photographers, designers, and social media users looking to enhance their visuals with unique light effects.

Leave a Reply

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