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!
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.