Please bookmark this page to avoid losing your image tool!

Image Disco Ball 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.
async function processImage(originalImg, tileSize = 20, gap = 2, curvature = 0.15, shineIntensity = 0.5, shineDensity = 0.1, gapColor = "black") {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

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

    // Ensure tileSize and gap are valid
    tileSize = Math.max(1, tileSize);
    gap = Math.max(0, gap);

    // Fill background for gaps
    ctx.fillStyle = gapColor;
    ctx.fillRect(0, 0, imgWidth, imgHeight);

    const imgCenterX = imgWidth / 2;
    const imgCenterY = imgHeight / 2;
    
    // Normalization factor for distortion effect. Using Math.max ensures points on the
    // larger dimension's edge correspond to a normalized distance of 1.
    // Add Math.max(1, ...) to prevent division by zero for 1x1 or smaller images.
    const normDistFactor = Math.max(1, imgCenterX, imgCenterY);

    const step = tileSize + gap;

    for (let y = 0; y < imgHeight; y += step) {
        for (let x = 0; x < imgWidth; x += step) {
            // Current tile's destination top-left corner on the canvas
            const tileDestX = x;
            const tileDestY = y;

            // Determine the actual size of the facet to draw, can be smaller at image edges
            const currentTileWidth = Math.min(tileSize, imgWidth - tileDestX);
            const currentTileHeight = Math.min(tileSize, imgHeight - tileDestY);

            // Skip if the tile is too small or completely outside (redundant with loop condition but safe)
            if (currentTileWidth <= 0 || currentTileHeight <= 0) {
                continue;
            }

            // Center of the current destination facet
            const facetCenterX_dest = tileDestX + currentTileWidth / 2;
            const facetCenterY_dest = tileDestY + currentTileHeight / 2;

            // Calculate normalized distance of facet center from image center
            const dx_norm = facetCenterX_dest - imgCenterX;
            const dy_norm = facetCenterY_dest - imgCenterY;
            const distFromImgCenter = Math.sqrt(dx_norm * dx_norm + dy_norm * dy_norm);
            
            let normalizedRadialPos = 0;
            if (normDistFactor > 0) { // normDistFactor is max(1, ...) so always > 0
                 normalizedRadialPos = distFromImgCenter / normDistFactor;
            }
            
            // Distortion factor for sampling source image
            // Positive curvature: pulls source sample towards center and shrinks it (barrel distortion reflection)
            // Negative curvature: pushes source sample outwards and expands it (pincushion distortion reflection)
            let radialShiftAndScale = 1 - curvature * normalizedRadialPos * normalizedRadialPos;
            radialShiftAndScale = Math.max(0.01, radialShiftAndScale); // Prevent zero/negative scale

            // Calculate source region center (shifted by distortion)
            const srcCenterX = imgCenterX + dx_norm * radialShiftAndScale;
            const srcCenterY = imgCenterY + dy_norm * radialShiftAndScale;

            // Calculate source region dimensions (scaled by distortion)
            const srcTotalWidth = currentTileWidth * radialShiftAndScale;
            const srcTotalHeight = currentTileHeight * radialShiftAndScale;

            // Calculate source region top-left before clipping
            const srcClipX = srcCenterX - srcTotalWidth / 2;
            const srcClipY = srcCenterY - srcTotalHeight / 2;

            // Clip source rectangle to be within originalImg bounds
            // Determine top-left of source after ensuring it's within [0, imgWidth/Height]
            const finalSrcX = Math.max(0, srcClipX);
            const finalSrcY = Math.max(0, srcClipY);

            // Calculate how much the source width/height needs to be adjusted
            // due to finalSrcX/Y being potentially different from srcClipX/Y
            const widthAdjustment = finalSrcX - srcClipX;
            const heightAdjustment = finalSrcY - srcClipY;

            const adjustedSrcWidth = srcTotalWidth - widthAdjustment;
            const adjustedSrcHeight = srcTotalHeight - heightAdjustment;
            
            // Ensure source width/height don't exceed image boundaries from finalSrcX/Y
            const clippedSrcWidth = Math.max(0.1, Math.min(adjustedSrcWidth, imgWidth - finalSrcX));
            const clippedSrcHeight = Math.max(0.1, Math.min(adjustedSrcHeight, imgHeight - finalSrcY));


            if (clippedSrcWidth < 0.1 || clippedSrcHeight < 0.1) {
                 continue; // Skip if source region is too small after clipping
            }
            
            ctx.drawImage(originalImg,
                finalSrcX, finalSrcY, clippedSrcWidth, clippedSrcHeight,
                tileDestX, tileDestY, currentTileWidth, currentTileHeight);

            // Add shine effect
            if (Math.random() < shineDensity && shineIntensity > 0) {
                const shineX = tileDestX + currentTileWidth / 2;
                const shineY = tileDestY + currentTileHeight / 2;
                const shineRadius = Math.min(currentTileWidth, currentTileHeight) / 2;

                if (shineRadius > 0) {
                    const shineGradient = ctx.createRadialGradient(
                        shineX, shineY, 0,
                        shineX, shineY, shineRadius
                    );
                    shineGradient.addColorStop(0, `rgba(255, 255, 255, ${shineIntensity})`);
                    shineGradient.addColorStop(0.8, `rgba(255, 255, 255, ${shineIntensity * 0.3})`);
                    shineGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
                    
                    ctx.fillStyle = shineGradient;
                    ctx.fillRect(tileDestX, tileDestY, currentTileWidth, currentTileHeight);
                }
            }
        }
    }
    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 Disco Ball Filter tool allows users to transform images into a visually captivating disco ball effect. By dividing the image into facets and applying distortion, shine, and color gaps, it creates a playful, reflective appearance. This tool is ideal for adding fun and vibrant effects to photos for use in social media, party invitations, or creative art projects, making any image stand out with a unique and festive flair.

Leave a Reply

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