Please bookmark this page to avoid losing your image tool!

Image Film Strip Frame Creator

(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,
    numFrames = 3,
    filmColor = "black",
    sprocketHoleColor = "white",
    photoBorderColor = "#333333",
    photoBorderWidth = 2,
    photoTargetHeight = 150,
    photoPadding = 5,
    sprocketAreaHeight = 25,
    frameGap = 5,
    otherFramesDimFactor = "0.5" // String "0.0" to "1.0"
) {
    // Helper function to draw rounded rectangles (for sprocket holes)
    // Uses arcTo for robust rounded corners
    function drawRoundedRect(ctx, x, y, width, height, radius) {
        if (width < 2 * radius) radius = width / 2;
        if (height < 2 * radius) radius = height / 2;
        if (radius < 0) radius = 0;

        ctx.beginPath();
        ctx.moveTo(x + radius, y);
        ctx.arcTo(x + width, y, x + width, y + height, radius);
        ctx.arcTo(x + width, y + height, x, y + height, radius);
        ctx.arcTo(x, y + height, x, y, radius);
        ctx.arcTo(x, y, x + width, y, radius);
        ctx.closePath();
    }

    // Validate input image
    if (!originalImg || !originalImg.naturalWidth || !originalImg.naturalHeight) {
        const errorCanvas = document.createElement('canvas');
        const errorWidth = Math.max(200, Number(photoTargetHeight) * 1.5 || 200);
        const errorHeight = Math.max(100, Number(photoTargetHeight) || 100);
        errorCanvas.width = errorWidth;
        errorCanvas.height = errorHeight;
        const errCtx = errorCanvas.getContext('2d');
        errCtx.fillStyle = 'lightgray';
        errCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
        errCtx.fillStyle = 'red';
        errCtx.font = '14px Arial';
        errCtx.textAlign = 'center';
        errCtx.textBaseline = 'middle';
        errCtx.fillText('Image not loaded or invalid.', errorCanvas.width / 2, errorCanvas.height / 2);
        return errorCanvas;
    }

    // Sanitize numeric parameters
    const actualNumFrames = Math.max(1, Number(numFrames));
    const actualPhotoBorderWidth = Math.max(0, Number(photoBorderWidth));
    const actualPhotoTargetHeight = Math.max(10, Number(photoTargetHeight));
    const actualPhotoPadding = Math.max(0, Number(photoPadding));
    const actualSprocketAreaHeight = Math.max(5, Number(sprocketAreaHeight));
    const actualFrameGap = Math.max(0, Number(frameGap));
    let dimFactor = parseFloat(otherFramesDimFactor);
    if (isNaN(dimFactor)) {
        dimFactor = 0.5;
    }
    dimFactor = Math.max(0, Math.min(1, dimFactor));

    // 1. Scale photo based on target height
    const aspectRatio = originalImg.naturalWidth / originalImg.naturalHeight;
    const scaledPhotoHeight = actualPhotoTargetHeight;
    const scaledPhotoWidth = scaledPhotoHeight * aspectRatio;

    // 2. Calculate dimensions for the "photo box" (photo + its border)
    const photoBoxWidth = scaledPhotoWidth + 2 * actualPhotoBorderWidth;
    const photoBoxHeight = scaledPhotoHeight + 2 * actualPhotoBorderWidth;

    // 3. Calculate dimensions for the content area of a single film frame (photo box + padding around it)
    const frameContentWidth = photoBoxWidth + 2 * actualPhotoPadding;
    const frameContentHeight = photoBoxHeight + 2 * actualPhotoPadding;

    // 4. Calculate full dimensions for a single film frame (including top/bottom sprocket areas)
    const singleFrameWidth = frameContentWidth;
    const singleFrameHeight = frameContentHeight + 2 * actualSprocketAreaHeight;

    // 5. Calculate total canvas dimensions
    const canvasWidth = (actualNumFrames * singleFrameWidth) + ((actualNumFrames > 1 ? actualNumFrames - 1 : 0) * actualFrameGap);
    const canvasHeight = singleFrameHeight;

    const canvas = document.createElement('canvas');
    canvas.width = Math.max(1, canvasWidth); // Ensure canvas dimensions are at least 1x1
    canvas.height = Math.max(1, canvasHeight);
    const ctx = canvas.getContext('2d');
    
    // Fallback for extremely small calculated dimensions
    if (canvas.width <= 1 || canvas.height <= 1 ) {
        canvas.width = Math.max(canvas.width, 50);
        canvas.height = Math.max(canvas.height, 50);
        ctx.fillStyle = 'lightpink'; // Use a different color for this specific error
        ctx.fillRect(0,0,canvas.width,canvas.height);
        ctx.fillStyle = 'black';
        ctx.font = '10px Arial';
        ctx.fillText('Calc. Error', 5, 15);
        return canvas;
    }

    let currentXOffset = 0;
    const centralFrameIndex = (actualNumFrames === 1) ? 0 : Math.floor(actualNumFrames / 2);

    for (let i = 0; i < actualNumFrames; i++) {
        const frameStartX = currentXOffset;

        // Draw film background for this frame segment
        ctx.fillStyle = filmColor;
        ctx.fillRect(frameStartX, 0, singleFrameWidth, singleFrameHeight);

        // Draw photo border area
        const borderX = frameStartX + actualPhotoPadding;
        const borderY = actualSprocketAreaHeight + actualPhotoPadding;
        ctx.fillStyle = photoBorderColor;
        ctx.fillRect(borderX, borderY, photoBoxWidth, photoBoxHeight);

        // Draw the photo itself
        const actualPhotoX = borderX + actualPhotoBorderWidth;
        const actualPhotoY = borderY + actualPhotoBorderWidth;
        
        const isCentralFrame = (i === centralFrameIndex);
        if (actualNumFrames === 1 || isCentralFrame || dimFactor === 1.0) {
            ctx.globalAlpha = 1.0;
        } else {
            ctx.globalAlpha = dimFactor;
        }
        
        // Enable image smoothing for better quality when scaling
        ctx.imageSmoothingEnabled = true;
        if (ctx.imageSmoothingQuality) { // Not supported in all older browsers
             ctx.imageSmoothingQuality = "high";
        }
       
        ctx.drawImage(originalImg, actualPhotoX, actualPhotoY, scaledPhotoWidth, scaledPhotoHeight);
        ctx.globalAlpha = 1.0; // Reset alpha after drawing

        // Draw sprocket holes for this frame
        const sprocketHoleMinHeight = 2; // Minimum height for a sprocket hole to be drawn
        const sprocketHoleMinEffectiveAreaHeight = 5; // Minimum sprocket area height to attempt drawing holes

        if (actualSprocketAreaHeight >= sprocketHoleMinEffectiveAreaHeight) {
            let sprocketHoleHeight = actualSprocketAreaHeight * 0.6;
            if (sprocketHoleHeight < sprocketHoleMinHeight) sprocketHoleHeight = 0; // Don't draw if too small

            if (sprocketHoleHeight > 0) {
                const sprocketHoleWidth = Math.max(1, sprocketHoleHeight * 0.8); 
                const sprocketHoleRadius = Math.min(sprocketHoleWidth / 2, sprocketHoleHeight / 2, Math.max(0,sprocketHoleWidth * 0.25));
                
                // Calculate number of sprockets and their spacing
                const numSprockets = Math.max(1, Math.floor(singleFrameWidth / (sprocketHoleWidth * 1.8))); // *1.8 for hole + typical gap
                
                const totalSprocketWidthOccupied = numSprockets * sprocketHoleWidth;
                const totalGapWidth = singleFrameWidth - totalSprocketWidthOccupied;
                const sprocketSpacingX = totalGapWidth / (numSprockets + 1); // Distribute gaps evenly, including ends

                const sprocketVerticalMargin = (actualSprocketAreaHeight - sprocketHoleHeight) / 2;
                ctx.fillStyle = sprocketHoleColor;

                for (let j = 0; j < numSprockets; j++) {
                    const sx = frameStartX + sprocketSpacingX + j * (sprocketHoleWidth + sprocketSpacingX);
                    
                    // Check if sprocket is reasonably within the frame width boundaries before drawing
                    if (sx + sprocketHoleWidth > frameStartX && sx < frameStartX + singleFrameWidth) {
                        // Top sprockets
                        const syTop = sprocketVerticalMargin;
                        if (syTop >=0 && syTop + sprocketHoleHeight <= actualSprocketAreaHeight) {
                           drawRoundedRect(ctx, sx, syTop, sprocketHoleWidth, sprocketHoleHeight, sprocketHoleRadius);
                           ctx.fill();
                        }

                        // Bottom sprockets
                        const syBottom = singleFrameHeight - actualSprocketAreaHeight + sprocketVerticalMargin;
                         if (syBottom >= singleFrameHeight - actualSprocketAreaHeight && syBottom + sprocketHoleHeight <= singleFrameHeight) {
                           drawRoundedRect(ctx, sx, syBottom, sprocketHoleWidth, sprocketHoleHeight, sprocketHoleRadius);
                           ctx.fill();
                        }
                    }
                }
            }
        }
        
        currentXOffset += singleFrameWidth + actualFrameGap;
    }

    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 Film Strip Frame Creator is a versatile online tool that allows users to transform a single image into a film strip format, complete with customizable frames and sprocket holes. Users can specify the number of frames, colors for the film strip and sprocket holes, as well as dimensions for the photo and padding. This tool is ideal for creating artistic displays of photos, perfect for projects like photo collages, scrapbook layouts, or digital art presentations. It enhances creativity by allowing detailed control over the visual presentation of images in a nostalgic film strip style.

Leave a Reply

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