You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, numCrystals = 150, overallDarkness = 0.7, edgeBrightness = 1.3) {
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (width === 0 || height === 0) {
// For a 0x0 image, or 0-width/0-height, return an empty canvas of the correct size.
// Drawing the original image (which is nothing if it's 0x0) would also be fine.
return canvas;
}
// Draw original image onto the canvas to sample colors from it.
// This ensures we can use getImageData consistently.
ctx.drawImage(originalImg, 0, 0);
const originalImageData = ctx.getImageData(0, 0, width, height);
const originalPixels = originalImageData.data;
// 1. Generate crystal seeds
const seeds = [];
// Ensure numCrystals is at least 1 and an integer to avoid issues.
const effectiveNumCrystals = Math.max(1, Math.floor(numCrystals));
for (let i = 0; i < effectiveNumCrystals; i++) {
const x = Math.floor(Math.random() * width);
const y = Math.floor(Math.random() * height);
const index = (y * width + x) * 4;
seeds.push({
x: x,
y: y,
r: originalPixels[index],
g: originalPixels[index + 1],
b: originalPixels[index + 2]
});
}
// Fallback: if somehow (e.g., width/height > 0 but random points failed logic, though unlikely with current setup)
// no seeds were generated, add one at the center. This ensures `seeds[0]` is always valid later.
if (seeds.length === 0 && width > 0 && height > 0) {
const x = Math.floor(width / 2);
const y = Math.floor(height / 2);
const index = (y * width + x) * 4;
seeds.push({
x: x, y: y,
r: originalPixels[index],
g: originalPixels[index + 1],
b: originalPixels[index + 2]
});
}
// 2. Create crystallized image data (Voronoi tessellation)
// This forms the base "crystal" facets by assigning each pixel the color of the nearest seed.
const crystallizedImageData = ctx.createImageData(width, height);
const crystallizedPixels = crystallizedImageData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let minDistSq = Infinity;
let closestSeed = seeds[0]; // Initialize with the first seed (guaranteed to exist if width/height > 0)
for (const seed of seeds) {
const dx = x - seed.x;
const dy = y - seed.y;
const distSq = dx * dx + dy * dy; // Euclidean distance squared
if (distSq < minDistSq) {
minDistSq = distSq;
closestSeed = seed;
}
}
const currentIndex = (y * width + x) * 4;
crystallizedPixels[currentIndex] = closestSeed.r;
crystallizedPixels[currentIndex + 1] = closestSeed.g;
crystallizedPixels[currentIndex + 2] = closestSeed.b;
crystallizedPixels[currentIndex + 3] = 255; // Alpha
}
}
// 3. Apply "Cave" effect: overall darkening and facet edge enhancement
const outputImageData = ctx.createImageData(width, height);
const outputPixels = outputImageData.data;
// Clamp parameters to sensible ranges
overallDarkness = Math.max(0.0, Math.min(1.0, overallDarkness));
edgeBrightness = Math.max(0.0, edgeBrightness); // Can be > 1.0 for brightening
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const currentIndex = (y * width + x) * 4;
let r = crystallizedPixels[currentIndex];
let g = crystallizedPixels[currentIndex + 1];
let b = crystallizedPixels[currentIndex + 2];
// Apply overall darkness to simulate the "cave" aspect
r *= overallDarkness;
g *= overallDarkness;
b *= overallDarkness;
// Edge detection on the crystallized image to find facet boundaries.
// A pixel is an "edge" if any of its direct neighbors has a different color.
let isEdge = false;
const currentCR = crystallizedPixels[currentIndex];
const currentCG = crystallizedPixels[currentIndex + 1];
const currentCB = crystallizedPixels[currentIndex + 2];
// Check neighbors (right, left, bottom, top)
const neighborCoords = [
[x + 1, y], // Right
[x - 1, y], // Left
[x, y + 1], // Bottom
[x, y - 1] // Top
];
for (const [nx, ny] of neighborCoords) {
if (nx >= 0 && nx < width && ny >= 0 && ny < height) { // Check bounds
const neighborIdx = (ny * width + nx) * 4;
if (crystallizedPixels[neighborIdx] !== currentCR ||
crystallizedPixels[neighborIdx + 1] !== currentCG ||
crystallizedPixels[neighborIdx + 2] !== currentCB) {
isEdge = true;
break; // Found a differing neighbor, current pixel is an edge
}
}
}
if (isEdge) {
// Brighten the edge pixel to make crystals "pop" or "glow"
r *= edgeBrightness;
g *= edgeBrightness;
b *= edgeBrightness;
}
// Clamp values to [0, 255] and set final pixel data
outputPixels[currentIndex] = Math.max(0, Math.min(255, Math.round(r)));
outputPixels[currentIndex + 1] = Math.max(0, Math.min(255, Math.round(g)));
outputPixels[currentIndex + 2] = Math.max(0, Math.min(255, Math.round(b)));
outputPixels[currentIndex + 3] = 255; // Alpha
}
}
// Draw the final processed image data onto the canvas
ctx.putImageData(outputImageData, 0, 0);
return canvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Crystal Cave Filter Effect Tool allows users to apply a unique ‘cave’ effect to their images, transforming them into a stylized crystalline appearance. By generating random crystal-like seeds based on the original image colors, the tool creates a visual effect characterized by darkened overall tones and highlighted edges that enhance the crystalline facets. This tool is suitable for artists, graphic designers, and anyone looking to create visually appealing images for personal use, social media, or digital projects.