Please bookmark this page to avoid losing your image tool!

Image Super 8 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.
async function processImage(originalImg, grainAmount = 25, sepiaStrength = 0.35, vignetteStrength = 0.7, lightLeakOpacity = 0.15, scratchCount = 3) {
    // Ensure the image is loaded and has dimensions
    if (!originalImg.complete || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
        try {
            await new Promise((resolve, reject) => {
                // If already complete and valid, resolve immediately
                if (originalImg.complete && originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0) {
                    resolve();
                    return;
                }
                // If src is not even set, it's an error
                if (!originalImg.src) {
                    reject(new Error("Image source is not set."));
                    return;
                }
                originalImg.onload = resolve;
                originalImg.onerror = () => reject(new Error("Image failed to load (onerror triggered)."));
                // Check if src is a data URL and if it's potentially empty, which might not trigger onload/onerror
                if (originalImg.src.startsWith('data:') && originalImg.src.length < 100) { // Arbitrary small length
                     // This is a heuristic; a more robust check might be needed for specific empty data URLs
                     if (!originalImg.complete || originalImg.naturalWidth === 0) {
                          setTimeout(() => reject(new Error("Image source seems invalid or empty (data URL).")), 50);
                     }
                }
            });
        } catch (error) {
            console.error("Error loading image:", error.message);
            const errorCanvas = document.createElement('canvas');
            errorCanvas.width = Math.max(250, originalImg.width || 0); // Use originalImg.width if available
            errorCanvas.height = Math.max(60, originalImg.height || 0);
            const errCtx = errorCanvas.getContext('2d');
            if (errCtx) {
                errCtx.fillStyle = 'black';
                errCtx.fillRect(0,0, errorCanvas.width, errorCanvas.height);
                errCtx.fillStyle = 'red';
                errCtx.font = '12px Arial';
                errCtx.textAlign = 'center';
                errCtx.fillText("Error: Image could not be loaded.", errorCanvas.width/2, errorCanvas.height/2 - 10);
                errCtx.fillText(error.message.substring(0,100), errorCanvas.width/2, errorCanvas.height/2 + 10);
            }
            return errorCanvas;
        }
    }
    
    // Final check after attempting to load
    if (originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
        console.error("Image has zero dimensions after load attempt.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 250; errorCanvas.height = 30;
        const errCtx = errorCanvas.getContext('2d');
        if (errCtx){
             errCtx.fillStyle = 'red';
             errCtx.font = '12px Arial';
             errCtx.fillText("Error: Image has zero dimensions.", 5, 20);
        }
        return errorCanvas;
    }

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

    canvas.width = originalImg.naturalWidth;
    canvas.height = originalImg.naturalHeight;

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

    // 2. Get pixel data for sepia, grain
    let imageData;
    try {
        imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    } catch (e) {
        console.error("Could not get image data (tainted canvas? Cross-origin image without CORS headers):", e);
        // Draw an error message on the canvas as pixel manipulation is not possible
        ctx.fillStyle = 'rgba(128,128,128,0.8)'; // Semi-transparent grey overlay
        ctx.fillRect(0,0,canvas.width, canvas.height);
        ctx.fillStyle = 'white';
        ctx.strokeStyle = 'black';
        ctx.lineWidth = 2;
        ctx.font = `bold ${Math.min(24, canvas.width/15)}px Arial`;
        ctx.textAlign = 'center';
        const message = "Cannot apply effects: Cross-origin image.";
        ctx.strokeText(message, canvas.width/2, canvas.height/2);
        ctx.fillText(message, canvas.width/2, canvas.height/2);
        return canvas; // Return canvas with original image + error message
    }
    
    let data = imageData.data;

    // Apply Sepia tone and Grain
    for (let i = 0; i < data.length; i += 4) {
        let r = data[i];
        let g = data[i+1];
        let b = data[i+2];

        // Sepia
        if (sepiaStrength > 0) {
            const tr = 0.393 * r + 0.769 * g + 0.189 * b;
            const tg = 0.349 * r + 0.686 * g + 0.168 * b;
            const tb = 0.272 * r + 0.534 * g + 0.131 * b;
            
            r = r * (1 - sepiaStrength) + tr * sepiaStrength;
            g = g * (1 - sepiaStrength) + tg * sepiaStrength;
            b = b * (1 - sepiaStrength) + tb * sepiaStrength;
        }

        // Grain
        if (grainAmount > 0) {
            const noise = (Math.random() - 0.5) * grainAmount;
            r += noise;
            g += noise;
            b += noise;
        }
        
        data[i] = Math.max(0, Math.min(255, r));
        data[i+1] = Math.max(0, Math.min(255, g));
        data[i+2] = Math.max(0, Math.min(255, b));
    }
    ctx.putImageData(imageData, 0, 0);

    // 3. Light Leaks (draw on top)
    if (lightLeakOpacity > 0 && canvas.width > 0 && canvas.height > 0) {
        ctx.globalCompositeOperation = 'lighter'; 
        
        const numLeaks = Math.random() < 0.6 ? 1 : 2; // 60% chance 1 leak, 40% chance 2 leaks

        for (let k=0; k < numLeaks; k++) {
            // Position leaks towards edges/corners
            const edgeOrCorner = Math.random();
            let leakX, leakY;
            if (edgeOrCorner < 0.25) { // Top edge
                leakX = canvas.width * Math.random(); leakY = canvas.height * (Math.random()*0.1 - 0.05);
            } else if (edgeOrCorner < 0.5) { // Bottom edge
                leakX = canvas.width * Math.random(); leakY = canvas.height * (1 - Math.random()*0.1 + 0.05);
            } else if (edgeOrCorner < 0.75) { // Left edge
                leakX = canvas.width * (Math.random()*0.1 - 0.05); leakY = canvas.height * Math.random();
            } else { // Right edge
                leakX = canvas.width * (1 - Math.random()*0.1 + 0.05); leakY = canvas.height * Math.random();
            }

            const leakRadiusStart = Math.min(canvas.width, canvas.height) * (0.02 + Math.random() * 0.08);
            const leakRadiusEnd = Math.min(canvas.width, canvas.height) * (0.25 + Math.random() * 0.35);

            if (leakRadiusEnd <= leakRadiusStart) continue; 

            const leakGradient = ctx.createRadialGradient(
                leakX, leakY, leakRadiusStart,
                leakX, leakY, leakRadiusEnd
            );

            const rCol = 255;
            const gCol = 60 + Math.random() * 100; // Orange-ish to Yellow-ish
            const bCol = Math.random() * 40;       // Hint of red

            const currentOpacity = lightLeakOpacity * (0.5 + Math.random() * 0.5);
            leakGradient.addColorStop(0, `rgba(${rCol}, ${gCol}, ${bCol}, ${currentOpacity})`);
            leakGradient.addColorStop(0.6, `rgba(${rCol}, ${Math.min(255,gCol + 60)}, ${Math.min(255,bCol + 30)}, ${currentOpacity * 0.4})`);
            leakGradient.addColorStop(1, `rgba(${rCol}, ${Math.min(255,gCol + 120)}, ${Math.min(255,bCol + 60)}, 0)`);

            ctx.fillStyle = leakGradient;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }
        ctx.globalCompositeOperation = 'source-over'; 
    }

    // 4. Vignette (draw on top)
    if (vignetteStrength > 0 && canvas.width > 0 && canvas.height > 0) {
        const centerX = canvas.width / 2;
        const centerY = canvas.height / 2;
        const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY);
        
        const innerRadiusRatio = Math.max(0, 0.95 - vignetteStrength * 0.85);
        const innerRadius = outerRadius * innerRadiusRatio;
        
        // Only draw if the vignette has some perceivable effect
        if (outerRadius > innerRadius || vignetteStrength > 0.01) {
            const gradient = ctx.createRadialGradient(
                centerX, centerY, Math.max(0, innerRadius), // Ensure innerRadius is not negative
                centerX, centerY, outerRadius
            );
            
            gradient.addColorStop(0, 'rgba(0,0,0,0)');
            gradient.addColorStop(1, `rgba(0,0,0, ${Math.min(1, vignetteStrength * 0.95)})`);
            
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }
    }

    // 5. Scratches and Dust (draw on top)
    if (scratchCount > 0 && canvas.width > 0 && canvas.height > 0) {
        // Scratches
        ctx.strokeStyle = `rgba(200, 200, 200, ${0.1 + Math.random() * 0.15 * Math.min(1, scratchCount/2)})`; 
        ctx.lineWidth = Math.max(0.5, Math.min(1.5, (canvas.width + canvas.height) / 2000)); 

        for (let k = 0; k < scratchCount; k++) {
            const x = Math.random() * canvas.width;
            const yStart = Math.random() * canvas.height;
            
            const length = (Math.random() * 0.4 + 0.05) * canvas.height; // 5-45% of height
            // Angle mostly vertical, but can be varied more
            const angle = (Math.random() - 0.5) * (Math.PI / 2); // from -45 to +45 deg from vertical

            const xEnd = x + Math.sin(angle) * length;
            let yEnd = yStart + Math.cos(angle) * length;
            if (Math.random() < 0.5) yEnd = yStart - Math.cos(angle) * length;


            ctx.beginPath();
            ctx.moveTo(x, yStart);
            ctx.lineTo(xEnd, yEnd);
            ctx.stroke();
        }
        
        // Dust specks
        const dustParticleCount = Math.floor(scratchCount * (2 + Math.random() * 5)); 
        ctx.fillStyle = `rgba(190, 190, 190, ${0.15 + Math.random() * 0.2 * Math.min(1, scratchCount/2)})`; 
        for (let k = 0; k < dustParticleCount; k++) {
            const dustX = Math.random() * canvas.width;
            const dustY = Math.random() * canvas.height;
            const dustRadius = Math.random() * 1.0 + 0.3; 
            ctx.beginPath();
            ctx.arc(dustX, dustY, dustRadius, 0, Math.PI * 2);
            ctx.fill();
        }
    }
    
    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 Super 8 Film Filter Effect Tool allows users to transform their images by applying a vintage Super 8 film effect. This tool simulates classic film characteristics by adding sepia tones, grain, light leaks, vignetting, and scratches, creating a nostalgic and artistic appearance. It is useful for photographers, content creators, and social media users who want to enhance their images with a retro look, ideal for artistic projects, nostalgic themes, or simply to add a unique filter to their photos.

Leave a Reply

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

Other Image Tools:

Image Anamorphic Lens Flare Filter Effect Tool

Image Prism Photography Filter Effect Tool

Image Freelensing Effect Creator

Image Tiffen Glimmerglass Filter Effect Tool

Image Mamiya RZ67 Medium Format Filter Effect Tool

Image Wet Plate Collodion Filter Effect Tool

Image Ilford Pan F Plus 50 Filter Effect Tool

Image X-ray Photography Filter Effect Tool

Image Radial Graduated Filter Effect Tool

Image Lee 80A Cooling Filter Effect Application

Image Autochrome Lumière Filter Effect Tool

Photo Infrared 720nm Filter Effect Tool

Image 10-Stop ND Filter Effect Tool

Photo Full Spectrum Filter Effect Tool

Image Motion Blur Filter Effect Tool

Image Panavision Film Look Filter Effect Tool

Image Rolleiflex TLR Camera Filter Effect Tool

Image Lee 85B Warming Filter Effect Application

Image Tiffen Black Pro-Mist Filter Effect Tool

Image Fomapan 100 Filter Effect Application

Image Lens Flare Filter Effect Tool

Image Ilford XP2 Super Filter Effect Application

Image Cinemascope Filter Effect Applicator

Image Dubblefilm Solar Filter Effect Application

Image Night Vision Filter Effect Tool

Image Tintype Filter Effect Application

Image Color Graduated Filter Effect Tool

Image Agfa Vista Filter Effect Application

Image Schneider Hollywood Black Magic Filter Effect Tool

Image TMax 400 Filter Effect Tool

Image Double Exposure Filter Effect Tool

Image Fujichrome Velvia 50 Filter Effect Application

Image Revolog Texture Film Filter Effect Tool

Image Lomography Metropolis Filter Effect Application

Image Variable ND Filter Effect Tool

Image 8mm Movie Film Filter Effect Application

See All →