Please bookmark this page to avoid losing your image tool!

Image 8mm 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,
    blur = 0.3,         // number: blur radius in pixels, 0 for no blur
    sepia = 0.8,        // number: 0-1, intensity of sepia effect. 0 for original colors (still subject to grain etc.)
    grain = 20,         // number: 0-50 (approx), intensity of film grain. 0 for no grain.
    scratches = 0.5,    // number: 0-1, intensity/density of scratches. 0 for no scratches.
    dust = 0.5,         // number: 0-1, intensity/density of dust particles. 0 for no dust.
    vignette = 0.6      // number: 0-1, intensity of vignette darkening. 0 for no vignette.
) {
    // Ensure parameters are numbers
    const blurAmount = Number(blur);
    const sepiaIntensity = Math.max(0, Math.min(1, Number(sepia)));
    const grainAmount = Math.max(0, Number(grain));
    const scratchesIntensity = Math.max(0, Math.min(1, Number(scratches)));
    const dustIntensity = Math.max(0, Math.min(1, Number(dust)));
    const vignetteIntensity = Math.max(0, Math.min(1, Number(vignette)));

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

    const width = originalImg.naturalWidth || originalImg.width || 0;
    const height = originalImg.naturalHeight || originalImg.height || 0;
    canvas.width = width;
    canvas.height = height;

    if (width === 0 || height === 0) {
        // Return an empty canvas if image dimensions are zero
        return canvas;
    }

    // --- Step 1: Initial slight blur (if any) ---
    if (blurAmount > 0) {
        ctx.filter = `blur(${blurAmount}px)`;
    }
    ctx.drawImage(originalImg, 0, 0, width, height);
    ctx.filter = 'none'; // Reset filter for subsequent drawing operations

    // --- Step 2: Pixel Manipulation for Sepia and Grain ---
    if (sepiaIntensity > 0 || grainAmount > 0) {
        let imageData = ctx.getImageData(0, 0, width, height);
        let 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];
            
            let finalR = r, finalG = g, finalB = b;

            // Apply Sepia
            if (sepiaIntensity > 0) {
                const sr = 0.393 * r + 0.769 * g + 0.189 * b;
                const sg = 0.349 * r + 0.686 * g + 0.168 * b;
                const sb = 0.272 * r + 0.534 * g + 0.131 * b;

                finalR = (1 - sepiaIntensity) * finalR + sepiaIntensity * sr;
                finalG = (1 - sepiaIntensity) * finalG + sepiaIntensity * sg;
                finalB = (1 - sepiaIntensity) * finalB + sepiaIntensity * sb;
            }
            
            // Apply Grain
            if (grainAmount > 0) {
                const noise = (Math.random() - 0.5) * grainAmount;
                finalR += noise;
                finalG += noise;
                finalB += noise;
            }

            data[i] = Math.max(0, Math.min(255, finalR));
            data[i + 1] = Math.max(0, Math.min(255, finalG));
            data[i + 2] = Math.max(0, Math.min(255, finalB));
        }
        ctx.putImageData(imageData, 0, 0);
    }

    // --- Step 3: Adding Scratches ---
    if (scratchesIntensity > 0) {
        // Adjust scratch count based on image size and intensity
        const numScratches = Math.floor((width / 80 + Math.random() * 2 + 2) * scratchesIntensity * 2); 
        for (let i = 0; i < numScratches; i++) {
            const x = Math.random() * width;
            const y1 = Math.random() * height * 0.2; // Start near top
            const y2 = height - (Math.random() * height * 0.2); // End near bottom
            
            const midY = (y1 + y2) / 2 + (Math.random() - 0.5) * height * 0.1; // Midpoint y-wobble
            const midX = x + (Math.random() - 0.5) * 10; // Midpoint x-wobble
            const xEnd = x + (Math.random() - 0.5) * 8; // End x-wobble

            ctx.beginPath();
            ctx.moveTo(x, y1 + (Math.random() - 0.5) * 10); // Start y jitter
            ctx.quadraticCurveTo(midX, midY, xEnd, y2 + (Math.random() - 0.5) * 10); // End y jitter
            
            const scratchOpacity = Math.random() * 0.15 * scratchesIntensity + 0.03; // Min opacity 0.03
            // Scratches are typically lighter
            ctx.strokeStyle = `rgba(230, 230, 230, ${Math.min(1, scratchOpacity)})`; 
            // Line width slightly influenced by intensity
            ctx.lineWidth = (Math.random() * 1.0 + 0.25) * (0.75 + scratchesIntensity * 0.5); 
            
            if (ctx.lineWidth > 0.01 && scratchOpacity > 0.001) {
                ctx.stroke();
            }
        }
    }

    // --- Step 4: Adding Dust Particles ---
    if (dustIntensity > 0) {
        const baseDustDensityFactor = 0.00012; // Slightly increased density
        const secondaryDustFactor = 0.025;   // Slightly increased for smaller images
        const numDustSpecks = Math.floor(width * height * baseDustDensityFactor * dustIntensity) + 
                              Math.floor(Math.sqrt(width * height) * secondaryDustFactor * dustIntensity);
        
        for (let i = 0; i < numDustSpecks; i++) {
            const x = Math.random() * width;
            const y = Math.random() * height;
            // Particle size slightly influenced by intensity
            const particleSize = (Math.random() * 1.2 + 0.5) * (0.75 + dustIntensity * 0.5); 
            const particleOpacity = (Math.random() * 0.35 * dustIntensity + 0.05); // Min opacity 0.05
            
            if (particleSize > 0.1 && particleOpacity > 0.01) {
                const colorVal = Math.random() > 0.4 ? 245 : 35; // Slightly more light dust
                ctx.fillStyle = `rgba(${colorVal}, ${colorVal}, ${colorVal}, ${Math.min(1, particleOpacity)})`;
                ctx.beginPath();
                ctx.arc(x, y, particleSize / 2, 0, Math.PI * 2); // Draw as small circles
                ctx.fill();
            }
        }
    }
    
    // --- Step 5: Adding Vignette ---
    if (vignetteIntensity > 0) {
        const centerX = width / 2;
        const centerY = height / 2;
        const cornerDist = Math.sqrt(centerX * centerX + centerY * centerY);
        
        // Vignette falloff starts gently from about 30% of center-to-corner distance
        const gradientInnerRadius = cornerDist * 0.3; 
        const gradientOuterRadius = cornerDist; 

        const gradient = ctx.createRadialGradient(centerX, centerY, gradientInnerRadius, centerX, centerY, gradientOuterRadius);
        // Start of darkening effect (at innerRadius)
        gradient.addColorStop(0, `rgba(0, 0, 0, ${vignetteIntensity * 0.1})`); 
        // Full darkening effect (at outerRadius/corners)
        gradient.addColorStop(1, `rgba(0, 0, 0, ${Math.min(0.9, vignetteIntensity * 0.85)})`); // Max vignette alpha capped

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

    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 8mm Film Filter Effect Tool allows users to give their photos a nostalgic and vintage film look reminiscent of 8mm movies. By applying a series of customizable effects, including blur, sepia tones, grain, scratches, dust particles, and vignetting, users can simulate the characteristics of aged film footage. This tool is ideal for photographers, graphic designers, and enthusiasts looking to add a classic touch to their images, creating a familiar aesthetic suitable for personal projects, social media sharing, or artistic presentations.

Leave a Reply

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