Please bookmark this page to avoid losing your image tool!

90s Analog Security Camera Style Image Filter

(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,
    scanlineIntensity = 0.4,
    noiseIntensity = 0.25,
    grayscale = 0.8,
    contrast = 1.5,
    brightness = 1.2,
    blurAmount = 1.0,
    vhsTracking = 1,
    rgbShift = 3,
    timestampText = "1998-10-24 23:14:00",
    vignetteAmount = 0.6,
    tintColor = "none", // options: "none", "green", "blue", "amber"
    pixelation = 2, // >= 1, lowers resolution for true 90s look
    recDot = 1 // 1 for true, 0 for false
) {
    // Parse parameters to ensure type safety
    scanlineIntensity = Number(scanlineIntensity);
    noiseIntensity = Number(noiseIntensity);
    grayscale = Number(grayscale);
    contrast = Number(contrast);
    brightness = Number(brightness);
    blurAmount = Number(blurAmount);
    vhsTracking = Number(vhsTracking);
    rgbShift = Math.floor(Number(rgbShift));
    vignetteAmount = Number(vignetteAmount);
    pixelation = Math.max(1, Math.floor(Number(pixelation)));
    recDot = Number(recDot);

    const w = originalImg.width;
    const h = originalImg.height;

    // Output Canvas setup
    const canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    // Step 1: Pixelation pass via an off-screen canvas to downgrade quality
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = Math.ceil(w / pixelation);
    tempCanvas.height = Math.ceil(h / pixelation);
    const tempCtx = tempCanvas.getContext('2d');
    tempCtx.drawImage(originalImg, 0, 0, tempCanvas.width, tempCanvas.height);

    // Step 2: Draw the downscaled image scaled back up, applying standard post-processing filters
    ctx.imageSmoothingEnabled = false; // Gives that jagged low-res aesthetic
    ctx.filter = `blur(${blurAmount}px) brightness(${brightness}) contrast(${contrast}) grayscale(${grayscale})`;
    ctx.drawImage(tempCanvas, 0, 0, w, h);

    // Step 3: Draw Timestamp and REC marker
    // We draw this BEFORE pixel manipulation so it gets the VHS glitch and scanlines baked into it
    ctx.filter = 'none';
    const fontSize = Math.max(12, Math.floor(h * 0.04));
    ctx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
    ctx.shadowColor = 'black';
    ctx.shadowBlur = Math.max(2, fontSize * 0.2);
    ctx.textAlign = 'left';
    ctx.textBaseline = 'bottom';

    if (timestampText) {
        ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
        ctx.fillText(timestampText, w * 0.05, h * 0.95);
    }

    if (recDot === 1) {
        const dotX = w * 0.05 + fontSize * 0.4;
        const dotY = h * 0.05 + fontSize * 0.4;
        
        ctx.fillStyle = '#ff0000';
        ctx.beginPath();
        ctx.arc(dotX, dotY, fontSize * 0.4, 0, Math.PI * 2);
        ctx.fill();
        
        ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
        ctx.textBaseline = 'middle';
        ctx.fillText("REC", dotX + fontSize * 0.8, dotY);
    }

    // Step 4: Core pixel manipulations (VHS tracking, Noise, Chromatic Aberration, Scanlines, Vignette, Tint)
    const srcImageData = ctx.getImageData(0, 0, w, h);
    const srcData = srcImageData.data;
    const dstImageData = ctx.createImageData(w, h);
    const dstData = dstImageData.data;

    const cx = w / 2;
    const cy = h / 2;
    const maxDist = Math.sqrt(cx * cx + cy * cy);

    // Tint channel adjustments
    let tr = 1, tg = 1, tb = 1;
    if (tintColor === 'green') { tr = 0.5; tg = 1.3; tb = 0.5; }
    else if (tintColor === 'blue') { tr = 0.5; tg = 0.6; tb = 1.4; }
    else if (tintColor === 'amber') { tr = 1.2; tg = 0.9; tb = 0.3; }

    for (let y = 0; y < h; y++) {
        let trackOffset = 0;
        
        // VHS Tracking tear artifacts near the bottom of the feed
        if (vhsTracking === 1 && y > h * 0.85) {
            trackOffset = Math.floor((Math.random() - 0.5) * (w * 0.02));
            if (y > h * 0.9 && y < h * 0.92) {
                // Severe tear band
                trackOffset += Math.floor((Math.random() - 0.5) * (w * 0.06));
            }
        }

        // Scanline logic + Interlacing
        let scanFactor = 1;
        if (scanlineIntensity > 0) {
            // Darken alternate lines, and add a subtle vertical sine wave 
            const lineDarkness = y % 2 === 0 ? 0.3 : 0.0;
            const waveDarkness = (Math.sin(y * Math.PI * 0.3) * 0.5 + 0.5) * 0.2;
            scanFactor = 1 - (scanlineIntensity * (lineDarkness + waveDarkness));
        }

        for (let x = 0; x < w; x++) {
            const dstIdx = (y * w + x) * 4;

            let srcX = x + trackOffset;
            if (srcX < 0) srcX = 0;
            else if (srcX >= w) srcX = w - 1;

            // Chromatic Aberration / RGB Glitch Offset
            let rx = Math.max(0, srcX - rgbShift);
            let gx = srcX;
            let bx = Math.min(w - 1, srcX + rgbShift);

            let rIdx = (y * w + rx) * 4;
            let gIdx = (y * w + gx) * 4;
            let bIdx = (y * w + bx) * 4;

            let r = srcData[rIdx];
            let g = srcData[gIdx + 1];
            let b = srcData[bIdx + 2];
            let a = srcData[gIdx + 3];

            // Static Noise
            if (noiseIntensity > 0) {
                const noise = (Math.random() - 0.5) * 255 * noiseIntensity;
                r += noise;
                g += noise;
                b += noise;
            }

            // CRT Color Tinting
            r *= tr;
            g *= tg;
            b *= tb;

            // Apply Scanlines
            r *= scanFactor;
            g *= scanFactor;
            b *= scanFactor;

            // Lens Vignette Extrapolation
            if (vignetteAmount > 0) {
                let dx = x - cx;
                let dy = y - cy;
                let dist = Math.sqrt(dx * dx + dy * dy);
                let vFactor = 1 - (dist / maxDist) * vignetteAmount;
                if (vFactor < 0) vFactor = 0;
                r *= vFactor;
                g *= vFactor;
                b *= vFactor;
            }

            // Clamping and applying modifications to output pixels
            dstData[dstIdx] = Math.max(0, Math.min(255, r));
            dstData[dstIdx + 1] = Math.max(0, Math.min(255, g));
            dstData[dstIdx + 2] = Math.max(0, Math.min(255, b));
            dstData[dstIdx + 3] = a;
        }
    }

    // Apply the manipulated pixels to the canvas buffer
    ctx.putImageData(dstData, 0, 0);

    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 90s analog security camera aesthetic to your images. It simulates the characteristic visual imperfections of old surveillance footage, such as scanlines, static noise, chromatic aberration (RGB shifting), and VHS tracking glitches. Users can customize the effect with settings for pixelation, brightness, contrast, vignette, and color tints like amber, green, or blue. It also features an optional customizable timestamp and a red ‘REC’ indicator to complete the retro lo-fi look. This tool is ideal for creators looking to add a nostalgic or suspenseful atmosphere to photos for social media, digital art projects, or storytelling.

Leave a Reply

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