Please bookmark this page to avoid losing your image tool!

Image 35mm Film Camera 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,
    grainAmount = 0.15,       // 0 to 1 (0 = no grain, 0.15 = typical, 1 = very coarse)
    desaturationAmount = 0.2, // 0 to 1 (0 = original saturation, 1 = grayscale)
    sepiaAmount = 0.3,        // 0 to 1 (0 = no sepia, 1 = full sepia tint)
    contrastAmount = 1.1,     // 0.5 to 2.0 (1 = original contrast, >1 more, <1 less)
    vignetteAmount = 0.6,     // 0 to 1 (0 = no vignette, 1 = strong vignette darkening edges)
    lightLeakStrength = 0.25, // 0 to 1 (0 = no leaks, 1 = strong, more opaque leaks)
    softnessAmount = 0.1      // 0 to 1 (0 = sharp, maps to ~0-1px blur for softness)
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

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

    // 1. Initial Draw (with optional softness/blur)
    let initialDrawFilter = '';
    // Clamp softnessAmount to a reasonable range for blur (e.g., 0-1 for 0-1px)
    const softAmount = Math.max(0, Math.min(1, softnessAmount)); 
    if (softAmount > 0) {
        const blurRadius = softAmount * 1.0; // Max 1px blur for softnessAmount=1
        initialDrawFilter += `blur(${blurRadius.toFixed(1)}px) `;
    }

    if (initialDrawFilter) {
        ctx.filter = initialDrawFilter.trim();
    }
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    ctx.filter = 'none'; // Reset filter

    // Get image data for pixel manipulation
    let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    let data = imageData.data;

    // 2. Per-pixel effects: Desaturation, Sepia, Grain
    const applyPixelOps = grainAmount > 0 || desaturationAmount > 0 || sepiaAmount > 0;
    if (applyPixelOps) {
        const desat = Math.max(0, Math.min(1, desaturationAmount));
        const sep = Math.max(0, Math.min(1, sepiaAmount));
        const grain = Math.max(0, Math.min(1, grainAmount));

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

            // --- Desaturation ---
            if (desat > 0) {
                const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Luminance
                r = r * (1 - desat) + gray * desat;
                g = g * (1 - desat) + gray * desat;
                b = b * (1 - desat) + gray * desat;
            }

            // --- Sepia ---
            if (sep > 0) {
                const current_r = r;
                const current_g = g;
                const current_b = b;

                const sepiaR = current_r * 0.393 + current_g * 0.769 + current_b * 0.189;
                const sepiaG = current_r * 0.349 + current_g * 0.686 + current_b * 0.168;
                const sepiaB = current_r * 0.272 + current_g * 0.534 + current_b * 0.131;
                
                r = current_r * (1 - sep) + sepiaR * sep;
                g = current_g * (1 - sep) + sepiaG * sep;
                b = current_b * (1 - sep) + sepiaB * sep;
            }
            
            // --- Grain ---
            if (grain > 0) {
                // Noise intensity: grain=0.1 results in noise range of approx +/- 12.75
                const noise = (Math.random() - 0.5) * 255 * grain; 
                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. Contrast adjustment (globally, using CSS-like filter)
    const contrast = Math.max(0, contrastAmount); // Ensure contrast is not negative
    if (contrast !== 1.0) { 
        const tempCanvas = document.createElement('canvas');
        tempCanvas.width = canvas.width;
        tempCanvas.height = canvas.height;
        const tempCtx = tempCanvas.getContext('2d');
        tempCtx.drawImage(canvas, 0, 0); // Copy current main canvas to temp
        
        ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear main canvas
        ctx.filter = `contrast(${contrast * 100}%)`;
        ctx.drawImage(tempCanvas, 0, 0); // Draw temp canvas (with original content) back to main applying filter
        ctx.filter = 'none'; // Reset filter
    }

    // 4. Vignette
    const vigAmount = Math.max(0, Math.min(1, vignetteAmount));
    if (vigAmount > 0) {
        ctx.save();
        ctx.globalCompositeOperation = 'multiply'; 

        const centerX = canvas.width / 2;
        const centerY = canvas.height / 2;
        const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY);
        const innerRadius = outerRadius * (1 - vigAmount); 

        const gradient = ctx.createRadialGradient(
            centerX, centerY, innerRadius,
            centerX, centerY, outerRadius
        );

        const vignetteColorDarkness = Math.min(1.0, vigAmount * 1.2); 
        gradient.addColorStop(0, `rgba(0,0,0,0)`);
        gradient.addColorStop(1, `rgba(0,0,0,${vignetteColorDarkness})`);

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

    // 5. Light Leaks
    const effectiveLeakStrength = Math.max(0, Math.min(1, lightLeakStrength));
    if (effectiveLeakStrength > 0 && Math.random() < 0.65) { // Chance to apply leaks
        ctx.save();
        ctx.globalCompositeOperation = 'lighter'; 

        const numLeaks = Math.floor(Math.random() * 2) + 1; 
        for (let i = 0; i < numLeaks; i++) {
            const leakType = Math.random();
            const rCol = 180 + Math.random() * 75; 
            const gCol = 50 + Math.random() * 100;  
            const bCol = Math.random() * 50;       
            const alpha = (0.15 + Math.random() * 0.3) * effectiveLeakStrength;

            if (leakType < 0.6) { // Radial leak
                const x = Math.random() * canvas.width;
                const y = Math.random() * canvas.height;
                const r1 = Math.random() * Math.min(canvas.width, canvas.height) * 0.05;
                const r2 = r1 + (Math.random() * Math.min(canvas.width, canvas.height) * 0.3);

                const grad = ctx.createRadialGradient(x, y, r1, x, y, r2);
                grad.addColorStop(0, `rgba(${rCol}, ${gCol}, ${bCol}, ${alpha})`);
                grad.addColorStop(1, `rgba(${rCol}, ${gCol}, ${bCol}, 0)`);
                ctx.fillStyle = grad;
                ctx.beginPath();
                ctx.arc(x, y, r2, 0, Math.PI * 2);
                ctx.fill();
            } else { // Linear leak
                const x1 = Math.random() * canvas.width;
                const y1 = Math.random() * canvas.height;
                // Make x2, y2 somewhat distant and angled from x1, y1
                const angle = Math.random() * Math.PI * 2;
                const length = (Math.random() * 0.5 + 0.3) * Math.max(canvas.width, canvas.height); // 30-80% of max dimension
                const x2 = x1 + Math.cos(angle) * length;
                const y2 = y1 + Math.sin(angle) * length;
                
                const grad = ctx.createLinearGradient(x1, y1, x2, y2);
                grad.addColorStop(0, `rgba(${rCol}, ${gCol}, ${bCol}, ${alpha * 0.9})`);
                grad.addColorStop(0.5, `rgba(${Math.max(0,rCol-20)}, ${Math.max(0,gCol-20)}, ${bCol}, ${alpha * 0.5})`);
                grad.addColorStop(1, `rgba(${Math.max(0,rCol-40)}, ${Math.max(0,gCol-40)}, ${bCol}, 0)`);
                
                ctx.strokeStyle = grad;
                ctx.lineWidth = (Math.random() * 0.08 + 0.02) * Math.min(canvas.width, canvas.height) ; 
                ctx.beginPath();
                // Using the calculated x1,y1,x2,y2 directly for line is fine
                ctx.moveTo(x1, y1);
                ctx.lineTo(x2, y2);
                ctx.stroke();
            }
        }
        ctx.restore();
    }

    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 35mm Film Camera Filter Effect Tool allows users to apply a variety of vintage film effects to their images, simulating the look and feel of classic 35mm photography. Users can adjust parameters such as grain amount, desaturation, sepia tone, contrast, vignette, light leaks, and softness to create unique, stylistic images. This tool is perfect for photographers, graphic designers, and social media enthusiasts looking to enhance their visuals with nostalgic and artistic flair.

Leave a Reply

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