Please bookmark this page to avoid losing your image tool!

Image 35mm Half-frame Camera 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, 
    eachFrameMaxHeight = 400, 
    borderColor = "black", 
    borderWidth = 15, 
    cornerRadiusFraction = 0.05, 
    grainAmount = 0.08, 
    sepiaAmount = 0.25, 
    saturationAmount = 0.9, 
    vignetteStrength = 0.3
) {

    // Helper function for creating a rounded rectangle path
    function createRoundedRectPath(ctx, x, y, width, height, radius) {
        ctx.beginPath();
        ctx.moveTo(x + radius, y);
        ctx.lineTo(x + width - radius, y);
        ctx.arcTo(x + width, y, x + width, y + radius, radius);
        ctx.lineTo(x + width, y + height - radius);
        ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
        ctx.lineTo(x + radius, y + height);
        ctx.arcTo(x, y + height, x, y + height - radius, radius);
        ctx.lineTo(x, y + radius);
        ctx.arcTo(x, y, x + radius, y, radius);
        ctx.closePath();
    }

    // Ensure originalImg is valid and has dimensions
    if (!originalImg || typeof originalImg.width !== 'number' || typeof originalImg.height !== 'number' || originalImg.width === 0 || originalImg.height === 0) {
        console.error("Original image is invalid or has zero dimensions.");
        // Create a small canvas indicating an error
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 200;
        errorCanvas.height = 100;
        const errCtx = errorCanvas.getContext('2d');
        if (errCtx) { // Check if context was obtainable (e.g. not in Node.js without canvas lib)
            errCtx.fillStyle = 'rgb(200, 200, 200)';
            errCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
            errCtx.fillStyle = 'red';
            errCtx.textAlign = 'center';
            errCtx.textBaseline = 'middle';
            errCtx.fillText("Error: Invalid Image Input", errorCanvas.width / 2, errorCanvas.height / 2);
        }
        return errorCanvas;
    }

    // 1. Calculate frame content dimensions
    // Use originalImg.height as basis for scaling, capped by eachFrameMaxHeight
    const frameContentRenderHeight = Math.max(1, Math.min(originalImg.height, eachFrameMaxHeight));
    const halfFrameAspectRatio = 3 / 4; // Width / Height for typical portrait half-frame
    const frameContentRenderWidth = Math.max(1, frameContentRenderHeight * halfFrameAspectRatio);

    // 2. Calculate overall canvas dimensions
    const canvas = document.createElement('canvas');
    // Total width: 2 frames + 3 borders (left, middle, right)
    canvas.width = Math.max(1, (2 * frameContentRenderWidth) + (3 * borderWidth));
    // Total height: 1 frame height + 2 borders (top, bottom)
    canvas.height = Math.max(1, frameContentRenderHeight + (2 * borderWidth));
    const ctx = canvas.getContext('2d');
    
    if (!ctx) { // Fallback if canvas context cannot be created
        console.error("Could not get 2D context from canvas");
        return document.createElement('div'); // Return empty div or throw error
    }


    // 3. Fill background with borderColor
    ctx.fillStyle = borderColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 4. Calculate source image crop ('cover' style) for the 3:4 aspect ratio frames
    let sx, sy, sWidth, sHeight;
    const originalImgAspectRatio = originalImg.width / originalImg.height;

    if (originalImgAspectRatio > halfFrameAspectRatio) {
        // Image is wider than frame aspect ratio: fit height, crop width
        sHeight = originalImg.height;
        sWidth = originalImg.height * halfFrameAspectRatio;
        sx = (originalImg.width - sWidth) / 2;
        sy = 0;
    } else {
        // Image is taller or same aspect ratio as frame: fit width, crop height
        sWidth = originalImg.width;
        sHeight = originalImg.width / halfFrameAspectRatio;
        sx = 0;
        sy = (originalImg.height - sHeight) / 2;
    }
    
    // Ensure sx, sy are non-negative and sWidth, sHeight are positive and within source image bounds
    sx = Math.max(0, sx);
    sy = Math.max(0, sy);
    sWidth = Math.max(1, Math.min(sWidth, originalImg.width - sx)); 
    sHeight = Math.max(1, Math.min(sHeight, originalImg.height - sy));


    // 5. Define draw positions (destinations) for the two frames' content on the canvas
    const frameDestinations = [
        { x: borderWidth, y: borderWidth, w: frameContentRenderWidth, h: frameContentRenderHeight },
        { x: borderWidth + frameContentRenderWidth + borderWidth, y: borderWidth, w: frameContentRenderWidth, h: frameContentRenderHeight }
    ];

    const actualCornerRadius = Math.max(0, frameContentRenderWidth * cornerRadiusFraction);

    // 6. Process and draw each frame
    for (const dest of frameDestinations) {
        if (dest.w <= 0 || dest.h <= 0) continue; // Skip if frame dimension is zero/negative

        ctx.save();

        // Create rounded rectangle clipping path
        createRoundedRectPath(ctx, dest.x, dest.y, dest.w, dest.h, actualCornerRadius);
        ctx.clip();

        // Apply color filters (sepia, saturation)
        let filterString = "";
        if (sepiaAmount > 0 && sepiaAmount <=1) filterString += `sepia(${sepiaAmount}) `;
        if (saturationAmount >=0 ) filterString += `saturate(${saturationAmount}) `;
        
        if (filterString.trim() !== "") {
            ctx.filter = filterString.trim();
        }

        // Draw the (cropped part of the) original image into the frame
        try {
            ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, dest.x, dest.y, dest.w, dest.h);
        } catch (e) {
            console.error("Error drawing image:", e);
            // Could draw an error message within the frame
            ctx.fillStyle = "red";
            ctx.fillRect(dest.x, dest.y, dest.w, dest.h);
            ctx.restore(); // Restore before next iteration or returning
            continue;      // Skip further processing for this frame
        }
        

        ctx.filter = 'none'; // Reset filter before applying grain or vignette manually

        // Apply film grain
        if (grainAmount > 0 && grainAmount <=1) {
            try {
                const frameImageData = ctx.getImageData(dest.x, dest.y, dest.w, dest.h);
                const data = frameImageData.data;
                const dLength = data.length;
                for (let i = 0; i < dLength; i += 4) {
                    if (data[i + 3] === 0) continue; // Skip fully transparent pixels
                    const grainVal = (Math.random() - 0.5) * 255 * grainAmount;
                    data[i]   = Math.max(0, Math.min(255, data[i] + grainVal));
                    data[i+1] = Math.max(0, Math.min(255, data[i+1] + grainVal));
                    data[i+2] = Math.max(0, Math.min(255, data[i+2] + grainVal));
                }
                ctx.putImageData(frameImageData, dest.x, dest.y);
            } catch (e) {
                console.warn("Could not apply grain (possibly due to tainted canvas from cross-origin image without CORS):", e);
            }
        }

        // Apply vignette
        if (vignetteStrength > 0 && vignetteStrength <= 1) {
            const centerX = dest.x + dest.w / 2;
            const centerY = dest.y + dest.h / 2;
            const outerEffectiveRadius = Math.sqrt(Math.pow(dest.w / 2, 2) + Math.pow(dest.h / 2, 2));

            const gradient = ctx.createRadialGradient(
                centerX, centerY, outerEffectiveRadius * Math.max(0, (1 - vignetteStrength)), // Inner circle (transparent part)
                centerX, centerY, outerEffectiveRadius                                         // Outer circle (edge of vignette)
            );
            
            gradient.addColorStop(0, 'rgba(0,0,0,0)'); 
            gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`);

            ctx.fillStyle = gradient;
            ctx.fillRect(dest.x, dest.y, dest.w, dest.h);
        }
        
        ctx.restore(); // Remove clipping path, restore filters and other context states
    }

    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 Half-frame Camera Filter Effect tool allows users to apply a vintage camera filter effect to their images, emulating the look of half-frame film photography. This tool can resize images to fit within a specified height while maintaining a 3:4 aspect ratio for authenticity. It applies customizable effects including sepia tones, saturation adjustments, film grain, and vignette shading. This is ideal for photographers, graphic designers, and enthusiasts looking to give a retro aesthetic to their digital images, perfect for social media posts, printed memorabilia, or artistic projects.

Leave a Reply

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

Other Image Tools:

Image Kodak Vision3 250D Motion Picture Film Effect Filter

Image 120 Film Format Filter Effect

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

See All →