Please bookmark this page to avoid losing your image tool!

Image Signal Loss 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, intensity = 30, maxHorizontalShift = 20, colorDistortion = 0.3, noiseLevel = 0.1) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    canvas.width = originalImg.naturalWidth || originalImg.width;
    canvas.height = originalImg.naturalHeight || originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        // Handle cases where image dimensions are not available (e.g. image not loaded yet)
        // Return a minimal canvas or could throw an error.
        canvas.width = 1; // Avoid division by zero or errors with 0-size canvas
        canvas.height = 1;
        console.warn("Original image has zero width or height. Ensure the image is loaded before processing.");
        return canvas;
    }
    
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    if (intensity === 0) {
        return canvas; // No effect if intensity is 0
    }

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const pixels = imageData.data;
    const width = canvas.width;
    const height = canvas.height;

    // Create a copy of pixel data to read from, ensuring glitches are based on the state before this iteration's effects.
    const originalPixels = new Uint8ClampedArray(pixels);

    const intensityFactor = intensity / 100; // Normalized intensity (0.0 to 1.0)
    const intensityFactorSqrt = Math.sqrt(intensityFactor); // Used for non-linear scaling of some effects

    // Number of glitch iterations/bands. More intensity = more glitches.
    const glitchIterations = Math.floor(intensityFactor * 50 + 10); // Min 10, Max 60 iterations

    for (let i = 0; i < glitchIterations; i++) {
        const startY = Math.floor(Math.random() * height);
        
        // Determine the height of this glitch strip. Scales with intensity.
        // Max ~2% of image height at full intensity for a single strip. Min 1 pixel.
        const stripHeightMaxRandomPart = (height * 0.01 * (intensityFactor / 0.5) + 1); // (intensityFactor / 0.5) normalizes intensityFactor to 0-2 range
        const stripHeight = Math.floor(Math.random() * stripHeightMaxRandomPart) + 1;

        // Determine the horizontal shift for this strip. Scales with maxHorizontalShift and intensity.
        const currentActualShift = Math.floor((Math.random() - 0.5) * 2 * maxHorizontalShift * intensityFactorSqrt);
        
        // Probabilities for applying color distortion and noise within this strip
        const applyColorDistortionThisStrip = Math.random() < colorDistortion;
        const applyNoiseThisStrip = Math.random() < noiseLevel;

        // Determine color channel separation if color distortion is applied.
        // Scales with colorDistortion parameter and intensity. Max ~8px at Nfull intensity/distortion.
        const channelSeparation = applyColorDistortionThisStrip ? 
                                  Math.floor(Math.random() * 8 * colorDistortion * intensityFactorSqrt + 1) : 0;

        for (let y = startY; y < Math.min(startY + stripHeight, height); y++) {
            for (let x = 0; x < width; x++) {
                const targetIdx = (y * width + x) * 4;

                // Determine base source X coordinate after applying the horizontal shift for this strip.
                // Uses a robust modulo operation for potentially negative intermediate results.
                const x_content_src_base = ((x - currentActualShift) % width + width) % width;

                let r_final, g_final, b_final, a_final;

                if (applyColorDistortionThisStrip) {
                    // Apply chromatic aberration like effect by sampling R, G, B channels from slightly different source pixels
                    const r_src_x = Math.max(0, Math.min(width - 1, x_content_src_base - channelSeparation));
                    const g_src_x = x_content_src_base; // Green channel from the central (shifted) position
                    const b_src_x = Math.max(0, Math.min(width - 1, x_content_src_base + channelSeparation));

                    r_final = originalPixels[(y * width + r_src_x) * 4];
                    g_final = originalPixels[(y * width + g_src_x) * 4 + 1];
                    b_final = originalPixels[(y * width + b_src_x) * 4 + 2];
                    a_final = originalPixels[(y * width + g_src_x) * 4 + 3]; // Alpha from the central source pixel
                } else {
                    // No color distortion, just use the pixel from the shifted horizontal position
                    const srcIdx = (y * width + x_content_src_base) * 4;
                    r_final = originalPixels[srcIdx];
                    g_final = originalPixels[srcIdx + 1];
                    b_final = originalPixels[srcIdx + 2];
                    a_final = originalPixels[srcIdx + 3];
                }

                if (applyNoiseThisStrip) {
                    // Add random noise to R, G, B channels
                    // Noise magnitude scales with noiseLevel parameter. Max +/-30 (out of 255) at noiseLevel=1.0.
                    const noiseMagnitude = (Math.random() - 0.5) * 60 * noiseLevel; 
                    r_final = Math.max(0, Math.min(255, r_final + noiseMagnitude));
                    g_final = Math.max(0, Math.min(255, g_final + noiseMagnitude));
                    b_final = Math.max(0, Math.min(255, b_final + noiseMagnitude));
                }
                
                pixels[targetIdx]     = r_final;
                pixels[targetIdx + 1] = g_final;
                pixels[targetIdx + 2] = b_final;
                pixels[targetIdx + 3] = a_final; // Preserve alpha from the (potentially shifted) source
            }
        }
    }

    // Static Blocks: Overwrite parts of the image with solid color blocks or B/W static
    // Number of static blocks scales with intensity. Max ~3 blocks at full intensity.
    const numStaticBlocks = Math.floor(Math.random() * (intensityFactor * 3)); 
    for (let k = 0; k < numStaticBlocks; k++) {
        const blockX = Math.floor(Math.random() * width * 0.9); // Start block within most of the image
        const blockY = Math.floor(Math.random() * height * 0.9);

        // Block size also somewhat scales with intensity.
        const blockMaxWidth = width * 0.20 * intensityFactor + width * 0.05; // Max 20% width from intensity + 5% base width
        const blockMaxHeight = height * 0.03 * intensityFactor + height * 0.01; // Max 3% height from intensity + 1% base height
        
        const minBlockWidth = width * 0.01; // Min 1% width
        const minBlockHeight = Math.max(1, height * 0.005); // Min 0.5% height, at least 1px

        const currentBlockWidth = Math.floor(Math.random() * blockMaxWidth + minBlockWidth);
        const currentBlockHeight = Math.floor(Math.random() * blockMaxHeight + minBlockHeight);

        const isColorBlock = Math.random() > 0.4; // 60% chance of a colored block, 40% B/W static
        
        let R_block, G_block, B_block;
        if (isColorBlock) {
            // For color blocks, tend towards darker, primary/secondary "glitchy" colors
            R_block = (Math.random() > 0.6 ? Math.floor(Math.random() * 150) : 0);
            G_block = (Math.random() > 0.6 ? Math.floor(Math.random() * 150) : 0);
            B_block = (Math.random() > 0.6 ? Math.floor(Math.random() * 150) : 0);
        } else {
            // For B/W static, simple black or white pixels
            const c = Math.random() > 0.5 ? 255 : 0;
            R_block = c; G_block = c; B_block = c;
        }

        for (let y = blockY; y < Math.min(blockY + currentBlockHeight, height); y++) {
            for (let x = blockX; x < Math.min(blockX + currentBlockWidth, width); x++) {
                const idx = (y * width + x) * 4;
                pixels[idx]     = R_block;
                pixels[idx + 1] = G_block;
                pixels[idx + 2] = B_block;
                // Static blocks are typically opaque. Preserve transparency only if original area was fully transparent.
                if (originalPixels[idx+3] > 0) { 
                     pixels[idx+3] = 255; // Make opaque over non-transparent original parts
                } else {
                     pixels[idx+3] = 0; // Preserve full transparency
                }
            }
        }
    }

    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 Signal Loss Filter Effect Tool allows users to apply artistic signal loss effects to their images, emulating a glitch or distortion aesthetic. Users can customize the intensity of the effects, introduce horizontal shifts, and add color distortions and noise to create unique visuals. This tool is ideal for artists, graphic designers, and content creators looking to enhance their images with a retro or digitally corrupted look for use in digital art, social media posts, or graphic design projects.

Leave a Reply

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