Please bookmark this page to avoid losing your image tool!

Image 120 Film Format Filter Effect

(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,
    outputSize = 600,
    borderPercent = 4,
    borderStyle = "square", // "square" or "rounded"
    borderColor = "#0A0A0A",
    strength = 0.7,         // Overall effect strength (0-1)
    vignetteAmount = 0.7,   // Base vignette intensity (0-1)
    grainAmount = 0.3,      // Base grain intensity (0-1)
    sepiaAmount = 0.2,      // Base sepia intensity (0-1)
    saturationValue = 0.8,  // Saturation level (0-2, 1 is original)
    blurAmount = 0.3        // Base blur intensity (0-1, translates to small px value)
) {
    // 1. Calculate dimensions
    const finalCanvas = document.createElement('canvas');
    finalCanvas.width = outputSize;
    finalCanvas.height = outputSize;
    const finalCtx = finalCanvas.getContext('2d');

    const borderPx = Math.max(0, Math.floor(outputSize * (borderPercent / 100)));
    const contentAreaX = borderPx;
    const contentAreaY = borderPx;
    const contentAreaWidth = Math.max(1, outputSize - 2 * borderPx);
    const contentAreaHeight = Math.max(1, outputSize - 2 * borderPx);

    // 2. Create contentCanvas & draw original image scaled into it
    const contentCanvas = document.createElement('canvas');
    contentCanvas.width = contentAreaWidth;
    contentCanvas.height = contentAreaHeight;
    const contentCtx = contentCanvas.getContext('2d');

    const imgAR = originalImg.width / originalImg.height;
    const contentAR = contentAreaWidth / contentAreaHeight;

    let sImageW, sImageH, sImageX, sImageY; // Scaled image W, H, X, Y in contentCanvas
    if (imgAR > contentAR) { // Image is wider than content area (letterbox top/bottom)
        sImageW = contentAreaWidth;
        sImageH = sImageW / imgAR;
        sImageX = 0;
        sImageY = (contentAreaHeight - sImageH) / 2;
    } else { // Image is taller or same aspect as content area (pillarbox left/right)
        sImageH = contentAreaHeight;
        sImageW = sImageH * imgAR;
        sImageY = 0;
        sImageX = (contentAreaWidth - sImageW) / 2;
    }
    // Fill letterbox/pillarbox areas with black
    contentCtx.fillStyle = 'black';
    contentCtx.fillRect(0, 0, contentCanvas.width, contentCanvas.height);
    contentCtx.drawImage(originalImg, sImageX, sImageY, sImageW, sImageH);

    // 3. Create effectsCanvas for applying filters
    const effectsCanvas = document.createElement('canvas');
    effectsCanvas.width = contentAreaWidth;
    effectsCanvas.height = contentAreaHeight;
    const effectsCtx = effectsCanvas.getContext('2d');
    effectsCtx.drawImage(contentCanvas, 0, 0); // Start with the scaled image

    // 4. Apply image filters (Sepia, Saturation, Brightness, Contrast, Blur)
    // These filters apply to the source drawn, so we draw effectsCanvas onto itself (via temp)
    const tempFilterLayerCanvas = document.createElement('canvas');
    tempFilterLayerCanvas.width = effectsCanvas.width;
    tempFilterLayerCanvas.height = effectsCanvas.height;
    const tempFilterLayerCtx = tempFilterLayerCanvas.getContext('2d');
    tempFilterLayerCtx.drawImage(effectsCanvas, 0, 0); // Store current state of effectsCanvas

    let filterArray = [];
    const currentStrength = Math.max(0, Math.min(1, strength)); // Clamp strength 0-1

    if (sepiaAmount * currentStrength > 0.01) {
        filterArray.push(`sepia(${sepiaAmount * currentStrength})`);
    }
    if (Math.abs(saturationValue - 1) > 0.01) {
        filterArray.push(`saturate(${saturationValue})`);
    }
    
    // Subtle brightness/contrast adjustments typical of film
    let effectiveBrightness = 1 - (0.1 * currentStrength);
    let effectiveContrast = 1 + (0.1 * currentStrength);
    filterArray.push(`brightness(${effectiveBrightness.toFixed(3)})`);
    filterArray.push(`contrast(${effectiveContrast.toFixed(3)})`);

    if (blurAmount * currentStrength > 0.01) {
        let blurPx = (blurAmount * currentStrength * 1.5).toFixed(2); // Max ~1.5px blur
        if (parseFloat(blurPx) > 0.05) { // Avoid sub-pixel blur that does nothing
            filterArray.push(`blur(${blurPx}px)`);
        }
    }

    if (filterArray.length > 0) {
        effectsCtx.filter = filterArray.join(' ');
        effectsCtx.clearRect(0, 0, effectsCanvas.width, effectsCanvas.height);
        effectsCtx.drawImage(tempFilterLayerCanvas, 0, 0); // Draw with all filters applied
        effectsCtx.filter = 'none'; // Reset filter for subsequent operations
    }

    // 5. Apply Grain
    if (grainAmount * currentStrength > 0.01) {
        const grainPatternCanvas = document.createElement('canvas'); // Create a small canvas for grain pattern
        const grainPatternSize = 80;
        grainPatternCanvas.width = grainPatternSize;
        grainPatternCanvas.height = grainPatternSize;
        const gpCtx = grainPatternCanvas.getContext('2d');
        const gpId = gpCtx.createImageData(grainPatternSize, grainPatternSize);
        const gpData = gpId.data;
        
        const noiseIntensity = grainAmount * currentStrength * 70; // Controls the +/- range of pixel noise
        const grainSpotAlpha = (grainAmount * currentStrength * 0.4 + 0.1) * 255; // Alpha of individual grain spots

        for (let i = 0; i < gpData.length; i += 4) {
            const noiseVal = (Math.random() - 0.5) * noiseIntensity;
            gpData[i]   = 128 + noiseVal; // R
            gpData[i+1] = 128 + noiseVal; // G
            gpData[i+2] = 128 + noiseVal; // B
            gpData[i+3] = Math.random() * grainSpotAlpha; // Alpha - some spots more/less visible
        }
        gpCtx.putImageData(gpId, 0, 0);
        
        // Use a temporary canvas to hold the repeating grain pattern
        tempFilterLayerCtx.clearRect(0, 0, tempFilterLayerCanvas.width, tempFilterLayerCanvas.height);
        tempFilterLayerCtx.fillStyle = tempFilterLayerCtx.createPattern(grainPatternCanvas, 'repeat');
        tempFilterLayerCtx.fillRect(0, 0, tempFilterLayerCanvas.width, tempFilterLayerCanvas.height);

        // Blend the grain pattern onto the image
        effectsCtx.globalAlpha = 0.2 + grainAmount * currentStrength * 0.3; // Overall opacity of grain layer
        effectsCtx.globalCompositeOperation = 'overlay'; // 'soft-light' or 'hard-light' could also work
        effectsCtx.drawImage(tempFilterLayerCanvas, 0, 0);
        
        // Reset global settings
        effectsCtx.globalAlpha = 1.0;
        effectsCtx.globalCompositeOperation = 'source-over';
    }

    // 6. Apply Vignette
    if (vignetteAmount * currentStrength > 0.01) {
        const vigCenterX = effectsCanvas.width / 2;
        const vigCenterY = effectsCanvas.height / 2;
        const vigOuterRadius = Math.sqrt(Math.pow(vigCenterX, 2) + Math.pow(vigCenterY, 2));
        
        const vigEffectStrength = vignetteAmount * currentStrength;
        const vigInnerRadiusFactor = 1 - Math.min(0.95, vigEffectStrength); // Closer to 0 means vignette starts further from center
        const vigInnerRadius = vigOuterRadius * vigInnerRadiusFactor;

        const gradient = effectsCtx.createRadialGradient(
            vigCenterX, vigCenterY, vigInnerRadius,
            vigCenterX, vigCenterY, vigOuterRadius
        );
        
        const vigColorRgb = "20,15,10"; // Dark brownish base for vignette
        let vigOpacity = Math.min(0.85, vigEffectStrength * 1.1); // Max opacity for vignette edge

        gradient.addColorStop(0, `rgba(${vigColorRgb},0)`);
        gradient.addColorStop(0.7, `rgba(${vigColorRgb},${(vigOpacity * 0.65).toFixed(2)})`);
        gradient.addColorStop(1, `rgba(${vigColorRgb},${vigOpacity.toFixed(2)})`);
        
        effectsCtx.fillStyle = gradient;
        effectsCtx.fillRect(0, 0, effectsCanvas.width, effectsCanvas.height);
    }

    // 7. Draw to finalCanvas (background and processed image content)
    finalCtx.fillStyle = borderColor;
    finalCtx.fillRect(0, 0, finalCanvas.width, finalCanvas.height);

    if (borderStyle === "rounded" && borderPx > 0) { // Rounded corners only if there's a border
        finalCtx.save();
        const cornerRadius = Math.min(contentAreaWidth, contentAreaHeight) * 0.07; // 7% of content dimension for corner
        
        finalCtx.beginPath();
        finalCtx.moveTo(contentAreaX + cornerRadius, contentAreaY);
        finalCtx.lineTo(contentAreaX + contentAreaWidth - cornerRadius, contentAreaY);
        finalCtx.arcTo(contentAreaX + contentAreaWidth, contentAreaY, contentAreaX + contentAreaWidth, contentAreaY + cornerRadius, cornerRadius);
        finalCtx.lineTo(contentAreaX + contentAreaWidth, contentAreaY + contentAreaHeight - cornerRadius);
        finalCtx.arcTo(contentAreaX + contentAreaWidth, contentAreaY + contentAreaHeight, contentAreaX + contentAreaWidth - cornerRadius, contentAreaY + contentAreaHeight, cornerRadius);
        finalCtx.lineTo(contentAreaX + cornerRadius, contentAreaY + contentAreaHeight);
        finalCtx.arcTo(contentAreaX, contentAreaY + contentAreaHeight, contentAreaX, contentAreaY + contentAreaHeight - cornerRadius, cornerRadius);
        finalCtx.lineTo(contentAreaX, contentAreaY + cornerRadius);
        finalCtx.arcTo(contentAreaX, contentAreaY, contentAreaX + cornerRadius, contentAreaY, cornerRadius);
        finalCtx.closePath();
        finalCtx.clip();
    }

    finalCtx.drawImage(effectsCanvas, contentAreaX, contentAreaY, contentAreaWidth, contentAreaHeight);

    if (borderStyle === "rounded" && borderPx > 0) {
        finalCtx.restore(); // Remove clipping path
    }
    
    return finalCanvas;
}

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 120 Film Format Filter Effect tool allows users to apply a vintage film-like effect to their images. It offers various customization options, including the amount of sepia tone, saturation, grain, blur, and vignette effects. This tool can be particularly useful for photographers, graphic designers, and hobbyists looking to recreate the nostalgic aesthetic of 120 film photography. Users can also adjust the size of the final image and choose between square or rounded borders, enhancing the presentation of their images for social media or personal projects.

Leave a Reply

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

Other Image Tools:

Image Lens Whacking Filter Effect Tool

Image Black and White Red Filter Effect Tool

Image Lee Medium Stopper 6-Stop ND Filter Effect Tool

Image Nikon F3 Film Camera Render Effect Tool

Image Polaroid Spectra Filter Effect Tool

Image Contax T2/T3 Filter Effect Application

Image Bronica ETRS Medium Format Filter Effect Application

Image Soap Bubble Bokeh Effect Generator

Image Center Graduated ND Filter Effect Tool

Image Breakthrough Photography X4 ND Filter Effect

Photo Filter Effect Creator for Yashica T4 Point-and-Shoot

Image AGFA APX 25 Film Filter Effect Tool

Image Singh-Ray Gold-N-Blue Polarizer Effect Tool

Image Black and White Blue Filter Effect Tool

Image Pinhole Solargraphy Effect Creator

Image Kodak Vision3 500T Motion Picture Film Effect Simulator

Image Soft Focus Filter Effect for Nikon Nikkor

Image Bergger Pancro 400 Film Filter Effect Tool

Image Agfa Optima Filter Effect Application

Image Technicolor 3-Strip Process Filter Effect

Image Cyanotype Process Filter Effect

Image Black and White with Orange #21 Filter Effect Tool

Image Bleach Bypass Effect Filter

Image IMAX Camera Filter Effect Tool

Image Super 8 Film Filter Effect Tool

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

See All →