You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, numCracks = 5, crackColor = "rgba(0,0,0,0.7)",
crackWidthMultiplier = 1.0, shadowOffset = 1, shadowColorLight = "rgba(255,255,255,0.3)",
shadowColorDark = "rgba(0,0,0,0.3)") {
const pNumCracks = parseInt(String(numCracks), 10);
const pCrackColor = String(crackColor);
const pCrackWidthMultiplier = parseFloat(String(crackWidthMultiplier));
const pShadowOffset = parseFloat(String(shadowOffset));
const pShadowColorLight = String(shadowColorLight);
const pShadowColorDark = String(shadowColorDark);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
// Handle invalid image dimensions
console.error("Image has zero width or height.");
return canvas; // Return an empty canvas
}
// 1. Draw the original image
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Helper class for Cracks
class Crack {
constructor(startX, startY, startAngle, canvasWidth, canvasHeight, baseWidth, globalCrackWidthMultiplier) {
this.points = [{ x: startX, y: startY }];
this.currentAngle = startAngle;
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.alive = true;
this.baseWidth = baseWidth; // Specific to this crack path
this.globalCrackWidthMultiplier = globalCrackWidthMultiplier; // From function params
this.segmentsDrawn = 0;
this.maxSegments = 30 + Math.random() * 70; // Crack length: 30 to 100 segments
}
step() {
if (!this.alive || this.segmentsDrawn >= this.maxSegments) {
this.alive = false;
return false;
}
const lastPoint = this.points[this.points.length - 1];
const baseSegmentLength = Math.max(1, Math.min(this.canvasWidth, this.canvasHeight) / 75);
const segmentLength = baseSegmentLength + Math.random() * baseSegmentLength;
this.currentAngle += (Math.random() - 0.5) * 0.7; // Angle variation
let newX = lastPoint.x + Math.cos(this.currentAngle) * segmentLength;
let newY = lastPoint.y + Math.sin(this.currentAngle) * segmentLength;
let attempts = 0;
const maxAttempts = 5;
while ((newX < 0 || newX > this.canvasWidth || newY < 0 || newY > this.canvasHeight) && attempts < maxAttempts) {
this.currentAngle += (Math.PI / 3) * (Math.random() > 0.5 ? 1 : -1); // Turn sharper
newX = lastPoint.x + Math.cos(this.currentAngle) * segmentLength;
newY = lastPoint.y + Math.sin(this.currentAngle) * segmentLength;
attempts++;
}
if (newX < 0 || newX > this.canvasWidth || newY < 0 || newY > this.canvasHeight) {
this.alive = false;
return false;
}
this.points.push({ x: newX, y: newY });
this.segmentsDrawn++;
if (Math.random() < 0.005 * this.segmentsDrawn) {
this.alive = false;
}
return true;
}
canBranch(totalCrackObjects, maxCrackObjectsAllowed) {
const branchProbabilityFactor = Math.max(0.1, (1 - totalCrackObjects / maxCrackObjectsAllowed));
const branchProbability = 0.04 * branchProbabilityFactor;
return this.alive && this.points.length > 2 && this.segmentsDrawn < this.maxSegments * 0.8 && Math.random() < branchProbability;
}
getSegmentWidth(segmentIndex) {
const taperFactor = 1 - (segmentIndex / (this.points.length || 1)) * 0.7;
const randomVariation = 0.7 + Math.random() * 0.6;
return Math.max(0.5, this.baseWidth * this.globalCrackWidthMultiplier * taperFactor * randomVariation);
}
}
let cracks = [];
let activeCracks = [];
const validatedNumCracks = Math.max(1, pNumCracks || 5);
for (let i = 0; i < validatedNumCracks; i++) {
let startX, startY, startAngle;
if (Math.random() < 0.6) { // Start from edge
const edge = Math.floor(Math.random() * 4);
if (edge === 0) { startX = Math.random() * canvas.width; startY = 0; startAngle = Math.random() * Math.PI; }
else if (edge === 1) { startX = canvas.width; startY = Math.random() * canvas.height; startAngle = Math.PI / 2 + Math.random() * Math.PI; }
else if (edge === 2) { startX = Math.random() * canvas.width; startY = canvas.height; startAngle = Math.PI + Math.random() * Math.PI; }
else { startX = 0; startY = Math.random() * canvas.height; startAngle = -Math.PI / 2 + Math.random() * Math.PI; }
} else { // Start from random internal point
startX = Math.random() * canvas.width;
startY = Math.random() * canvas.height;
startAngle = Math.random() * 2 * Math.PI;
}
const baseWidth = (0.8 + Math.random() * 1.2);
activeCracks.push(new Crack(startX, startY, startAngle, canvas.width, canvas.height, baseWidth, pCrackWidthMultiplier));
}
const maxCrackObjects = validatedNumCracks * 8;
let totalCrackObjects = activeCracks.length;
let iterations = 0;
const maxIterations = 250;
while (activeCracks.length > 0 && iterations < maxIterations && totalCrackObjects < maxCrackObjects) {
for (let i = activeCracks.length - 1; i >= 0; i--) {
const crack = activeCracks[i];
if (!crack.step()) {
cracks.push(crack);
activeCracks.splice(i, 1);
} else if (crack.canBranch(totalCrackObjects, maxCrackObjects)) {
const lastPoint = crack.points[crack.points.length - 1];
const branchAngle = crack.currentAngle + (Math.random() - 0.5) * Math.PI / 1.5; // Branch off at wider angle
const branchBaseWidth = crack.baseWidth * (0.4 + Math.random() * 0.4); // Branches are thinner
activeCracks.push(new Crack(lastPoint.x, lastPoint.y, branchAngle, canvas.width, canvas.height, branchBaseWidth, pCrackWidthMultiplier));
totalCrackObjects++;
if (totalCrackObjects >= maxCrackObjects) break; // Optimization
}
}
if (totalCrackObjects >= maxCrackObjects) break; // Optimization
iterations++;
}
cracks.push(...activeCracks);
// Draw cracks
cracks.forEach(crack => {
if (crack.points.length < 2) return;
ctx.lineCap = "round";
ctx.lineJoin = "round";
for (let i = 1; i < crack.points.length; i++) {
const p1 = crack.points[i-1];
const p2 = crack.points[i];
const currentLineWidth = crack.getSegmentWidth(i);
if (currentLineWidth <= 0.1) continue; // Skip negligible lines
if (pShadowOffset > 0) {
ctx.lineWidth = currentLineWidth;
// Dark shadow
ctx.strokeStyle = pShadowColorDark;
ctx.beginPath();
ctx.moveTo(p1.x + pShadowOffset, p1.y + pShadowOffset);
ctx.lineTo(p2.x + pShadowOffset, p2.y + pShadowOffset);
ctx.stroke();
// Light highlight
ctx.strokeStyle = pShadowColorLight;
ctx.beginPath();
ctx.moveTo(p1.x - pShadowOffset, p1.y - pShadowOffset);
ctx.lineTo(p2.x - pShadowOffset, p2.y - pShadowOffset);
ctx.stroke();
}
// Main crack line
ctx.lineWidth = currentLineWidth;
ctx.strokeStyle = pCrackColor;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
});
// Chipping effect
const chipDensity = 0.15;
const chipColor = pCrackColor;
const maxChipOffsetFactor = 2.5; // Multiplier for baseCrackWidthAtPoint for offset
cracks.forEach(crack => {
if (crack.points.length < 2) return;
for (let i = 0; i < crack.points.length; i += Math.floor(Math.random() * 5 + 4)) { // Every 4-8 points
if (Math.random() < chipDensity) {
const p = crack.points[i];
const baseCrackWidthAtPoint = crack.getSegmentWidth(i);
if (baseCrackWidthAtPoint <= 0.1) continue;
const numMicroChips = Math.floor(Math.random() * 3) + 1; // 1 to 3 micro chips
for (let j = 0; j < numMicroChips; j++) {
const microChipSize = Math.max(0.5, (0.2 + Math.random() * 0.5) * baseCrackWidthAtPoint + Math.random() * 0.5);
const offsetX = (Math.random() - 0.5) * baseCrackWidthAtPoint * maxChipOffsetFactor;
const offsetY = (Math.random() - 0.5) * baseCrackWidthAtPoint * maxChipOffsetFactor;
ctx.fillStyle = chipColor;
ctx.beginPath();
ctx.arc(p.x + offsetX, p.y + offsetY, microChipSize, 0, Math.PI * 2);
ctx.fill();
}
}
}
});
return canvas;
}
Apply Changes