Please bookmark this page to avoid losing your image tool!

Image Super 8 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, grainIntensity = 20, vignetteIntensity = 0.6, warmth = 0.6, showSprocketsStr = "true", lightLeakOpacity = 0.2, blurAmountPx = 0.5) {

    // Helper function to draw rounded rectangle Paths
    function drawRoundRectPath(ctx, x, y, width, height, radiusInput) {
        let radius = {tl: 0, tr: 0, br: 0, bl: 0};
        if (typeof radiusInput === 'number') {
            radius = {tl: radiusInput, tr: radiusInput, br: radiusInput, bl: radiusInput};
        } else {
            for (let side in radius) {
                radius[side] = radiusInput[side] || radius[side];
            }
        }
        
        ctx.beginPath();
        ctx.moveTo(x + radius.tl, y);
        ctx.lineTo(x + width - radius.tr, y);
        ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
        ctx.lineTo(x + width, y + height - radius.br);
        ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
        ctx.lineTo(x + radius.bl, y + height);
        ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
        ctx.lineTo(x, y + radius.tl);
        ctx.quadraticCurveTo(x, y, x + radius.tl, y);
        ctx.closePath();
    }
    
    // Helper function to fill rounded rectangles
    function fillRoundRect(ctx, x, y, width, height, radiusInput) {
        drawRoundRectPath(ctx, x, y, width, height, radiusInput);
        ctx.fill();
    }

    const _showSprockets = String(showSprocketsStr).toLowerCase() === "true";

    // 1. Create an offscreen canvas for image processing (filters, grain)
    const pCanvas = document.createElement('canvas');
    pCanvas.width = originalImg.naturalWidth || originalImg.width;
    pCanvas.height = originalImg.naturalHeight || originalImg.height;
    const pCtx = pCanvas.getContext('2d');

    // Clamp parameters to sensible ranges
    warmth = Math.max(0, Math.min(1, warmth));
    blurAmountPx = Math.max(0, blurAmountPx);
    grainIntensity = Math.max(0, grainIntensity);
    vignetteIntensity = Math.max(0, Math.min(1, vignetteIntensity));
    lightLeakOpacity = Math.max(0, Math.min(1, lightLeakOpacity));


    // Apply filters (warmth, blur, contrast, etc.)
    let filterOps = [];
    if (warmth > 0) { filterOps.push(`sepia(${warmth})`); }
    filterOps.push(`contrast(${1 + warmth * 0.25})`); // Contrast increases slightly with warmth
    filterOps.push(`brightness(${1 - warmth * 0.15})`); // Brightness decreases slightly
    filterOps.push(`saturate(${Math.max(0.1, 1 - warmth * 0.7)})`); // Desaturate more significantly with warmth
    if (warmth > 0.05) { filterOps.push(`hue-rotate(-${warmth * 20}deg)`); } // Shift to more orange/red
    if (blurAmountPx > 0) { filterOps.push(`blur(${blurAmountPx}px)`); }
    
    if (filterOps.length > 0) {
        pCtx.filter = filterOps.join(' ');
    }
    pCtx.drawImage(originalImg, 0, 0, pCanvas.width, pCanvas.height);
    pCtx.filter = 'none'; // Reset filter for subsequent operations like grain

    // Apply grain
    if (grainIntensity > 0) {
        const imageData = pCtx.getImageData(0, 0, pCanvas.width, pCanvas.height);
        const data = imageData.data;
        const len = data.length;
        // Scale grainIntensity: 0-50 maps to pixel adjustment range.
        // A value of 20 means noise up to +/-20 on R,G,B.
        const actualGrainAmount = grainIntensity; 

        for (let i = 0; i < len; i += 4) {
            const noise = (Math.random() - 0.5) * actualGrainAmount; 
            data[i]   = Math.max(0, Math.min(255, data[i]   + noise));
            data[i+1] = Math.max(0, Math.min(255, data[i+1] + noise));
            data[i+2] = Math.max(0, Math.min(255, data[i+2] + noise));
        }
        pCtx.putImageData(imageData, 0, 0);
    }

    // 2. Create the output canvas with film strip appearance
    const contentW = pCanvas.width;
    const contentH = pCanvas.height;

    // Define film strip padding and rounded corners for the image aperture effect
    const filmStripPaddingHorizontal = Math.max(8, Math.min(contentW, contentH) * 0.025);
    const filmStripPaddingVertical = Math.max(8, Math.min(contentW, contentH) * 0.025);
    const cornerRadius = Math.max(5, Math.min(contentW, contentH) * 0.03);
    
    // Define width for the sprocket holes area
    const sprocketAreaWidth = _showSprockets ? Math.max(25, contentW * 0.10) : 0; // Sprocket area relative to content width

    // Calculate final frame dimensions
    const frameWidth = contentW + 2 * filmStripPaddingHorizontal + sprocketAreaWidth;
    const frameHeight = contentH + 2 * filmStripPaddingVertical;

    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = frameWidth;
    outputCanvas.height = frameHeight;
    const oCtx = outputCanvas.getContext('2d');

    // Fill film base color (dark, slightly warm brown/grey)
    oCtx.fillStyle = '#100C08'; // Darkest brown, almost black
    oCtx.fillRect(0, 0, frameWidth, frameHeight);

    // Calculate image content position within the frame
    const imgX = (_showSprockets ? sprocketAreaWidth : 0) + filmStripPaddingHorizontal;
    const imgY = filmStripPaddingVertical;

    // Draw image content with rounded corners (aperture effect) and then apply vignette
    oCtx.save();
    drawRoundRectPath(oCtx, imgX, imgY, contentW, contentH, cornerRadius);
    oCtx.clip(); // Clip to the rounded rectangle image area
    oCtx.drawImage(pCanvas, imgX, imgY, contentW, contentH); // Draw the processed image

    // Vignette (applied inside the clipped image area)
    if (vignetteIntensity > 0) {
        const gradCenterX = imgX + contentW / 2;
        const gradCenterY = imgY + contentH / 2;
        const gradOuterRadius = Math.sqrt(contentW*contentW + contentH*contentH) / 2 * 1.1; // Ensure vignette covers corners
        const gradInnerRadiusFactor = 1 - Math.min(1, vignetteIntensity); // Inner clear area shrinks with intensity
        const gradInnerRadius = gradOuterRadius * Math.max(0.1, gradInnerRadiusFactor * 0.9 + 0.1);


        const vignetteGrad = oCtx.createRadialGradient(gradCenterX, gradCenterY, gradInnerRadius, gradCenterX, gradCenterY, gradOuterRadius);
        const midStop = Math.max(0.1, 0.5 + (0.5 - vignetteIntensity * 0.5)); // Adjusted for smoother transition
        vignetteGrad.addColorStop(0, 'rgba(5,0,0,0)'); // Slightly warm transparent center
        vignetteGrad.addColorStop(midStop, `rgba(5,0,0,${vignetteIntensity * 0.6})`);
        vignetteGrad.addColorStop(1, `rgba(5,0,0,${vignetteIntensity * 0.95})`); // Darker, slightly reddish vignette edges
        oCtx.fillStyle = vignetteGrad;
        oCtx.fillRect(imgX, imgY, contentW, contentH); // Fill over the image area
    }
    oCtx.restore(); // Remove clip, image and vignette are now on canvas

    // Sprocket holes (if enabled)
    if (_showSprockets) {
        const spPaddingFromEdge = filmStripPaddingHorizontal * 0.1; // Small space from frame edge
        const spAreaEffectiveX = spPaddingFromEdge;
        const spAvailableWidth = sprocketAreaWidth - 2 * spPaddingFromEdge;
        
        // Super 8 sprocket: ~0.91mm W x 1.22mm H. Aspect Ratio H/W ~ 1.34
        let spHeight = contentH * 0.022; 
        let spWidth = spHeight / 1.34; 

        if (spWidth > spAvailableWidth * 0.85) { // Ensure sprocket fits
            const scale = (spAvailableWidth * 0.85) / spWidth;
            spWidth *= scale;
            spHeight *= scale;
        }
        const spRadius = spWidth * 0.25; // Roundedness of sprockets

        const spGapFactor = 2.8; // Spacing factor: 2.8 * height = center-to-center distance
        const numSprockets = Math.floor(contentH / (spHeight * spGapFactor));
        const spColTotalHeight = numSprockets * spHeight * spGapFactor - spHeight * (spGapFactor - 1); // Calculated occupied height
        const spColStartY = imgY + (contentH - spColTotalHeight) / 2; // Center the column of sprockets relative to image

        oCtx.fillStyle = '#050301'; // Very dark, almost black for holes
        for (let i = 0; i < numSprockets; i++) {
            const currentSpY = spColStartY + i * (spHeight * spGapFactor);
            const currentSpX = spAreaEffectiveX + (spAvailableWidth - spWidth) / 2; // Center sprocket in its available width
            fillRoundRect(oCtx, currentSpX, currentSpY, spWidth, spHeight, spRadius);
        }
    }
    
    // Light Leaks (drawn last with 'lighter' composite op for additive effect)
    if (lightLeakOpacity > 0 && Math.random() < 0.45) { // 45% chance of a light leak
        oCtx.globalCompositeOperation = 'lighter';
        const numLeaks = 1; // For simplicity, one leak per image
        for (let i = 0; i < numLeaks; ++i) {
            const leakX = Math.random() * frameWidth;
            const leakY = Math.random() * frameHeight;
            const rMax = Math.min(frameWidth, frameHeight) * (Math.random() * 0.4 + 0.3); // Random radius
            const rMin = rMax * (Math.random() * 0.3 + 0.1); // Inner radius of gradient
           
            const leakGrad = oCtx.createRadialGradient(leakX, leakY, rMin, leakX, leakY, rMax);
            // Colors for light leaks: typically warm (red, orange, yellow)
            const R = 190 + Math.random() * 65; 
            const G = 100 + Math.random() * 100;
            const B = Math.random() * 60;
            const opacityFactor = Math.random() * 0.4 + 0.6; // Randomize opacity slightly
            leakGrad.addColorStop(0, `rgba(${R},${G},${B},${lightLeakOpacity * opacityFactor})`);
            leakGrad.addColorStop(1, `rgba(${R},${G},${B},0)`); // Fade to transparent
            
            oCtx.fillStyle = leakGrad;
            oCtx.beginPath(); // Ensure path is new for each leak
            oCtx.arc(leakX, leakY, rMax, 0, Math.PI * 2); // Circular leak shape
            oCtx.fill();
        }
        oCtx.globalCompositeOperation = 'source-over'; // Reset composite operation
    }

    return outputCanvas;
}

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 Filter Effect Tool allows users to apply a vintage film effect to their images, simulating the distinctive look and feel of Super 8 film. With customizable parameters, users can adjust grain intensity, vignette effect, warmth, and blur to create a nostalgic aesthetic reminiscent of classic home movies. This tool is ideal for photographers and creatives looking to enhance their visuals with a retro flair, perfect for social media posts, artistic projects, or personal photo collections.

Leave a Reply

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