Please bookmark this page to avoid losing your image tool!

Image To 16mm VHSRip Style Filter Converter

(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,
    noiseIntensity = 0.15,
    rgbShift = 4,
    scanlineIntensity = 0.15,
    blurAmount = 0.5,
    showText = 1,
    vhsText = "PLAY \u25BA"
) {
    // Ensure input parameters are of correct numeric type
    const noiseInt = Number(noiseIntensity);
    const shiftAmount = Number(rgbShift);
    const scanlineInt = Number(scanlineIntensity);
    const blurAmt = Number(blurAmount);
    const displayTxt = Number(showText);

    const width = originalImg.width;
    const height = originalImg.height;

    // Create the primary canvas
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Step 1: Draw the image onto the canvas with a slight base blur/contrast boost for softness
    ctx.filter = `blur(${blurAmt}px) contrast(1.1) saturate(1.1)`;
    ctx.drawImage(originalImg, 0, 0);
    ctx.filter = 'none';

    // Step 2: Grab pixel data for heavy manipulation (VHS & 16mm organic artifacts)
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;
    const outData = new Uint8ClampedArray(data.length);

    const maxDist = Math.sqrt(0.5); // Base reference for distance at corners (approx x: 0.5^2 + y: 0.5^2)
    const bleed = 3; // Simulates chroma blur/color bleed horizontally common in VHS

    // Iterate over all pixels
    for (let y = 0; y < height; y++) {
        // Tape Tracking Wiggle Distance (Wavy alignment lines + analog jitter)
        const waveOffset = Math.floor(Math.sin(y * 0.02) * shiftAmount * 1.5) + (Math.random() > 0.95 ? 1 : 0);

        for (let x = 0; x < width; x++) {
            const i = (y * width + x) * 4;
            const localX = x + waveOffset;

            // -- Red Channel Process (Chroma Blur/Bleed) --
            // Averages neighboring red pixels to create horizontal smeared tailing
            let rSum = 0;
            for (let k = 0; k < bleed; k++) {
                const rx = Math.max(0, Math.min(width - 1, localX - shiftAmount - k));
                rSum += data[(y * width + rx) * 4];
            }
            let r = rSum / bleed;

            // -- Green Channel Process (Central Tracked Channel) --
            const gx = Math.max(0, Math.min(width - 1, localX));
            let g = data[(y * width + gx) * 4 + 1];

            // -- Blue Channel Process (Shifted Right) --
            const bx = Math.max(0, Math.min(width - 1, localX + shiftAmount));
            let b = data[(y * width + bx) * 4 + 2];

            // Add High-Fidelity 16mm Film Grain / VHS Static
            const noise = (Math.random() - 0.5) * 255 * noiseInt;
            r += noise;
            g += noise;
            b += noise;

            // Inject Analog Scanlines (Interlacing artifacts on every 3rd pixel line)
            if (y % 3 === 0) {
                r -= r * scanlineInt;
                g -= g * scanlineInt;
                b -= b * scanlineInt;
            }

            // Calculate Vignette (Darkening edges)
            const dx = (x / width) - 0.5;
            const dy = (y / height) - 0.5;
            const dist = Math.sqrt(dx * dx + dy * dy);
            const vignetteStrength = 0.5; // Moderately dark edges
            const vignette = 1 - Math.min(1, dist / maxDist) * vignetteStrength;

            r *= vignette;
            g *= vignette;
            b *= vignette;

            // -- 16mm Analog Color Grading & Exposure Map --
            // Shift tones to be warmer (higher red/green, less blue) and lift absolute blacks
            r = r * 0.96 + 18;
            g = g * 0.92 + 12;
            b = b * 0.85 + 5;

            // Apply a slight S-Curve to midtones to mimic film emulation (Crush)
            r = (r - 128) * 1.15 + 128;
            g = (g - 128) * 1.15 + 128;
            b = (b - 128) * 1.15 + 128;

            outData[i] = r;
            outData[i + 1] = g;
            outData[i + 2] = b;
            outData[i + 3] = data[i + 3]; // Preserve Original Alpha
        }
    }

    // Step 3: Put processed image data back into the canvas
    const outImgData = new ImageData(outData, width, height);
    ctx.putImageData(outImgData, 0, 0);

    // Step 4: Form 16mm physical Scratches and Dust specs
    const numScratches = Math.floor(width / 150);
    for (let i = 0; i < numScratches; i++) {
        const scX = Math.random() * width;
        const scW = Math.random() * 2 + 1;
        
        // Light scratch
        ctx.fillStyle = `rgba(255, 255, 255, ${Math.random() * 0.06 + 0.02})`;
        ctx.fillRect(scX, 0, scW, height);
        
        // Dark dust streak
        const scDarkX = Math.random() * width;
        ctx.fillStyle = `rgba(0, 0, 0, ${Math.random() * 0.1 + 0.03})`;
        ctx.fillRect(scDarkX, 0, scW, height);
    }

    // Step 5: VHS Tape tracking error bands (Random thin horizontal noise lines)
    const numCreases = Math.floor(Math.random() * 3) + 1;
    for (let i = 0; i < numCreases; i++) {
        const cy = Math.random() * height;
        const ch = Math.random() * (height * 0.02) + 2; 

        // White static block
        ctx.fillStyle = 'rgba(255, 255, 255, 0.08)';
        ctx.fillRect(0, cy, width, ch);

        // Sub-channel RGB band inside crease
        ctx.fillStyle = Math.random() > 0.5 ? 'rgba(255, 0, 0, 0.06)' : 'rgba(0, 255, 0, 0.06)';
        ctx.fillRect(0, cy + ch / 3, width, ch / 3);
    }

    // Step 6: Render VHS OSD overlay metrics
    if (displayTxt === 1 && String(vhsText).trim() !== "") {
        const fontSize = Math.max(22, Math.floor(height * 0.045));
        
        // Use a blocky native monospaced font
        ctx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
        ctx.textAlign = 'left';
        ctx.textBaseline = 'top';

        // Glowing tube shadow setup for text
        ctx.shadowColor = 'rgba(0, 0, 0, 0.9)';
        ctx.shadowBlur = Math.max(3, fontSize * 0.15);
        ctx.shadowOffsetX = Math.max(1, fontSize * 0.05);
        ctx.shadowOffsetY = Math.max(1, fontSize * 0.05);

        ctx.fillStyle = '#ffffff';

        const padding = fontSize;
        ctx.fillText(String(vhsText), padding, padding);

        // Add standard timing layout text at the bottom
        const bottomText = "SP  0:00:00";
        ctx.textBaseline = 'bottom';
        ctx.fillText(bottomText, padding, height - padding);

        // Add static date/time marker to right bottom (e.g. AM 12:00)
        ctx.textAlign = 'right';
        ctx.fillText("AM 12:00", width - padding, height - padding);

        ctx.shadowColor = 'transparent'; // Cleanup
    }

    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

This tool applies a vintage analog aesthetic to your images, simulating the distinctive look of 16mm film and VHS video tapes. It uses advanced pixel manipulation to introduce effects such as chromatic aberration (color bleeding), analog noise, scanlines, and tape tracking jitters. The filter also adds organic textures like film grain, scratches, dust specs, and vignette darkening, alongside a customizable VHS-style On-Screen Display (OSD) overlay. This tool is ideal for creators looking to give digital photos a nostalgic, retro, or lo-fi aesthetic for social media, artistic projects, or themed graphic design.

Leave a Reply

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