You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, position = 'bottom-right', size = 25, numTeeth = 5, addCrumbs = 'true') {
const canvas = document.createElement('canvas');
const W = originalImg.naturalWidth || originalImg.width;
const H = originalImg.naturalHeight || originalImg.height;
canvas.width = W;
canvas.height = H;
const ctx = canvas.getContext('2d');
// Parse and validate parameters
let sizeVal = parseFloat(size);
if (isNaN(sizeVal) || sizeVal <= 0) sizeVal = 25; // default bite size 25%
let teethVal = parseInt(numTeeth, 10);
if (isNaN(teethVal) || teethVal < 2) teethVal = 5; // default 5 teeth marks
let posVal = String(position).toLowerCase();
const validPos = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top', 'bottom', 'left', 'right'];
if (!validPos.includes(posVal)) posVal = 'bottom-right';
const showCrumbs = String(addCrumbs).toLowerCase() === 'true';
// Draw the original image completely onto the canvas
ctx.drawImage(originalImg, 0, 0, W, H);
// Calculate bite radius (R) relative to the smallest dimension
const minDim = Math.min(W, H);
const R = minDim * (sizeVal / 100);
// Determine the center point (fulcrum) and the angular sweep of the bite arc
let cx, cy, theta1, theta2;
switch (posVal) {
case 'top-left': cx = 0; cy = 0; theta1 = 0; theta2 = Math.PI / 2; break;
case 'top-right': cx = W; cy = 0; theta1 = Math.PI / 2; theta2 = Math.PI; break;
case 'bottom-left': cx = 0; cy = H; theta1 = Math.PI * 1.5; theta2 = Math.PI * 2; break;
case 'bottom-right': cx = W; cy = H; theta1 = Math.PI; theta2 = Math.PI * 1.5; break;
case 'top': cx = W/2; cy = 0; theta1 = 0; theta2 = Math.PI; break;
case 'bottom': cx = W/2; cy = H; theta1 = Math.PI; theta2 = Math.PI * 2; break;
case 'left': cx = 0; cy = H/2; theta1 = -Math.PI / 2; theta2 = Math.PI / 2; break;
case 'right': cx = W; cy = H/2; theta1 = Math.PI / 2; theta2 = Math.PI * 1.5; break;
}
// Change composite mode so that drawn shapes "erase" the existing image
ctx.globalCompositeOperation = 'destination-out';
// Calculate individual tooth radius based on arc length to ensure good overlap
const arcLen = R * Math.abs(theta2 - theta1);
const rTooth = (arcLen / (teethVal - 1)) * 0.65; // 0.65 creates a realistic bite scallop
const teethCenters = [];
// Phase 1: Erase the main inner chunk up to the teeth edge
ctx.beginPath();
ctx.moveTo(cx, cy);
for (let i = 0; i < teethVal; i++) {
const t = i / (teethVal - 1);
const theta = theta1 + (theta2 - theta1) * t;
const tx = cx + R * Math.cos(theta);
const ty = cy + R * Math.sin(theta);
teethCenters.push({ x: tx, y: ty, theta: theta });
ctx.lineTo(tx, ty);
}
ctx.lineTo(cx, cy);
ctx.fill();
// Phase 2: Erase overlapping circular scallops forming the "teeth" marks
for (let i = 0; i < teethVal; i++) {
ctx.beginPath();
ctx.arc(teethCenters[i].x, teethCenters[i].y, rTooth, 0, Math.PI * 2);
ctx.fill();
}
// Phase 3: Add some bite fragments ("crumbs") back in the cleared void
if (showCrumbs) {
// Revert to normal rendering to repaint specific bits
ctx.globalCompositeOperation = 'source-over';
const numCrumbs = 5;
// Predetermined distances and sizes relative to create an organic scattered look
const dists = [0.4, 0.7, 0.5, 0.8, 0.45];
const rs = [0.015, 0.01, 0.02, 0.012, 0.018];
const crumbs = [];
for (let i = 1; i <= numCrumbs; i++) {
const t = i / (numCrumbs + 1);
const theta = theta1 + (theta2 - theta1) * t;
const dist = R * dists[i - 1]; // Position between the corner and the bite mark
const cr = minDim * rs[i - 1]; // Size of the crumb relative to image size
crumbs.push({
x: cx + dist * Math.cos(theta),
y: cy + dist * Math.sin(theta),
r: cr
});
}
// Draw the image exclusively inside the crumbs
ctx.save();
ctx.beginPath();
for (const c of crumbs) {
ctx.moveTo(c.x + c.r, c.y);
ctx.arc(c.x, c.y, c.r, 0, Math.PI * 2);
}
ctx.clip(); // Mask the following draw to only the crumb circles
ctx.drawImage(originalImg, 0, 0, W, H);
ctx.restore();
}
// Reset default properties
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes