Please bookmark this page to avoid losing your image tool!

Photo CRT Screen Filter 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.
function processImage(
    originalImg,
    scanlineOpacity = 0.15,
    scanlineThickness = 2,
    curvature = 0.08,
    bloomSize = 0.005, // As a fraction of image diagonal for blur radius
    bloomOpacity = 0.1,
    vignetteStrength = 0.3, // 0 to 1, controls darkness and spread
    noiseAmount = 0.04    // 0 to 1, e.g., 0.1 means noise can alter pixel component by +/- 12.8
) {
    if (!originalImg || originalImg.width === 0 || originalImg.height === 0) {
        console.error("Invalid image provided to processImage. Ensure it's loaded and has dimensions.");
        const errorCanvas = document.createElement('canvas');
        errorCanvas.width = 1; // Minimal canvas
        errorCanvas.height = 1;
        // Returning a small canvas to avoid breaking downstream if expecting a canvas.
        return errorCanvas;
    }

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

    // This will be the canvas we progressively build the effect on.
    const workingCanvas = document.createElement('canvas');
    workingCanvas.width = w;
    workingCanvas.height = h;
    const ctx = workingCanvas.getContext('2d');

    if (!ctx) {
        console.error("Could not get 2D context for the working canvas.");
        return workingCanvas; // Return empty canvas, though this is highly unlikely
    }

    // Use image smoothing for effects like curvature and bloom for smoother results.
    ctx.imageSmoothingEnabled = true;

    // Initial draw of the original image onto our working canvas.
    ctx.drawImage(originalImg, 0, 0, w, h);

    // Step 1: Apply Curvature
    // This effect redraws the image with barrel distortion.
    // Curvature value should be positive for barrel, typically < 0.5 for reasonable effect.
    if (curvature > 0 && curvature < 1) {
        const tempCanvasForCurvature = document.createElement('canvas');
        tempCanvasForCurvature.width = w;
        tempCanvasForCurvature.height = h;
        const tempCtx = tempCanvasForCurvature.getContext('2d');

        if (!tempCtx) { // Should not happen
             console.error("Could not get 2D context for temp curvature canvas.");
             // Proceed without curvature if this unlikely event occurs
        } else {
            // Copy current state (original image) to temp canvas for source reading
            tempCtx.drawImage(workingCanvas, 0, 0);
            ctx.clearRect(0, 0, w, h); // Clear working canvas for redrawing with curvature

            const centerX = w / 2;
            const centerY = h / 2;

            // Apply horizontal curvature (squeezing horizontal strips based on their vertical position)
            for (let y = 0; y < h; y++) {
                const normalizedY = (y - centerY) / centerY; // Normalize y: -1 (top) to 1 (bottom)
                const scaleX = 1 - curvature * (normalizedY * normalizedY); // Parabolic scaling
                const effectedWidth = w * scaleX;
                const offsetX = (w - effectedWidth) / 2;
                if (effectedWidth > 0) {
                    ctx.drawImage(tempCanvasForCurvature,
                        0, y, w, 1, // Source: 1px high strip from original image (on temp canvas)
                        offsetX, y, effectedWidth, 1 // Destination: Scaled width, centered horizontally
                    );
                }
            }

            // Now apply vertical curvature to the already horizontally curved image.
            // Update temp canvas to hold the horizontally curved image.
            tempCtx.clearRect(0, 0, w, h);
            tempCtx.drawImage(workingCanvas, 0, 0);
            ctx.clearRect(0, 0, w, h); // Clear working canvas again

            for (let x = 0; x < w; x++) {
                const normalizedX = (x - centerX) / centerX; // Normalize x: -1 (left) to 1 (right)
                const scaleY = 1 - curvature * (normalizedX * normalizedX); // Parabolic scaling
                const effectedHeight = h * scaleY;
                const offsetY = (h - effectedHeight) / 2;
                if (effectedHeight > 0) {
                    ctx.drawImage(tempCanvasForCurvature,
                        x, 0, 1, h, // Source: 1px wide strip from horizontally curved image
                        x, offsetY, 1, effectedHeight // Destination: Scaled height, centered vertically
                    );
                }
            }
        }
    }

    // Step 2: Apply Bloom (Phosphor Glow)
    if (bloomSize > 0 && bloomOpacity > 0) {
        const bloomCanvas = document.createElement('canvas');
        bloomCanvas.width = w;
        bloomCanvas.height = h;
        const bloomCtx = bloomCanvas.getContext('2d');
        
        if (!bloomCtx) { // Should not happen
            console.error("Could not get 2D context for bloom canvas.");
        } else {
            const blurRadius = Math.hypot(w, h) * bloomSize;
            if (blurRadius > 0) {
                bloomCtx.filter = `blur(${blurRadius}px)`;
                // Draw the current state of workingCanvas (with curvature) onto bloomCanvas, applying the blur.
                bloomCtx.drawImage(workingCanvas, 0, 0, w, h);
                bloomCtx.filter = 'none'; // Reset filter on bloomCtx

                // Blend the blurred (bloomed) image back onto the workingCanvas.
                ctx.save(); // Save current state (like globalAlpha, globalCompositeOperation)
                ctx.globalAlpha = bloomOpacity;
                ctx.globalCompositeOperation = 'lighter'; // Additive blending creates a glow effect
                ctx.drawImage(bloomCanvas, 0, 0);
                ctx.restore(); // Restore to previous globalAlpha and GCO
            }
        }
    }

    // Step 3: Apply Scanlines
    // These are dark lines overlaid on the image.
    if (scanlineOpacity > 0 && scanlineThickness > 0) {
        ctx.fillStyle = `rgba(0, 0, 0, ${scanlineOpacity})`;
        for (let y = 0; y < h; y += scanlineThickness * 2) { // Dark line, then gap of same thickness
            ctx.fillRect(0, y, w, scanlineThickness);
        }
    }

    // Step 4: Apply Noise
    // Adds random pixel intensity variations.
    if (noiseAmount > 0) {
        const imageData = ctx.getImageData(0, 0, w, h);
        const data = imageData.data;
        // noiseAmount (0-1) scales noise up to +/- 128:  e.g. 0.1 => +/- 12.8
        const noiseRange = noiseAmount * 128;

        for (let i = 0; i < data.length; i += 4) {
            // Random value between -noiseRange and +noiseRange
            const randomFactor = (Math.random() - 0.5) * 2 * noiseRange;
            data[i] = Math.max(0, Math.min(255, data[i] + randomFactor));
            data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + randomFactor));
            data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + randomFactor));
            // Alpha channel (data[i+3]) is not changed.
        }
        ctx.putImageData(imageData, 0, 0);
    }

    // Step 5: Apply Vignette
    // Darkens the corners/edges of the image.
    if (vignetteStrength > 0) {
        const centerX = w / 2;
        const centerY = h / 2;
        const outerRadius = Math.hypot(centerX, centerY); // Radius to reach corners

        // vignetteStrength (0-1): 0 means no vignette, 1 means vignette starts near center and is darker.
        const innerRadiusFactor = Math.max(0, 1 - vignetteStrength * 1.25);
        const effectiveInnerRadius = outerRadius * innerRadiusFactor;

        const gradient = ctx.createRadialGradient(
            centerX, centerY, effectiveInnerRadius,
            centerX, centerY, outerRadius
        );
        gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent center
        gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`); // Dark edges

        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, w, h);
    }

    // The workingCanvas now holds the final processed image.
    return workingCanvas;
}

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 CRT Screen Filter Effect Tool allows users to apply retro visual effects to their images, simulating the appearance of CRT (cathode ray tube) screens. This tool provides features such as curvature distortion for a rounded screen effect, bloom for phosphor glow, scanlines for an authentic vintage look, noise for texture, and a vignette to darken the edges of the image. These effects can be used to create nostalgic artwork, enhance graphic designs, or add a unique aesthetic to photographs.

Leave a Reply

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