Please bookmark this page to avoid losing your image tool!

Image Black Hole Event Horizon 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, 
    centerXParam = null, 
    centerYParam = null, 
    eventHorizonRadiusParam = null, 
    strength = 0.1, 
    glowColorString = "rgba(255,200,0,0.7)", 
    glowWidthParam = null
) {
    // Helper function to parse color strings (rgba, rgb, hex)
    // Returns [r, g, b, a] where r,g,b are 0-255 and a is 0-1.
    function _parseColor(colorStrIn) {
        let r = 0, g = 0, b = 0, a = 1.0; // Default to opaque black
        const colorStr = String(colorStrIn).trim();

        if (colorStr.startsWith("rgba(")) {
            try {
                const parts = colorStr.substring(5, colorStr.length - 1).split(',');
                r = parseInt(parts[0].trim(), 10);
                g = parseInt(parts[1].trim(), 10);
                b = parseInt(parts[2].trim(), 10);
                a = parseFloat(parts[3].trim());
            } catch (e) { console.warn("Image Black Hole: Could not parse rgba color:", colorStr, e); return [0,0,0,0]; }
        } else if (colorStr.startsWith("rgb(")) {
            try {
                const parts = colorStr.substring(4, colorStr.length - 1).split(',');
                r = parseInt(parts[0].trim(), 10);
                g = parseInt(parts[1].trim(), 10);
                b = parseInt(parts[2].trim(), 10);
                a = 1.0;
            } catch (e) { console.warn("Image Black Hole: Could not parse rgb color:", colorStr, e); return [0,0,0,0]; }
        } else if (colorStr.startsWith("#")) {
            let hex = colorStr.substring(1);
            try {
                if (hex.length === 3) { // #RGB
                    hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
                } else if (hex.length === 4) { // #RGBA
                     hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
                }
                // Now hex should be 6 or 8 characters long
                if (hex.length === 8) { // #RRGGBBAA
                    a = parseInt(hex.substring(6, 8), 16) / 255.0;
                    hex = hex.substring(0, 6); // For RGB parsing
                } else if (hex.length === 6) { // #RRGGBB
                    a = 1.0; 
                } else {
                    console.warn("Image Black Hole: Invalid hex color string length (input: "+colorStrIn+"): resolved hex '"+hex+"' not 6 or 8 chars");
                    return [0,0,0,0];
                }
                
                r = parseInt(hex.substring(0, 2), 16);
                g = parseInt(hex.substring(2, 4), 16);
                b = parseInt(hex.substring(4, 6), 16);
            } catch (e) { console.warn("Image Black Hole: Could not parse hex color:", colorStr, e); return [0,0,0,0]; }
        } else {
            console.warn("Image Black Hole: Unknown color string format:", colorStr, ". Defaulting to transparent black.");
            return [0,0,0,0]; 
        }

        r = Math.max(0, Math.min(255, isNaN(r) ? 0 : r));
        g = Math.max(0, Math.min(255, isNaN(g) ? 0 : g));
        b = Math.max(0, Math.min(255, isNaN(b) ? 0 : b));
        a = Math.max(0, Math.min(1.0, isNaN(a) ? 0 : a));
        
        return [r, g, b, a];
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    
    const W = originalImg.naturalWidth || originalImg.width;
    const H = originalImg.naturalHeight || originalImg.height;
    canvas.width = W;
    canvas.height = H;

    let centerX, centerY, eventHorizonRadius, glowWidth;

    if (typeof centerXParam === 'string' && centerXParam.endsWith('%')) {
        centerX = W * (parseFloat(centerXParam) / 100);
    } else if (typeof centerXParam === 'number') {
        centerX = centerXParam;
    } else {
        centerX = W / 2;
    }

    if (typeof centerYParam === 'string' && centerYParam.endsWith('%')) {
        centerY = H * (parseFloat(centerYParam) / 100);
    } else if (typeof centerYParam === 'number') {
        centerY = centerYParam;
    } else {
        centerY = H / 2;
    }
    
    if (typeof eventHorizonRadiusParam === 'string' && eventHorizonRadiusParam.endsWith('%')) {
        eventHorizonRadius = Math.min(W, H) * (parseFloat(eventHorizonRadiusParam) / 100);
    } else if (typeof eventHorizonRadiusParam === 'number') {
        eventHorizonRadius = eventHorizonRadiusParam;
    } else {
        eventHorizonRadius = Math.min(W, H) / 8;
    }
    
    if (eventHorizonRadius <= 0) eventHorizonRadius = 1; 

    if (typeof glowWidthParam === 'string' && glowWidthParam.endsWith('%')) {
        glowWidth = eventHorizonRadius * (parseFloat(glowWidthParam) / 100);
    } else if (typeof glowWidthParam === 'number') {
        glowWidth = glowWidthParam;
    } else {
        glowWidth = eventHorizonRadius * 0.3;
    }
    if (glowWidth < 0) glowWidth = 0;

    ctx.drawImage(originalImg, 0, 0, W, H);
    const originalImageData = ctx.getImageData(0, 0, W, H);
    const outputImageData = ctx.createImageData(W, H);

    const [glowR, glowG, glowB, glowA_color] = _parseColor(glowColorString);

    function _getPixelClamped(imgData, x, y) {
        const ix = Math.max(0, Math.min(imgData.width - 1, Math.floor(x)));
        const iy = Math.max(0, Math.min(imgData.height - 1, Math.floor(y)));
        const i = (iy * imgData.width + ix) * 4;
        return [imgData.data[i], imgData.data[i+1], imgData.data[i+2], imgData.data[i+3]];
    }

    function _getBilinearPixel(imgData, x, y) {
        const x0 = Math.floor(x);
        const y0 = Math.floor(y);
        
        // Speed up if way out of bounds for the first corner (x0,y0)
        if (x0 < -1 || x0 > imgData.width || y0 < -1 || y0 > imgData.height) { 
             return [0, 0, 0, 0]; 
        }

        const p00 = _getPixelClamped(imgData, x0, y0);
        const p10 = _getPixelClamped(imgData, x0 + 1, y0);
        const p01 = _getPixelClamped(imgData, x0, y0 + 1);
        const p11 = _getPixelClamped(imgData, x0 + 1, y0 + 1);

        const tx = x - x0;
        const ty = y - y0;

        const res = [0,0,0,0];
        for (let i=0; i<4; ++i) {
            res[i] = Math.round(
                p00[i]*(1-tx)*(1-ty) + 
                p10[i]*tx*(1-ty) + 
                p01[i]*(1-tx)*ty + 
                p11[i]*tx*ty
            );
        }
        return res;
    }
    
    for (let y_coord = 0; y_coord < H; y_coord++) {
        for (let x_coord = 0; x_coord < W; x_coord++) {
            const dx = x_coord - centerX;
            const dy = y_coord - centerY;
            const distance = Math.sqrt(dx * dx + dy * dy);
            
            let r_out=0, g_out=0, b_out=0, a_out=255;

            if (distance <= eventHorizonRadius) {
                // Pixel is inside the event horizon, remains black (or values set above)
            } else {
                // Pixel is outside the event horizon
                const r_eff = distance; 
                const source_r = r_eff + strength * eventHorizonRadius * (eventHorizonRadius / r_eff);
                
                const angle = Math.atan2(dy, dx);
                const source_x = centerX + source_r * Math.cos(angle);
                const source_y = centerY + source_r * Math.sin(angle);

                const distortedPixelColor = _getBilinearPixel(originalImageData, source_x, source_y);
                let current_r = distortedPixelColor[0];
                let current_g = distortedPixelColor[1];
                let current_b = distortedPixelColor[2];
                let current_a = distortedPixelColor[3]; // 0-255

                // Apply glow if applicable
                if (glowWidth > 0 && distance <= eventHorizonRadius + glowWidth) {
                    let glowIntensityFactor = 1.0 - (distance - eventHorizonRadius) / glowWidth;
                    glowIntensityFactor = Math.max(0, Math.min(1, glowIntensityFactor));
                    // glowIntensityFactor = glowIntensityFactor * glowIntensityFactor; // Optional: quadratic falloff for glow

                    const alphaGlowEffective = glowIntensityFactor * glowA_color; // Overall opacity of glow layer at this pixel

                    // Blend RGB: C_out = C_glow * A_glow_eff + C_current * (1 - A_glow_eff)
                    r_out = Math.round(glowR * alphaGlowEffective + current_r * (1 - alphaGlowEffective));
                    g_out = Math.round(glowG * alphaGlowEffective + current_g * (1 - alphaGlowEffective));
                    b_out = Math.round(glowB * alphaGlowEffective + current_b * (1 - alphaGlowEffective));

                    // Blend Alpha: A_out = A_glow_eff + A_current_norm * (1 - A_glow_eff)
                    const currentAlphaNormalized = current_a / 255.0;
                    const finalAlphaNormalized = alphaGlowEffective + currentAlphaNormalized * (1 - alphaGlowEffective);
                    a_out = Math.round(finalAlphaNormalized * 255);
                } else {
                    // No glow, use distorted pixel color directly
                    r_out = current_r;
                    g_out = current_g;
                    b_out = current_b;
                    a_out = current_a;
                }
            }
            const i = (y_coord * W + x_coord) * 4;
            outputImageData.data[i]   = r_out;
            outputImageData.data[i+1] = g_out;
            outputImageData.data[i+2] = b_out;
            outputImageData.data[i+3] = a_out;
        }
    }

    ctx.putImageData(outputImageData, 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 Black Hole Event Horizon Filter Effect Tool allows users to apply a visually striking black hole effect to images. By simulating an event horizon and a glowing aura, this tool enables creative modifications of images for various use cases, such as enhancing artwork, creating unique graphics for social media, or adding a dramatic flair to personal photos. Users can customize the position and size of the black hole effect, along with the glowing color and strength, making it versatile for artistic expression.

Leave a Reply

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