Please bookmark this page to avoid losing your image tool!

Image Broken LCD 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.
async function processImage(originalImg, blockSize = 8, breakProbability = 0.1, deadPixelProbability = 0.05, colorShiftProbability = 0.2, scanlineOpacity = 0.2, scanlineWidth = 1) {
    
    // It's good practice to ensure the image is loaded, but for this problem,
    // we'll assume originalImg is a loaded Image object as per typical problem constraints.
    // If not, naturalWidth/Height might be 0.
    // A more robust solution would await originalImg.onload if needed.

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

    if (imgWidth === 0 || imgHeight === 0) {
        console.warn("Image Broken LCD Filter: Image dimensions are zero. Returning empty canvas.");
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = 1; 
        emptyCanvas.height = 1;
        // Optionally draw a pixel to make it non-blank
        const eCtx = emptyCanvas.getContext('2d');
        if (eCtx) eCtx.fillRect(0,0,1,1); 
        return emptyCanvas;
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = imgWidth;
    canvas.height = imgHeight;

    // Use a temporary canvas to get image data without affecting the main canvas prematurely.
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = imgWidth;
    tempCanvas.height = imgHeight;
    const tempCtx = tempCanvas.getContext('2d');
    
    if (!tempCtx) { // Fallback if context cannot be created
        console.error("Image Broken LCD Filter: Cannot get 2D context for temp canvas.");
        ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight); // Draw original image
        return canvas;
    }
    tempCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
    
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, imgWidth, imgHeight);
    } catch (e) {
        // This can happen if the image is tainted (e.g., cross-origin without CORS)
        console.error("Image Broken LCD Filter: Error getting image data (possibly tainted canvas):", e);
        ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight); // Draw original as fallback
        
        // Optionally, add a text overlay indicating the issue if context is available on main canvas
        if (ctx) {
            ctx.fillStyle = "rgba(255,0,0,0.7)";
            ctx.font = "16px Arial";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillText("Error: Could not process image (cross-origin?)", imgWidth / 2, imgHeight / 2);
        }
        return canvas;
    }
    const data = imageData.data;

    // Sanitize blockSize
    blockSize = Math.max(1, Math.floor(blockSize));

    for (let y = 0; y < imgHeight; y += blockSize) {
        for (let x = 0; x < imgWidth; x += blockSize) {
            const currentBlockWidth = Math.min(blockSize, imgWidth - x);
            const currentBlockHeight = Math.min(blockSize, imgHeight - y);

            let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
            let numPixels = 0;

            // Calculate average color for the block
            for (let blockY = 0; blockY < currentBlockHeight; blockY++) {
                for (let blockX = 0; blockX < currentBlockWidth; blockX++) {
                    const sourceX = x + blockX;
                    const sourceY = y + blockY;
                    // Ensure we are within image bounds (primarily for the last blocks)
                    if (sourceX < imgWidth && sourceY < imgHeight) { 
                        const index = (sourceY * imgWidth + sourceX) * 4;
                        rSum += data[index];
                        gSum += data[index + 1];
                        bSum += data[index + 2];
                        aSum += data[index + 3];
                        numPixels++;
                    }
                }
            }

            let avgR = numPixels > 0 ? rSum / numPixels : 0;
            let avgG = numPixels > 0 ? gSum / numPixels : 0;
            let avgB = numPixels > 0 ? bSum / numPixels : 0;
            let avgA = numPixels > 0 ? aSum / numPixels : 255; // Default to opaque if no pixels

            let displayR = avgR;
            let displayG = avgG;
            let displayB = avgB;
            let displayA = avgA;

            // Apply "broken" effects sequentially (order matters)
            if (Math.random() < deadPixelProbability) { // Chance of dead pixel
                displayR = 0; 
                displayG = 0; 
                displayB = 0;
                // Alpha for dead pixels: use avgA to respect original transparency.
                // Alternatively, displayA = (avgA > 0) ? 255 : 0; for opaque black if not fully transparent.
            } else if (Math.random() < colorShiftProbability) { // Chance of color shift (if not dead)
                const shiftType = Math.floor(Math.random() * 3);
                if (shiftType === 0) { // Swap R and B
                    [displayR, displayB] = [displayB, displayR];
                } else if (shiftType === 1) { // Boost R, dim G and B slightly
                    displayR = Math.min(255, displayR * 1.5);
                    displayG = displayG * 0.6;
                    displayB = displayB * 0.8;
                } else { // Boost G, dim R and B slightly
                    displayG = Math.min(255, displayG * 1.5);
                    displayR = displayR * 0.6;
                    displayB = displayB * 0.8;
                }
            } else if (Math.random() < breakProbability) { // Chance of general break (if not dead or color-shifted)
                const breakStyle = Math.random();
                if (breakStyle < 0.4) { // Random solid color block
                    displayR = Math.random() * 255;
                    displayG = Math.random() * 255;
                    displayB = Math.random() * 255;
                } else if (breakStyle < 0.7) { // Desaturate or monochrome tint
                    const gray = avgR * 0.299 + avgG * 0.587 + avgB * 0.114;
                    if (Math.random() < 0.5) { // Desaturate towards gray
                        displayR = (avgR + gray) / 2;
                        displayG = (avgG + gray) / 2;
                        displayB = (avgB + gray) / 2;
                    } else { // Apply a monochrome tint (e.g., greenish)
                        displayR = gray * (0.3 + Math.random() * 0.4); // Random tint intensity R
                        displayG = gray * (0.7 + Math.random() * 0.5); // Random tint intensity G (more green)
                        displayB = gray * (0.3 + Math.random() * 0.4); // Random tint intensity B
                    }
                } else { // Add noise to the average color
                    const noiseAmount = 70; // Max deviation for noise
                    displayR = avgR + (Math.random() - 0.5) * noiseAmount;
                    displayG = avgG + (Math.random() - 0.5) * noiseAmount;
                    displayB = avgB + (Math.random() - 0.5) * noiseAmount;
                }
            }
            
            // Clamp final RGB values to 0-255 range
            displayR = Math.max(0, Math.min(255, displayR));
            displayG = Math.max(0, Math.min(255, displayG));
            displayB = Math.max(0, Math.min(255, displayB));

            ctx.fillStyle = `rgba(${Math.floor(displayR)}, ${Math.floor(displayG)}, ${Math.floor(displayB)}, ${displayA / 255})`;
            ctx.fillRect(x, y, currentBlockWidth, currentBlockHeight);
        }
    }

    // Draw scan lines on top
    if (scanlineOpacity > 0 && scanlineWidth > 0 && blockSize > 1) { // Scanlines make sense if blocks are larger than 1px
        ctx.fillStyle = `rgba(0, 0, 0, ${scanlineOpacity})`;
        const actualScanlineWidth = Math.max(1, Math.floor(scanlineWidth));

        // Horizontal scan lines (between blocks)
        for (let i = blockSize; i < imgHeight; i += blockSize) {
            ctx.fillRect(0, i - Math.floor(actualScanlineWidth / 2), imgWidth, actualScanlineWidth);
        }
        // Vertical scan lines (between blocks)
        for (let j = blockSize; j < imgWidth; j += blockSize) {
            ctx.fillRect(j - Math.floor(actualScanlineWidth / 2), 0, actualScanlineWidth, imgHeight);
        }
    }

    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 Broken LCD Filter Effect Tool allows users to apply a unique artistic filter to images that simulates the appearance of a broken LCD screen. This tool introduces visual elements such as dead pixels, color shifts, and noise to enhance the creative style of images. Users can customize parameters like block size, probability of distortions, and scanline effects to achieve their desired aesthetic. This tool can be used in graphic design, digital art, social media content creation, or simply for fun to transform photos into stylized works reminiscent of malfunctioning display screens.

Leave a Reply

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