Please bookmark this page to avoid losing your image tool!

Photo Shattered Glass Effect Generator

(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, numShards = 150, maxDisplacement = 10, maxRotation = 10, edgeDarkeningFactor = 0.2, edgeStrokeWidth = 1) {
    // Check image dimensions
    const w = originalImg.naturalWidth || originalImg.width;
    const h = originalImg.naturalHeight || originalImg.height;

    const outputCanvas = document.createElement('canvas');
    const ctx = outputCanvas.getContext('2d');

    if (w === 0 || h === 0) {
        console.error("Image has zero width or height.");
        outputCanvas.width = Math.max(1, w); // Ensure canvas has at least 1x1
        outputCanvas.height = Math.max(1, h);
        // Return an empty transparent canvas
        ctx.clearRect(0,0,outputCanvas.width, outputCanvas.height);
        return outputCanvas;
    }

    outputCanvas.width = w;
    outputCanvas.height = h;
    
    // Source canvas for reliable pixel sampling
    const sourceCanvas = document.createElement('canvas');
    sourceCanvas.width = w;
    sourceCanvas.height = h;
    const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });
    sourceCtx.drawImage(originalImg, 0, 0, w, h);

    ctx.clearRect(0, 0, w, h); // Ensure output canvas is clear

    // --- Delaunay Library Loading ---
    let DelaunayConstructor;

    // Helper to wait for a global variable to be defined
    function waitForGlobal(varName, timeout = 5000) {
        return new Promise((resolve, reject) => {
            let attempts = 0;
            const intervalTime = 100;
            const maxAttempts = timeout / intervalTime;
            const interval = setInterval(() => {
                if (typeof window[varName] !== 'undefined') {
                    clearInterval(interval);
                    resolve(window[varName]);
                } else if (typeof d3 !== 'undefined' && typeof d3[varName] !== 'undefined') { // Special case for d3.Delaunay
                    clearInterval(interval);
                    resolve(d3[varName]);
                }
                else if (attempts++ > maxAttempts) {
                    clearInterval(interval);
                    reject(new Error(`Global ${varName} not found after ${timeout}ms`));
                }
            }, intervalTime);
        });
    }
    
    // Helper to load a script
    function loadScript(src, globalToWaitFor) {
        return new Promise(async (resolve, reject) => {
            if (document.querySelector(`script[src="${src}"]`)) {
                // Script tag already exists, just wait for the global
                try {
                    const lib = await waitForGlobal(globalToWaitFor || (src.includes('d3.v7.min.js') ? 'd3' : 'Delaunay'));
                    resolve(lib);
                } catch (e) {
                    reject(e);
                }
                return;
            }
            const script = document.createElement('script');
            script.src = src;
            script.async = true;
            script.onload = async () => {
                script.remove();
                try {
                    const lib = await waitForGlobal(globalToWaitFor || (src.includes('d3.v7.min.js') ? 'd3' : 'Delaunay'));
                    resolve(lib);
                } catch (e) {
                    reject(e);
                }
            };
            script.onerror = (err) => {
                script.remove();
                reject(new Error(`Failed to load script: ${src}. Error: ${err}`));
            };
            document.head.appendChild(script);
        });
    }

    if (typeof d3 !== 'undefined' && typeof d3.Delaunay !== 'undefined') {
        DelaunayConstructor = d3.Delaunay;
    } else if (typeof window.Delaunay !== 'undefined') {
        DelaunayConstructor = window.Delaunay;
    } else {
        try {
            // Try loading full D3 (which includes Delaunay)
            await loadScript('https://d3js.org/d3.v7.min.js', 'Delaunay'); // d3.v7 sets d3.Delaunay
             if (typeof d3 !== 'undefined' && typeof d3.Delaunay !== 'undefined') {
                DelaunayConstructor = d3.Delaunay;
            } else {
                 throw new Error('d3.Delaunay not found after loading d3.v7.min.js');
            }
        } catch (e1) {
            console.warn("Failed to load D3 with Delaunay (d3.v7.min.js), trying standalone d3-delaunay.js. Reason:", e1.message);
            try {
                // Standalone d3-delaunay UMD build exposes global `Delaunay`
                DelaunayConstructor = await loadScript('https://cdn.jsdelivr.net/npm/d3-delaunay@6', 'Delaunay');
            } catch (e2) {
                console.error("Failed to load any Delaunay library. Reason:", e2.message);
                ctx.drawImage(originalImg, 0, 0, w, h); // Fallback
                return outputCanvas;
            }
        }
    }

    if (!DelaunayConstructor) {
        console.error("Delaunay library could not be initialized. Drawing original image.");
        ctx.drawImage(originalImg, 0, 0, w, h);
        return outputCanvas;
    }
    // --- End Delaunay Library Loading ---


    // Generate points for triangulation
    const numPointsForTriangulation = Math.max(20, Math.floor(numShards * 0.75)); 

    const points = [];
    // Add corners (use w-1, h-1 for strict bounds, important for pixel sampling)
    points.push([0, 0], [w - 1, 0], [0, h - 1], [w - 1, h - 1]);
    
    const numEdgePoints = Math.max(4, Math.floor(Math.sqrt(numPointsForTriangulation)));
    for (let i = 0; i < numEdgePoints; i++) {
        points.push([Math.random() * (w - 1), 0]);             // Top edge
        points.push([Math.random() * (w - 1), h - 1]);         // Bottom edge
        points.push([0, Math.random() * (h - 1)]);             // Left edge
        points.push([w - 1, Math.random() * (h - 1)]);         // Right edge
    }
    
    const currentPointsCount = points.length;
    const remainingPointsNeeded = numPointsForTriangulation - currentPointsCount;
    if (remainingPointsNeeded > 0) {
        for (let i = 0; i < remainingPointsNeeded; i++) {
            points.push([Math.random() * (w - 1), Math.random() * (h - 1)]);
        }
    }
    
    // Ensure at least 3 unique points for Delaunay triangulation
    const uniquePoints = Array.from(new Set(points.map(p => p.join(',')))).map(s => s.split(',').map(Number));
    if (uniquePoints.length < 3) {
         // Add a few more distinct points if necessary
        uniquePoints.push([w/3, h/3], [2*w/3, h/3], [w/2, 2*h/3]);
        const finalUniquePoints = Array.from(new Set(uniquePoints.map(p => p.join(',')))).map(s => s.split(',').map(Number));
        if (finalUniquePoints.length < 3) {
            console.error("Not enough unique points for triangulation even after adding more. Drawing original image.");
            ctx.drawImage(originalImg, 0, 0, w, h);
            return outputCanvas;
        }
         // Use the augmented unique points
        for(let i = 0; i < finalUniquePoints.length; ++i) points[i] = finalUniquePoints[i]; // Truncate or replace start
        points.length = finalUniquePoints.length; // ensure points array is only unique points
    }


    const delaunay = DelaunayConstructor.from(points);
    const triangles = delaunay.triangles; 

    for (let i = 0; i < triangles.length; i += 3) {
        const p1Idx = triangles[i];
        const p2Idx = triangles[i+1];
        const p3Idx = triangles[i+2];

        if (p1Idx === undefined || p2Idx === undefined || p3Idx === undefined ||
            !points[p1Idx] || !points[p2Idx] || !points[p3Idx]) {
            // console.warn("Skipping invalid triangle indices from Delaunay output.");
            continue;
        }

        const v1 = { x: points[p1Idx][0], y: points[p1Idx][1] };
        const v2 = { x: points[p2Idx][0], y: points[p2Idx][1] };
        const v3 = { x: points[p3Idx][0], y: points[p3Idx][1] };

        const centroidX = (v1.x + v2.x + v3.x) / 3;
        const centroidY = (v1.y + v2.y + v3.y) / 3;

        const sampleX = Math.max(0, Math.min(w - 1, Math.floor(centroidX)));
        const sampleY = Math.max(0, Math.min(h - 1, Math.floor(centroidY)));
        
        const pixel = sourceCtx.getImageData(sampleX, sampleY, 1, 1).data;
        const r = pixel[0], g = pixel[1], b = pixel[2], a = pixel[3] / 255;
        const fillStyle = `rgba(${r},${g},${b},${a})`;

        const rotationAngleRad = (Math.random() * 2 - 1) * maxRotation * (Math.PI / 180);
        const offsetX = (Math.random() * 2 - 1) * maxDisplacement;
        const offsetY = (Math.random() * 2 - 1) * maxDisplacement;

        ctx.save();
        ctx.translate(centroidX + offsetX, centroidY + offsetY);
        ctx.rotate(rotationAngleRad);
        
        // Draw triangle vertices relative to its original centroid, which is now at (0,0)
        // in the local transformed coordinate system.
        ctx.beginPath();
        ctx.moveTo(v1.x - centroidX, v1.y - centroidY);
        ctx.lineTo(v2.x - centroidX, v2.y - centroidY);
        ctx.lineTo(v3.x - centroidX, v3.y - centroidY);
        ctx.closePath();

        ctx.fillStyle = fillStyle;
        ctx.fill();

        if (edgeDarkeningFactor > 0 && edgeStrokeWidth > 0) {
            const strokeR = Math.max(0, Math.floor(r * (1 - edgeDarkeningFactor)));
            const strokeG = Math.max(0, Math.floor(g * (1 - edgeDarkeningFactor)));
            const strokeB = Math.max(0, Math.floor(b * (1 - edgeDarkeningFactor)));
            // Use original alpha for stroke to maintain shard transparency
            ctx.strokeStyle = `rgba(${strokeR},${strokeG},${strokeB},${a})`; 
            ctx.lineWidth = edgeStrokeWidth;
            ctx.stroke();
        }
        ctx.restore();
    }

    return outputCanvas;
}

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 Shattered Glass Effect Generator allows users to create a shattered glass effect on their images. By breaking the image into multiple shards and applying random displacement and rotation techniques, this tool transforms a standard photo into a creative shattered glass artwork. This can be particularly useful for graphic designers, artists, or anyone looking to add a dramatic and unique visual element to their images, such as for marketing materials, social media posts, or personal projects.

Leave a Reply

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