Please bookmark this page to avoid losing your image tool!

Photo VHS Found Footage Analog 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.
async function processImage(originalImg, aberrationAmount = 1.5, noiseAmount = 0.2, scanlineOpacity = 0.3, glitchAmount = 5, showTimestamp = 1, timestampText = '') {
    /**
     * Applies a VHS found footage analog effect to an image.
     * @param {Image} originalImg - The source javascript Image object.
     * @param {number} aberrationAmount - The strength of the chromatic aberration.
     * @param {number} noiseAmount - The intensity of the monochrome noise (0 to 1).
     * @param {number} scanlineOpacity - The opacity of the scanlines (0 to 1).
     * @param {number} glitchAmount - The number of horizontal glitch blocks to generate.
     * @param {number} showTimestamp - Set to 1 to show a timestamp, 0 to hide.
     * @param {string} timestampText - Custom text for the timestamp. If empty, a random one is generated.
     * @returns {Promise<HTMLCanvasElement>} A canvas element with the VHS effect applied.
     */

    // 1. Setup Canvas
    const canvas = document.createElement('canvas');
    // Using { willReadFrequently: true } is a performance hint for the browser.
    const ctx = canvas.getContext('2d', {
        willReadFrequently: true
    });
    const w = originalImg.naturalWidth;
    const h = originalImg.naturalHeight;
    canvas.width = w;
    canvas.height = h;

    // 2. Draw the base image with an initial filter for an analog feel
    ctx.filter = 'saturate(0.7) contrast(1.2) brightness(1.1) blur(0.5px)';
    ctx.drawImage(originalImg, 0, 0, w, h);
    ctx.filter = 'none';

    // 3. Process Pixels for Core VHS Effects (Aberration, Noise, Scanlines)
    const sourceData = ctx.getImageData(0, 0, w, h);
    const sourcePixels = sourceData.data;
    const outputData = ctx.createImageData(w, h);
    const outputPixels = outputData.data;

    // A random value to create a "wavy" horizontal distortion effect
    const wavyDistortion = Math.random() * 10;
    const aberration = Math.floor(aberrationAmount);

    for (let y = 0; y < h; y++) {
        // Calculate a horizontal shift for the entire line to simulate tracking issues
        const lineShift = Math.floor((Math.sin(y * 0.04 + wavyDistortion) + Math.sin(y * 0.07 + wavyDistortion)) * 2);

        for (let x = 0; x < w; x++) {
            const outputIndex = (y * w + x) * 4;
            const sourceX = Math.max(0, Math.min(w - 1, x + lineShift));

            // --- Chromatic Aberration ---
            // Get source pixel indices for R, G, B channels with horizontal offsets
            const rIndex = (y * w + Math.max(0, Math.min(w - 1, sourceX - aberration))) * 4;
            const gIndex = (y * w + sourceX) * 4;
            const bIndex = (y * w + Math.max(0, Math.min(w - 1, sourceX + aberration))) * 4;

            // --- Noise ---
            const monoNoise = (Math.random() - 0.5) * 255 * noiseAmount;

            // Combine a pixel from the R, G, B shifted sources and add noise
            // The Uint8ClampedArray will automatically clamp values between 0 and 255.
            outputPixels[outputIndex] = sourcePixels[rIndex] + monoNoise;
            outputPixels[outputIndex + 1] = sourcePixels[gIndex + 1] + monoNoise;
            outputPixels[outputIndex + 2] = sourcePixels[bIndex + 2] + monoNoise;
            outputPixels[outputIndex + 3] = sourcePixels[gIndex + 3]; // Alpha

            // --- Scanlines ---
            // Darken pixels on every 3rd line to create a scanline effect
            if (y % 3 !== 0) {
                const s = 1 - scanlineOpacity;
                outputPixels[outputIndex] *= s;
                outputPixels[outputIndex + 1] *= s;
                outputPixels[outputIndex + 2] *= s;
            }
        }
    }

    // 4. Put the manipulated pixel data back onto the canvas
    ctx.putImageData(outputData, 0, 0);

    // 5. Add Glitch Blocks
    // This draws shifted slices of the canvas over itself to create a blocky glitch effect.
    for (let i = 0; i < glitchAmount; i++) {
        const x = Math.random() * w * 0.8;
        const y = Math.random() * h;
        const h_ = Math.random() * 50 + 1;
        const w_ = Math.random() * w * 0.2 + w * 0.05;
        const offset = (Math.random() - 0.5) * 40;
        ctx.drawImage(canvas, x, y, w_, h_, x + offset, y, w_, h_);
    }

    // 6. Add Vignette for a CRT screen look
    const grd = ctx.createRadialGradient(w / 2, h / 2, Math.min(w, h) / 3, w / 2, h / 2, Math.max(w, h) / 1.5);
    grd.addColorStop(0, 'rgba(0,0,0,0)');
    grd.addColorStop(1, 'rgba(0,0,0,0.4)');
    ctx.fillStyle = grd;
    ctx.fillRect(0, 0, w, h);

    // 7. Add Timestamp
    if (showTimestamp > 0) {
        const fontName = 'VT323';
        // Using a direct woff2 link is more reliable and avoids parsing CSS.
        const fontUrl = `url(https://fonts.gstatic.com/s/vt323/v17/pxiKyp0ihIEF2isQFJXGdg.woff2)`;
        const vhsFont = new FontFace(fontName, fontUrl);

        try {
            // Check if font is already available to avoid re-loading
            if (!document.fonts.check(`12px ${fontName}`)) {
                await vhsFont.load();
                document.fonts.add(vhsFont);
            }

            // Generate a random timestamp text if none is provided
            let finalTimestampText = timestampText;
            if (!finalTimestampText) {
                const year = Math.floor(Math.random() * 20) + 1985; // 1985-2004
                const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0');
                const day = String(Math.floor(Math.random() * 28) + 1).padStart(2, '0');
                const hour = String(Math.floor(Math.random() * 24)).padStart(2, '0');
                const minute = String(Math.floor(Math.random() * 60)).padStart(2, '0');
                finalTimestampText = `PLAY   ${month}-${day}-${year}   ${hour}:${minute}`;
            }

            const fontSize = Math.max(20, Math.floor(w / 35));
            ctx.font = `${fontSize}px "${fontName}", monospace`;
            ctx.fillStyle = 'rgba(255, 230, 150, 0.75)';
            ctx.textBaseline = 'bottom';
            ctx.textAlign = 'right';

            // Add a slight text shadow to mimic CRT glow
            ctx.shadowColor = 'orange';
            ctx.shadowBlur = 8;

            const margin = Math.floor(w * 0.03);
            ctx.fillText(finalTimestampText, w - margin, h - margin);

            // Reset shadow for subsequent drawing operations
            ctx.shadowColor = 'transparent';
            ctx.shadowBlur = 0;

        } catch (e) {
            console.error('VHS font could not be loaded:', e);
            // If the font fails to load, we simply skip drawing the text.
        }
    }

    // 8. Return the final canvas
    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 Photo VHS Found Footage Analog Effect Tool allows users to apply a vintage VHS effect to their images, emulating the look and feel of analog footage. This tool can be particularly useful for creating nostalgic or retro-style visuals for projects such as music videos, art installations, or social media content. Users can customize the strength of various effects, including chromatic aberration, noise, scanlines, and glitches, as well as add a timestamp to enhance the authentic feel of old videotapes.

Leave a Reply

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