Please bookmark this page to avoid losing your image tool!

Image Crystallize 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.
function processImage(originalImg, crystalSizeParam = 30) {
    // 1. Parameter normalization
    let numCrystalSize = Number(crystalSizeParam);
    if (isNaN(numCrystalSize) || numCrystalSize <= 0) { // Handles NaN from non-numeric string, or non-positive numeric values
        numCrystalSize = 30; // Fallback to a sensible default.
    }
    // Ensure crystalSize is at least 1 for sensible calculations.
    const _crystalSize = Math.max(1, numCrystalSize);

    // 2. Initial checks for the image object
    if (!originalImg || typeof originalImg.width !== 'number' || typeof originalImg.height !== 'number' || originalImg.width === 0 || originalImg.height === 0) {
        const errorCanvas = document.createElement('canvas');
        // Try to use original dimensions if available, otherwise 0x0
        errorCanvas.width = (originalImg && typeof originalImg.width === 'number') ? originalImg.width : 0;
        errorCanvas.height = (originalImg && typeof originalImg.height === 'number') ? originalImg.height : 0;
        // Optionally, you could draw a placeholder or error message on this canvas.
        // For now, it returns a canvas of correct (or zero) size.
        // If originalImg is valid but just 0x0, it will return a 0x0 canvas.
        if (errorCanvas.width > 0 && errorCanvas.height > 0) {
             const errorCtx = errorCanvas.getContext('2d');
             if (errorCtx) { // Check if context was obtained (e.g. for 0x0 canvas it might not)
                errorCtx.drawImage(originalImg, 0, 0); // Return original if params invalid but image ok
             }
        }
        return errorCanvas;
    }

    const width = originalImg.width;
    const height = originalImg.height;

    // 3. Canvas setup for input pixel data
    // Create an offscreen canvas to draw the original image and get its pixel data.
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = width;
    tempCanvas.height = height;
    const tempCtx = tempCanvas.getContext('2d');
    if (!tempCtx) { // Fallback if context cannot be created
        return originalImg; // Or an error canvas
    }
    tempCtx.drawImage(originalImg, 0, 0, width, height);
    let imageData;
    try {
        imageData = tempCtx.getImageData(0, 0, width, height);
    } catch (e) {
        // Security error (tainted canvas) or other issues.
        console.error("Error getting image data:", e);
        // Return original image drawn on a new canvas as a fallback attempt
        const fallbackCanvas = document.createElement('canvas');
        fallbackCanvas.width = width;
        fallbackCanvas.height = height;
        const fallbackCtx = fallbackCanvas.getContext('2d');
        if (fallbackCtx) fallbackCtx.drawImage(originalImg, 0, 0);
        return fallbackCanvas;
    }
    const pixels = imageData.data;

    // 4. Canvas setup for output
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = width;
    outputCanvas.height = height;
    const outputCtx = outputCanvas.getContext('2d');
    if (!outputCtx) {  // Fallback if context cannot be created
        return originalImg; // Or an error canvas
    }
    const outputImageData = outputCtx.createImageData(width, height);
    const outputPixels = outputImageData.data;

    // 5. Generate Seed Points based on a grid
    // These points will be the centers of the "crystals".
    const numGridCols = Math.ceil(width / _crystalSize);
    const numGridRows = Math.ceil(height / _crystalSize);
    const pointGrid = Array(numGridRows).fill(null).map(() => Array(numGridCols).fill(null));

    for (let gridY = 0; gridY < numGridRows; gridY++) {
        for (let gridX = 0; gridX < numGridCols; gridX++) {
            const cellOriginX = gridX * _crystalSize;
            const cellOriginY = gridY * _crystalSize;

            // Generate a random point within the conceptual grid cell.
            // The point's coordinates are relative to the image top-left (0,0).
            const randomOffsetX = Math.random() * _crystalSize;
            const randomOffsetY = Math.random() * _crystalSize;
            
            let pointX = cellOriginX + randomOffsetX;
            let pointY = cellOriginY + randomOffsetY;

            // Clamp point to be within image bounds. This is important for cells
            // at the edges that might be smaller than _crystalSize.
            pointX = Math.min(pointX, width - 1);
            pointY = Math.min(pointY, height - 1);
            pointX = Math.max(0, pointX); // Ensure non-negative
            pointY = Math.max(0, pointY); // Ensure non-negative

            const px = Math.floor(pointX); // Integer part for pixel lookup
            const py = Math.floor(pointY);

            const pixelIndex = (py * width + px) * 4;
            const color = [
                pixels[pixelIndex],
                pixels[pixelIndex + 1],
                pixels[pixelIndex + 2],
                pixels[pixelIndex + 3]
            ];
            
            pointGrid[gridY][gridX] = { x: pointX, y: pointY, color: color };
        }
    }
    
    // 6. Assign Pixels based on closest seed point in local neighborhood
    // For each pixel in the output image, find the closest seed point from the 3x3 grid cell neighborhood.
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            // Determine which grid cell the current pixel (x,y) belongs to.
            const currentGridX = Math.floor(x / _crystalSize);
            const currentGridY = Math.floor(y / _crystalSize);

            let minDistSq = Number.POSITIVE_INFINITY;
            // Initialize with a default color (e.g., black or transparent).
            // This will be overridden if any seed point is found.
            let closestSeedColor = [0, 0, 0, 0]; 

            // Search in a 3x3 neighborhood of grid cells around (currentGridX, currentGridY).
            for (let searchOffsetY = -1; searchOffsetY <= 1; searchOffsetY++) {
                for (let searchOffsetX = -1; searchOffsetX <= 1; searchOffsetX++) {
                    const checkGridX = currentGridX + searchOffsetX;
                    const checkGridY = currentGridY + searchOffsetY;

                    // Check if the neighboring grid cell is within the bounds of pointGrid.
                    if (checkGridX >= 0 && checkGridX < numGridCols &&
                        checkGridY >= 0 && checkGridY < numGridRows) {
                        
                        const seed = pointGrid[checkGridY][checkGridX];
                        // This seed should always exist as pointGrid was fully populated.
                        if (seed) { 
                            const dx = x - seed.x;
                            const dy = y - seed.y;
                            const distSq = dx * dx + dy * dy;

                            if (distSq < minDistSq) {
                                minDistSq = distSq;
                                closestSeedColor = seed.color;
                            }
                        }
                    }
                }
            }

            const outputPixelIndex = (y * width + x) * 4;
            outputPixels[outputPixelIndex] = closestSeedColor[0];
            outputPixels[outputPixelIndex + 1] = closestSeedColor[1];
            outputPixels[outputPixelIndex + 2] = closestSeedColor[2];
            outputPixels[outputPixelIndex + 3] = closestSeedColor[3];
        }
    }

    // 7. Draw the result to the output canvas
    outputCtx.putImageData(outputImageData, 0, 0);
    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 Crystallize Filter Application allows users to apply a crystallization effect to their images. By specifying a crystal size parameter, users can control the granularity of the effect, resulting in a visually unique interpretation of the image. This tool is suitable for creating artistic representations of photos, digital artwork enhancement, or simply experimenting with image aesthetics. Users can easily upload any image and customize the crystallization to achieve their desired artistic result.

Leave a Reply

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