You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, piecesDensityStr = "40", crackAmountStr = "5", peelProbabilityStr = "0.15", ruinLevelStr = "0.6") {
// Parse constraint parameters
const piecesDensity = Math.max(5, Math.min(parseFloat(piecesDensityStr) || 40, 150));
const crackAmount = Math.max(0, Math.min(parseFloat(crackAmountStr) || 5, 20));
const peelProb = Math.max(0, Math.min(parseFloat(peelProbabilityStr) || 0.15, 0.95));
const ruinLvl = Math.max(0, Math.min(parseFloat(ruinLevelStr) || 0.6, 1));
const w = originalImg.width;
const h = originalImg.height;
// Create main canvas
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// 1. Generate a procedural rusty/dark background for peeled areas
const patSize = 256;
const pCan = document.createElement('canvas');
pCan.width = patSize;
pCan.height = patSize;
const pCtx = pCan.getContext('2d');
const pData = pCtx.createImageData(patSize, patSize);
for (let i = 0; i < pData.data.length; i += 4) {
const val = Math.random() * 255;
pData.data[i] = val * 0.6 + 40; // R
pData.data[i + 1] = val * 0.3 + 20; // G
pData.data[i + 2] = val * 0.2 + 10; // B
pData.data[i + 3] = 255; // A
}
pCtx.putImageData(pData, 0, 0);
// Add darker grunge spots to background
for (let i = 0; i < 40; i++) {
pCtx.fillStyle = `rgba(0,0,0,${Math.random() * 0.6})`;
pCtx.beginPath();
pCtx.arc(Math.random() * patSize, Math.random() * patSize, Math.random() * 25, 0, Math.PI * 2);
pCtx.fill();
}
let bgPattern = ctx.createPattern(pCan, 'repeat');
ctx.fillStyle = bgPattern;
ctx.fillRect(0, 0, w, h);
// 2. Compute a randomized shattered grid
const longestEdge = Math.max(w, h);
const cols = Math.max(2, Math.round(piecesDensity * (w / longestEdge)));
const rows = Math.max(2, Math.round(piecesDensity * (h / longestEdge)));
const cellW = w / cols;
const cellH = h / rows;
let points = [];
for (let y = 0; y <= rows; y++) {
let row = [];
for (let x = 0; x <= cols; x++) {
let px = x * cellW;
let py = y * cellH;
// Perturb interior points, keeping edges constrained
if (x > 0 && x < cols) {
px += (Math.random() - 0.5) * cellW * 0.8;
}
if (y > 0 && y < rows) {
py += (Math.random() - 0.5) * cellH * 0.8;
}
row.push({ x: px, y: py });
}
points.push(row);
}
// Connect points into triangles for the "broken pieces"
let triangles = [];
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
let pTopLeft = points[y][x];
let pTopRight = points[y][x + 1];
let pBotLeft = points[y + 1][x];
let pBotRight = points[y + 1][x + 1];
// Randomize diagonal split to make it look organically shattered
if (Math.random() > 0.5) {
triangles.push([pTopLeft, pTopRight, pBotRight]);
triangles.push([pTopLeft, pBotRight, pBotLeft]);
} else {
triangles.push([pTopLeft, pTopRight, pBotLeft]);
triangles.push([pTopRight, pBotRight, pBotLeft]);
}
}
}
// 3. Draw broken pieces randomly detached and rotated
const maxDisplacement = (w / 100) * (crackAmount / 5);
for (let tri of triangles) {
if (Math.random() < peelProb) continue; // Chance of piece peeling off completely
const [p1, p2, p3] = tri;
let minX = Math.floor(Math.min(p1.x, p2.x, p3.x));
let minY = Math.floor(Math.min(p1.y, p2.y, p3.y));
let maxX = Math.ceil(Math.max(p1.x, p2.x, p3.x));
let maxY = Math.ceil(Math.max(p1.y, p2.y, p3.y));
// Adding 2px padding to avoid precision artifacts on bounds
minX = Math.max(0, minX - 2);
minY = Math.max(0, minY - 2);
maxX = Math.min(w, maxX + 2);
maxY = Math.min(h, maxY + 2);
const tw = maxX - minX;
const th = maxY - minY;
if (tw <= 0 || th <= 0) continue;
// Centroid mapping
const cx = (p1.x + p2.x + p3.x) / 3;
const cy = (p1.y + p2.y + p3.y) / 3;
ctx.save();
// Calculate transformations to make pieces look disjointed/cracked
const dx = (Math.random() - 0.5) * maxDisplacement;
const dy = (Math.random() - 0.5) * maxDisplacement;
const rot = (Math.random() - 0.5) * (crackAmount * 0.015);
const scl = 1 - (crackAmount * 0.005) - (Math.random() * 0.01);
// Apply transformations
ctx.translate(cx + dx, cy + dy);
ctx.rotate(rot);
ctx.scale(scl, scl);
ctx.translate(-cx, -cy);
// Form clipping path for this shard
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.lineTo(p3.x, p3.y);
ctx.closePath();
ctx.clip();
// Draw original image part
ctx.drawImage(originalImg, minX, minY, tw, th, minX, minY, tw, th);
// Shade overlay to simulate distinct tilted paint shards
ctx.fillStyle = Math.random() > 0.5 ? 'rgba(255, 255, 255, 0.07)' : 'rgba(0, 0, 0, 0.07)';
ctx.fill();
// Outline each piece to trace the crack depth
ctx.lineWidth = Math.max(1, w / 600);
ctx.strokeStyle = 'rgba(0, 0, 0, 0.6)';
ctx.stroke();
ctx.restore();
}
// 4. Apply a global "ruined surface" dirt overlay
if (ruinLvl > 0) {
// Recycle pattern canvas for a grime map
for (let i = 0; i < pData.data.length; i += 4) {
let dirt = Math.random() * 255;
pData.data[i] = dirt;
pData.data[i + 1] = dirt * 0.9;
pData.data[i + 2] = dirt * 0.8;
pData.data[i + 3] = (Math.random() > 0.65) ? dirt * ruinLvl : 0;
}
pCtx.putImageData(pData, 0, 0);
const dirtPattern = ctx.createPattern(pCan, 'repeat');
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = dirtPattern;
ctx.fillRect(0, 0, w, h);
// Brown/sepia grunge overlay for aged paint feel
ctx.globalCompositeOperation = 'color-burn';
ctx.fillStyle = `rgba(110, 90, 75, ${ruinLvl * 0.35})`;
ctx.fillRect(0, 0, w, h);
// Outer dark vignette
ctx.globalCompositeOperation = 'multiply';
const grad = ctx.createRadialGradient(w/2, h/2, Math.min(w, h)/3, w/2, h/2, Math.max(w, h)/1.2);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, `rgba(0,0,0,${ruinLvl * 0.7})`);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
}
return canvas;
}
Apply Changes