Please bookmark this page to avoid losing your image tool!

Image Fractalize Filter 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.
function processImage(originalImg, iterations = 6, roughness = 0.6, displacement = 30) {

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

    if (width === 0 || height === 0) {
        const emptyCanvas = document.createElement('canvas');
        emptyCanvas.width = 0;
        emptyCanvas.height = 0;
        return emptyCanvas;
    }

    // Helper: Diamond-Square algorithm to generate fractal noise map
    // Uses map[y][x] (row-major) convention. Resulting values are in [0, 1].
    function diamondSquare(size, persistence) {
        // Size must be 2^n + 1
        let map = Array(size).fill(null).map(() => Array(size).fill(0.0));
        let maxIndex = size - 1; // Maximum index for the map array

        // Seed the four corner values randomly
        map[0][0] = Math.random();
        map[0][maxIndex] = Math.random();
        map[maxIndex][0] = Math.random();
        map[maxIndex][maxIndex] = Math.random();

        let currentAmplitude = 1.0; // Initial magnitude for random offsets

        // Main loop: reduce sideLength by half in each iteration
        for (let sideLength = maxIndex; sideLength >= 2; sideLength /= 2) {
            let halfSide = sideLength / 2;

            // Diamond step: For each square, set its midpoint value.
            // Midpoint is average of 4 corners + random offset.
            for (let y = 0; y < maxIndex; y += sideLength) { // y-coordinate of top-left corner of square
                for (let x = 0; x < maxIndex; x += sideLength) { // x-coordinate of top-left corner of square
                    let avg = (map[y][x] +                      // Top-left
                               map[y][x + sideLength] +         // Top-right
                               map[y + sideLength][x] +         // Bottom-left
                               map[y + sideLength][x + sideLength]) / 4.0; // Bottom-right
                    
                    let offset = (Math.random() * 2 - 1) * currentAmplitude;
                    map[y + halfSide][x + halfSide] = avg + offset; // Set center of the square
                }
            }

            // Square step: For each diamond, set its midpoint value.
            // Midpoint is average of 4 corners (diamond points) + random offset.
            // Iterate over points to set (these are centers of diamonds)
            for (let y = 0; y <= maxIndex; y += halfSide) {
                for (let x = (y + halfSide) % sideLength; x <= maxIndex; x += sideLength) {
                    // (x,y) is the point to set. map access is map[y_coord][x_coord]
                    let sum = 0;
                    let count = 0;
                    
                    // Top neighbor for diamond center (x,y) is (x, y - halfSide)
                    if (y - halfSide >= 0) { sum += map[y - halfSide][x]; count++; }
                    // Bottom neighbor (x, y + halfSide)
                    if (y + halfSide <= maxIndex) { sum += map[y + halfSide][x]; count++; }
                    // Left neighbor (x - halfSide, y)
                    if (x - halfSide >= 0) { sum += map[y][x - halfSide]; count++; }
                    // Right neighbor (x + halfSide, y)
                    if (x + halfSide <= maxIndex) { sum += map[y][x + halfSide]; count++; }
                    
                    if (count > 0) {
                        let avg = sum / count;
                        let offset = (Math.random() * 2 - 1) * currentAmplitude;
                        map[y][x] = avg + offset;
                    } else {
                        // This fallback should ideally not be reached if size > 1 and loops are correct.
                        // For a grid of size 1 (maxIndex=0), the main loop (sideLength >=2) doesn't run.
                        // Only corners are set. Map will be normalized based on those.
                        map[y][x] = Math.random(); 
                    }
                }
            }
            currentAmplitude *= persistence; // Reduce amplitude for the next, finer level of detail
        }

        // Normalize the map to the range [0, 1]
        let minVal = Infinity, maxVal = -Infinity;
        for (let r = 0; r < size; r++) { // r for row (y-coordinate)
            for (let c = 0; c < size; c++) { // c for column (x-coordinate)
                if (map[r][c] < minVal) minVal = map[r][c];
                if (map[r][c] > maxVal) maxVal = map[r][c];
            }
        }

        if (maxVal === minVal) { // Handle a flat map (e.g., if map has only one point, or all points got the same value)
             for (let r = 0; r < size; r++) {
                for (let c = 0; c < size; c++) map[r][c] = 0.5; // Default to a mid-value (e.g., 0.5 for gray)
            }
        } else {
            for (let r = 0; r < size; r++) {
                for (let c = 0; c < size; c++) {
                    map[r][c] = (map[r][c] - minVal) / (maxVal - minVal);
                }
            }
        }
        return map;
    }

    // Helper: Bilinear interpolation to get noise value at (imgX, imgY) from the noiseMap
    // noiseMap uses [y][x] (row-major) convention.
    function getInterpolatedNoise(imgX, imgY, noiseMap, currentImgWidth, currentImgHeight) {
        const noiseGridSize = noiseMap.length;
        if (noiseGridSize === 0) return 0.5; // Should not happen with a valid noiseMap
        if (noiseGridSize === 1) return noiseMap[0][0]; // Single point map, no interpolation needed

        // Map image coordinates (imgX, imgY) to noise map float coordinates (u, v)
        const u = (currentImgWidth === 1) ? 0 : imgX * (noiseGridSize - 1) / (currentImgWidth - 1);
        const v = (currentImgHeight === 1) ? 0 : imgY * (noiseGridSize - 1) / (currentImgHeight - 1);

        const x0 = Math.floor(u);
        const y0 = Math.floor(v);
        
        // Clamp coordinates to be within noiseMap bounds. Min is implicitly handled by floor.
        const x1 = Math.min(x0 + 1, noiseGridSize - 1); 
        const y1 = Math.min(y0 + 1, noiseGridSize - 1);

        const tx = u - x0; // Fractional part for x for interpolation
        const ty = v - y0; // Fractional part for y for interpolation

        // Get values at the four corners of the cell in the noise map
        const n00 = noiseMap[y0][x0]; // Value at (y0, x0)
        const n10 = noiseMap[y0][x1]; // Value at (y0, x1)
        const n01 = noiseMap[y1][x0]; // Value at (y1, x0)
        const n11 = noiseMap[y1][x1]; // Value at (y1, x1)

        // Interpolate along the x-axis for top and bottom edges of the cell
        const nx0 = n00 * (1 - tx) + n10 * tx;
        const nx1 = n01 * (1 - tx) + n11 * tx;
        
        // Interpolate along the y-axis using the x-interpolated values
        return nx0 * (1 - ty) + nx1 * ty;
    }
    
    // Determine the size of the noise grid based on iterations
    // iterations=0 -> size=2, iterations=1 -> size=3, iterations=6 -> size=65
    const noiseGridSize = Math.pow(2, iterations) + 1;

    // Generate two independent noise maps for X and Y displacements
    const noiseMapX = diamondSquare(noiseGridSize, roughness);
    const noiseMapY = diamondSquare(noiseGridSize, roughness);

    // Prepare input canvas to read original image pixels
    const inputCanvas = document.createElement('canvas');
    inputCanvas.width = width;
    inputCanvas.height = height;
    const inputCtx = inputCanvas.getContext('2d', { willReadFrequently: true });
    inputCtx.drawImage(originalImg, 0, 0);
    const originalImgData = inputCtx.getImageData(0, 0, width, height);
    const originalPixels = originalImgData.data;

    // Prepare output canvas
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = width;
    outputCanvas.height = height;
    const outputCtx = outputCanvas.getContext('2d');
    const outputImgData = outputCtx.createImageData(width, height);
    const outputPixels = outputImgData.data;

    // Process each pixel of the image
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            // Get interpolated noise values for current (x,y)
            const noiseValX = getInterpolatedNoise(x, y, noiseMapX, width, height); // Range [0, 1]
            const noiseValY = getInterpolatedNoise(x, y, noiseMapY, width, height); // Range [0, 1]

            // Map noise from [0,1] to [-1,1] and scale by displacement
            const dX = (noiseValX - 0.5) * 2 * displacement; // Displacement ranges from -displacement to +displacement
            const dY = (noiseValY - 0.5) * 2 * displacement;

            // Calculate source pixel coordinates after displacement
            const srcX = Math.round(x + dX);
            const srcY = Math.round(y + dY);

            // Clamp coordinates to be within the image bounds to avoid out-of-bounds reads
            const clampedSrcX = Math.max(0, Math.min(width - 1, srcX));
            const clampedSrcY = Math.max(0, Math.min(height - 1, srcY));

            // Calculate array indices for source and destination pixels (RGBA format)
            const srcPixelIndex = (clampedSrcY * width + clampedSrcX) * 4;
            const destPixelIndex = (y * width + x) * 4;

            // Copy pixel data from source to destination
            outputPixels[destPixelIndex]     = originalPixels[srcPixelIndex];     // Red
            outputPixels[destPixelIndex + 1] = originalPixels[srcPixelIndex + 1]; // Green
            outputPixels[destPixelIndex + 2] = originalPixels[srcPixelIndex + 2]; // Blue
            outputPixels[destPixelIndex + 3] = originalPixels[srcPixelIndex + 3]; // Alpha (transparency)
        }
    }

    // Put the modified pixel data onto the output canvas
    outputCtx.putImageData(outputImgData, 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 Fractalize Filter Tool applies a fractal displacement effect to images, creating unique visual patterns by manipulating pixel positions based on generated fractal noise. Users can adjust the number of iterations to control the complexity of the effect, as well as parameters like roughness and displacement to achieve various artistic results. This tool is ideal for graphic designers and artists looking to create abstract art, backgrounds, or textures, as well as for use in digital media projects that require interesting visual effects.

Leave a Reply

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