Please bookmark this page to avoid losing your image tool!

Image Stained Glass Filter Application

(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, cellSize = 30, borderWidth = 2, borderColor = "black", seedRandomness = 0.8) {
    const imgWidth = originalImg.width;
    const imgHeight = originalImg.height;

    // Ensure parameters are valid
    cellSize = Math.max(1, parseFloat(cellSize));
    borderWidth = Math.max(0, parseFloat(borderWidth));
    seedRandomness = Math.max(0, Math.min(1, parseFloat(seedRandomness))); // Clamp seedRandomness to [0,1]

    // Handle zero-dimension images
    if (imgWidth === 0 || imgHeight === 0) {
        const canvas = document.createElement('canvas');
        canvas.width = imgWidth;
        canvas.height = imgHeight;
        return canvas;
    }

    // Create a temporary canvas to get pixel data from originalImg
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = imgWidth;
    tempCanvas.height = imgHeight;
    const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
    tempCtx.drawImage(originalImg, 0, 0);

    // Generate seed points
    const seeds = [];
    const gridSeedMap = new Map(); 
    let seedIdCounter = 0;

    // Loop to generate seeds, ensuring at least one row/column of seeds if image dimensions are > 0
    for (let gy = 0; (gy * cellSize < imgHeight) || (gy === 0 && imgHeight > 0) ; gy++) {
        const cellYStart = gy * cellSize;
        for (let gx = 0; (gx * cellSize < imgWidth) || (gx === 0 && imgWidth > 0) ; gx++) {
            const cellXStart = gx * cellSize;
            
            const cellCenterX = cellXStart + cellSize / 2;
            const cellCenterY = cellYStart + cellSize / 2;

            const randomOffsetX = (Math.random() - 0.5) * cellSize * seedRandomness;
            const randomOffsetY = (Math.random() - 0.5) * cellSize * seedRandomness;

            let seedX = cellCenterX + randomOffsetX;
            let seedY = cellCenterY + randomOffsetY;

            seedX = Math.max(0, Math.min(imgWidth - 1, seedX));
            seedY = Math.max(0, Math.min(imgHeight - 1, seedY));

            const pixelData = tempCtx.getImageData(Math.floor(seedX), Math.floor(seedY), 1, 1).data;
            const color = { r: pixelData[0], g: pixelData[1], b: pixelData[2] };

            const seed = { x: seedX, y: seedY, color: color, id: seedIdCounter++, gx: gx, gy: gy };
            seeds.push(seed);
            const gridKey = `${gx}_${gy}`;
            gridSeedMap.set(gridKey, seed);
            
            if (gx * cellSize >= imgWidth && gx > 0) break; // Optimization: break if past image width
            if (gx === 0 && imgWidth === 0 && imgHeight > 0) break; // Edge case 0-width image
            if (gx === 0 && !(imgWidth > 0)) break; // Ensure loop runs once for gx=0 if imgWidth > 0
        }
        if (gy * cellSize >= imgHeight && gy > 0) break; // Optimization: break if past image height
        if (gy === 0 && imgHeight === 0 && imgWidth > 0) break; // Edge case 0-height image
        if (gy === 0 && !(imgHeight > 0)) break; // Ensure loop runs once for gy=0 if imgHeight > 0

    }
    
    // Create output canvas
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = imgWidth;
    outputCanvas.height = imgHeight;
    const outputCtx = outputCanvas.getContext('2d');

    const outputImageData = outputCtx.createImageData(imgWidth, imgHeight);
    const data = outputImageData.data;
    const pixelSeedIdMap = new Array(imgWidth * imgHeight);

    // This check is crucial if image dimensions are smaller than cellSize, seed loops might not run as expected by simple W/cellSize formula
    // However, the loop conditions `|| (gX ===0 && imgWidth > 0)` should ensure seed generation.
    // If somehow no seeds were generated for a valid image, this would be an issue.
    if (seeds.length === 0 && (imgWidth > 0 && imgHeight > 0)) {
         // This indicates an issue with seed generation logic for small images. Add a central seed.
        console.warn("No seeds generated, adding a central fallback seed.");
        const fallbackSeedX = Math.floor(imgWidth / 2);
        const fallbackSeedY = Math.floor(imgHeight / 2);
        const pixelData = tempCtx.getImageData(fallbackSeedX, fallbackSeedY, 1, 1).data;
        const color = { r: pixelData[0], g: pixelData[1], b: pixelData[2] };
        const seed = { x: fallbackSeedX, y: fallbackSeedY, color: color, id: seedIdCounter++, gx: 0, gy: 0 };
        seeds.push(seed);
        gridSeedMap.set(`0_0`, seed);
    }


    for (let y = 0; y < imgHeight; y++) {
        for (let x = 0; x < imgWidth; x++) {
            const currentPixelGx = Math.floor(x / cellSize);
            const currentPixelGy = Math.floor(y / cellSize);
            
            let minDistSq = Infinity;
            let bestSeed = null;

            for (let dgy = -1; dgy <= 1; dgy++) {
                for (let dgx = -1; dgx <= 1; dgx++) {
                    const checkGx = currentPixelGx + dgx;
                    const checkGy = currentPixelGy + dgy;
                    const key = `${checkGx}_${checkGy}`;
                    const seed = gridSeedMap.get(key);

                    if (seed) {
                        const dxSeed = seed.x - x;
                        const dySeed = seed.y - y;
                        const distSq = dxSeed * dxSeed + dySeed * dySeed;
                        if (distSq < minDistSq) {
                            minDistSq = distSq;
                            bestSeed = seed;
                        }
                    }
                }
            }
            
            if (!bestSeed && seeds.length > 0) { 
                // Fallback: if the 3x3 search fails (theoretically shouldn't if seeds are within their cells)
                // or if a pixel is outside all known seed grid cells (e.g. cellSize computation issue)
                // search all seeds (slower, but robust for edge cases or high randomness beyond cell bounds)
                for (const seed of seeds) {
                    const dxSeed = seed.x - x;
                    const dySeed = seed.y - y;
                    const distSq = dxSeed * dxSeed + dySeed * dySeed;
                    if (distSq < minDistSq) {
                        minDistSq = distSq;
                        bestSeed = seed;
                    }
                }
            }

            if (bestSeed) {
                const color = bestSeed.color;
                const index = (y * imgWidth + x) * 4;
                data[index]     = color.r;
                data[index + 1] = color.g;
                data[index + 2] = color.b;
                data[index + 3] = 255;
                pixelSeedIdMap[y * imgWidth + x] = bestSeed.id;
            } else { 
                // If no seed could be assigned (e.g., seeds array is empty, which shouldn't happen for valid images now)
                const index = (y * imgWidth + x) * 4;
                data[index] = data[index+1] = data[index+2] = 0; // Black
                data[index+3] = 0; // Transparent
            }
        }
    }
    outputCtx.putImageData(outputImageData, 0, 0);

    if (borderWidth > 0 && seeds.length > 0) {
        outputCtx.strokeStyle = borderColor;
        outputCtx.lineWidth = borderWidth;
        outputCtx.lineCap = "round"; 
        outputCtx.lineJoin = "round";

        outputCtx.beginPath();
        for (let y = 0; y < imgHeight; y++) {
            for (let x = 0; x < imgWidth; x++) {
                const currentPixelIndex = y * imgWidth + x;
                const currentSeedId = pixelSeedIdMap[currentPixelIndex];
                
                if (currentSeedId === undefined) continue; // Skip if pixel wasn't assigned a seed

                // Check right neighbor
                if (x < imgWidth - 1) {
                    const rightPixelIndex = y * imgWidth + (x + 1);
                    const rightSeedId = pixelSeedIdMap[rightPixelIndex];
                    if (rightSeedId !== undefined && currentSeedId !== rightSeedId) {
                        outputCtx.moveTo(x + 1, y); // Line is at junction of pixels
                        outputCtx.lineTo(x + 1, y + 1);
                    }
                }

                // Check bottom neighbor
                if (y < imgHeight - 1) {
                    const bottomPixelIndex = (y + 1) * imgWidth + x;
                    const bottomSeedId = pixelSeedIdMap[bottomPixelIndex];
                    if (bottomSeedId !== undefined && currentSeedId !== bottomSeedId) {
                        outputCtx.moveTo(x, y + 1);
                        outputCtx.lineTo(x + 1, y + 1);
                    }
                }
            }
        }
        outputCtx.stroke();
    }

    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 Image Stained Glass Filter Application allows users to transform images into a visually appealing stained glass effect. By adjusting parameters such as cell size, border width, and border color, users can create unique and artistic renditions of their images. This tool is ideal for enhancing photos for creative projects, creating art for social media, or producing custom designs for print. It provides a fun way to experiment with image effects and can be used by artists, designers, or anyone looking to add a touch of creativity to their visuals.

Leave a Reply

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