Please bookmark this page to avoid losing your image tool!

Image VHS Effect 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,
                        aberrationAmount = 1.5,    // In pixels, how much to shift R and B channels
                        noiseIntensity = 0.08,   // 0 to 1: 0 for no noise, 1 for very high noise
                        scanlineOpacity = 0.1,   // 0 to 1: Opacity of the scanlines (0.1 = 10% darker)
                        scanlineDensity = 3,     // Every Nth line will be a scanline. Use integer >= 1.
                        jitterIntensity = 0.3,   // Multiplier for horizontal jitter/warping effects
                        desaturation = 0.15) {   // 0 to 1: 0 for original colors, 1 for grayscale

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    canvas.width = imgWidth;
    canvas.height = imgHeight;

    ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);

    // Ensure scanlineDensity is a positive integer.
    const actualScanlineDensity = Math.max(1, Math.floor(scanlineDensity));

    const imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
    const data = imageData.data;
    // Create a pristine copy of the original pixel data for sampling
    const originalData = new Uint8ClampedArray(imageData.data);

    // Helper function to get a pixel from the originalData, ensures coordinates are within bounds
    function getOriginalPixel(x, y) {
        x = Math.round(x); // Ensure integer coordinates for pixel indexing
        y = Math.round(y);

        // Clamp coordinates to be within the image dimensions
        x = Math.max(0, Math.min(imgWidth - 1, x));
        y = Math.max(0, Math.min(imgHeight - 1, y));

        const i = (y * imgWidth + x) * 4;
        return [originalData[i], originalData[i + 1], originalData[i + 2], originalData[i + 3]];
    }

    for (let y = 0; y < imgHeight; y++) {
        let lineJitter = 0;
        if (jitterIntensity > 0) {
            // Base random jitter for the line
            lineJitter = (Math.random() - 0.5) * jitterIntensity * 5; // Max displacement: jitterIntensity * 2.5px
            // Add a slow sine wave distortion for a "rolling" or "wavy" effect
            // Math.random() in phase makes the wave irregular per line
            lineJitter += Math.sin(y * 0.075 + Math.random() * Math.PI) * jitterIntensity * 3; // Max displacement: jitterIntensity * 3px

            // Occasionally, a more pronounced "glitch" or "sync issue" for a line
            // Probability increases slightly with jitterIntensity
            if (Math.random() < (0.015 + 0.025 * jitterIntensity)) { 
                lineJitter += (Math.random() - 0.5) * jitterIntensity * 20; // Max displacement: jitterIntensity * 10px
            }
        }

        for (let x = 0; x < imgWidth; x++) {
            const currentIndex = (y * imgWidth + x) * 4;

            // 1. Chromatic Aberration & Jitter
            // Determine source coordinates for R, G, B channels including line jitter
            const jitteredX = x + lineJitter;
            
            const sourceX_r = jitteredX - aberrationAmount;
            const sourceX_g = jitteredX;
            const sourceX_b = jitteredX + aberrationAmount;

            // Sample R, G, B from their respective calculated source coordinates
            // Alpha channel is typically taken from the green (central) sample or original non-jittered pixel
            let r = getOriginalPixel(sourceX_r, y)[0];
            let g = getOriginalPixel(sourceX_g, y)[1];
            let b = getOriginalPixel(sourceX_b, y)[2];
            let a = getOriginalPixel(sourceX_g, y)[3]; // Alpha from the 'central' G channel sample

            // 2. Desaturation
            if (desaturation > 0) {
                const luminance = 0.299 * r + 0.587 * g + 0.114 * b; // Calculate luminance of the (possibly aberrated) color
                const d = Math.max(0, Math.min(1, desaturation)); // Clamp desaturation amount
                r = r * (1 - d) + luminance * d;
                g = g * (1 - d) + luminance * d;
                b = b * (1 - d) + luminance * d;
            }

            // 3. Noise
            if (noiseIntensity > 0) {
                const noiseVal = (Math.random() - 0.5) * 255 * noiseIntensity;
                r += noiseVal;
                g += noiseVal;
                b += noiseVal;
            }
            
            // 4. Scan Lines
            // Darken pixels on specific lines to create scanline effect
            if (scanlineOpacity > 0 && (y % actualScanlineDensity === 0)) {
                const factor = 1 - Math.max(0, Math.min(1, scanlineOpacity)); // Clamped opacity
                r *= factor;
                g *= factor;
                b *= factor;
            }

            // Write final pixel data, clamping RGB values to 0-255 range
            data[currentIndex]   = Math.max(0, Math.min(255, r));
            data[currentIndex+1] = Math.max(0, Math.min(255, g));
            data[currentIndex+2] = Math.max(0, Math.min(255, b));
            data[currentIndex+3] = a; // Use the sampled alpha
        }
    }

    ctx.putImageData(imageData, 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

The Image VHS Effect Filter tool allows users to apply a retro VHS-style effect to their images. This effect simulates classic video aesthetics by introducing chromatic aberration, noise, scanlines, and various distortions. It is ideal for creating vintage-style images, enhancing photos with a nostalgic touch, or adding a unique visual style to digital art. Users can customize the intensity of these effects, enabling them to achieve their desired look for personal projects, social media, or creative presentations.

Leave a Reply

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