You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
numPieces = 5,
maxOffset = 20,
maxRotation = 15,
edgeRoughness = 10,
subSegmentsPerEdge = 15,
shadowColor = 'rgba(0,0,0,0.3)',
shadowBlur = 5,
shadowOffsetX = 3,
shadowOffsetY = 3,
backgroundColor = 'transparent'
) {
// Helper for robust number parsing
function parseNumericParam(value, defaultValue) {
const num = Number(value);
return isNaN(num) ? defaultValue : num;
}
// Sanitize parameters
numPieces = Math.max(1, parseNumericParam(numPieces, 5));
maxOffset = parseNumericParam(maxOffset, 20);
maxRotation = parseNumericParam(maxRotation, 15);
edgeRoughness = Math.max(0, parseNumericParam(edgeRoughness, 10)); // Roughness cannot be negative
subSegmentsPerEdge = Math.max(1, parseNumericParam(subSegmentsPerEdge, 15)); // At least 1 segment
shadowBlur = Math.max(0, parseNumericParam(shadowBlur, 5));
shadowOffsetX = parseNumericParam(shadowOffsetX, 3);
shadowOffsetY = parseNumericParam(shadowOffsetY, 3);
shadowColor = String(shadowColor);
backgroundColor = String(backgroundColor);
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const ctx = canvas.getContext('2d');
if (backgroundColor !== 'transparent' && backgroundColor !== '') {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// Helper function to draw a jagged line segment.
// 'fromX, fromY' are the conceptual start of the straight segment.
// 'toX, toY' are the conceptual end of the straight segment.
// The actual drawing uses ctx.lineTo and extends the current path.
function drawJaggedLineSegment(ctx, fromX, fromY, toX, toY, roughness, numSubSegments) {
const dx = toX - fromX;
const dy = toY - fromY;
const len = Math.sqrt(dx * dx + dy * dy);
if (len < 0.001 || roughness === 0) {
ctx.lineTo(toX, toY);
return;
}
// Normalized perpendicular vector to the main segment direction
const perpX = -dy / len;
const perpY = dx / len;
for (let i = 1; i < numSubSegments; i++) { // Jitter intermediate points
const t = i / numSubSegments; // Progress along the main segment (0 to 1)
// Nominal point on the straight line segment
const nominalX = fromX + dx * t;
const nominalY = fromY + dy * t;
// Random jitter amount, perpendicular to the segment
const jitterAmount = (Math.random() * 2 - 1) * roughness;
ctx.lineTo(nominalX + perpX * jitterAmount, nominalY + perpY * jitterAmount);
}
ctx.lineTo(toX, toY); // Ensure the segment ends at the target point
}
for (let i = 0; i < numPieces; i++) {
ctx.save();
// 1. Determine source rectangle from originalImg (part of the image to use for this piece)
// Aim for pieces that are roughly proportional to image dimensions.
const basePieceWidth = originalImg.width / Math.max(1, Math.sqrt(numPieces) * 0.9); // Allow some overlap
const basePieceHeight = originalImg.height / Math.max(1, Math.sqrt(numPieces) * 0.9);
// Add random variation to piece size and aspect ratio
const widthVariation = 0.65 + Math.random() * 0.7; // Random factor between 0.65 and 1.35
const heightVariation = 0.65 + Math.random() * 0.7;
let srcW = basePieceWidth * widthVariation;
let srcH = basePieceHeight * heightVariation;
// Clamp piece dimensions to be within reasonable bounds of the original image size
srcW = Math.max(originalImg.width * 0.10, Math.min(srcW, originalImg.width * 0.95));
srcH = Math.max(originalImg.height * 0.10, Math.min(srcH, originalImg.height * 0.95));
const srcX = Math.max(0, Math.random() * (originalImg.width - srcW));
const srcY = Math.max(0, Math.random() * (originalImg.height - srcH));
// 2. Determine piece position and rotation on the destination canvas
// Try to place pieces somewhat centrally, not all clustered at edges.
const targetCenterX = (0.15 + Math.random() * 0.7) * canvas.width;
const targetCenterY = (0.15 + Math.random() * 0.7) * canvas.height;
const pieceRotation = (Math.random() - 0.5) * 2 * maxRotation; // In degrees
const pieceOffsetX = (Math.random() - 0.5) * 2 * maxOffset;
const pieceOffsetY = (Math.random() - 0.5) * 2 * maxOffset;
// Apply transformations: translate to piece's target center, then rotate
ctx.translate(targetCenterX + pieceOffsetX, targetCenterY + pieceOffsetY);
ctx.rotate(pieceRotation * Math.PI / 180);
// All subsequent drawing operations for this piece are relative to its rotated center (0,0).
// 3. Set shadow properties for the piece
if (shadowBlur > 0 || shadowOffsetX !== 0 || shadowOffsetY !== 0) {
ctx.shadowColor = shadowColor;
ctx.shadowBlur = shadowBlur;
ctx.shadowOffsetX = shadowOffsetX;
ctx.shadowOffsetY = shadowOffsetY;
}
// 4. Create the torn path for clipping
// The path is defined around (0,0), which is the piece's center.
const halfW = srcW / 2;
const halfH = srcH / 2;
// Max deviation for corners from a perfect rectangle, proportional to roughness
const cornerPerturbAmount = edgeRoughness * 0.3;
const randPerturb = () => (Math.random() - 0.5) * 2 * cornerPerturbAmount;
// Define the four nominal corners of the piece, with slight perturbations
const p_tl = { x: -halfW + randPerturb(), y: -halfH + randPerturb() }; // Top-left
const p_tr = { x: halfW + randPerturb(), y: -halfH + randPerturb() }; // Top-right
const p_br = { x: halfW + randPerturb(), y: halfH + randPerturb() }; // Bottom-right
const p_bl = { x: -halfW + randPerturb(), y: halfH + randPerturb() }; // Bottom-left
ctx.beginPath();
ctx.moveTo(p_tl.x, p_tl.y);
drawJaggedLineSegment(ctx, p_tl.x, p_tl.y, p_tr.x, p_tr.y, edgeRoughness, subSegmentsPerEdge); // Top edge
drawJaggedLineSegment(ctx, p_tr.x, p_tr.y, p_br.x, p_br.y, edgeRoughness, subSegmentsPerEdge); // Right edge
drawJaggedLineSegment(ctx, p_br.x, p_br.y, p_bl.x, p_bl.y, edgeRoughness, subSegmentsPerEdge); // Bottom edge
drawJaggedLineSegment(ctx, p_bl.x, p_bl.y, p_tl.x, p_tl.y, edgeRoughness, subSegmentsPerEdge); // Left edge (to close)
ctx.closePath();
// 5. Apply clipping using the created path
ctx.clip();
// 6. Draw the corresponding part of the original image into the clipped, transformed area
// The image is drawn centered at (0,0) in the current transformed space.
ctx.drawImage(originalImg, srcX, srcY, srcW, srcH, -halfW, -halfH, srcW, srcH);
ctx.restore(); // Restore transform, shadow, and clipping state for the next piece
}
return canvas;
}
Apply Changes