You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, bgColor = "#D4EDF4", tableColor = "#FFD1DC", speedMs = "110") {
// 1. Setup Canvas
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
const ctx = canvas.getContext('2d');
const interval = parseInt(speedMs) || 120;
// 2. Calculations for original image (the "bongo")
const maxW = 350;
const maxH = 150;
let scale = Math.min(maxW / originalImg.width, maxH / originalImg.height);
if (scale > 2) scale = 2; // Prevent extreme pixelation if image is tiny
const w = originalImg.width * scale;
const h = originalImg.height * scale;
const imgX = 400 - w / 2;
const imgY = 580 - h; // Sets it perfectly onto the table
// 3. Coordinate Targets
// Hitting targets (on the image surface)
const leftDownX = imgX + w * 0.15;
const leftDownY = imgY + h * 0.5;
const rightDownX = imgX + w * 0.85;
const rightDownY = imgY + h * 0.5;
// Raised paws positions (near the body)
const leftUpX = 220;
const leftUpY = 360;
const rightUpX = 580;
const rightUpY = 360;
// Angles for the paw ellipses giving a natural swing
const leftDownAngle = 20 * Math.PI / 180;
const leftUpAngle = -30 * Math.PI / 180;
const rightDownAngle = -20 * Math.PI / 180;
const rightUpAngle = 30 * Math.PI / 180;
// 4. Animation properties
const pattern = ['L', 'R', 'L', 'R', 'L', 'R', 'B', 'U'];
let startTime = Date.now();
let notes = [];
// 5. Helper draw functions
function drawPaw(ctx, x, y, angle, isDown) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.lineWidth = 6;
ctx.strokeStyle = '#111';
ctx.fillStyle = '#fff';
ctx.beginPath();
// Slightly flatter/wider paw when hitting down
ctx.ellipse(0, 0, isDown ? 45 : 35, 25, 0, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.restore();
}
function drawImpact(ctx, x, y) {
ctx.save();
ctx.lineWidth = 4;
ctx.strokeStyle = '#111';
ctx.lineCap = 'round';
// Draw 3 splash lines radiating outwards
for (let i = 0; i < 3; i++) {
let angle = -Math.PI / 2 + (i - 1) * 0.4;
let d1 = 30, d2 = 50;
ctx.beginPath();
ctx.moveTo(x + Math.cos(angle) * d1, y + Math.sin(angle) * d1);
ctx.lineTo(x + Math.cos(angle) * d2, y + Math.sin(angle) * d2);
ctx.stroke();
}
ctx.restore();
}
// 6. Main render loop
function render() {
const now = Date.now();
const elapsed = now - startTime;
const step = Math.floor(elapsed / interval) % pattern.length;
const action = pattern[step];
const isSmash = (action === 'B');
const leftDown = (action === 'L' || action === 'B');
const rightDown = (action === 'R' || action === 'B');
// Clear and draw background
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// --- CAT BODY ---
ctx.save();
ctx.lineWidth = 6;
ctx.strokeStyle = '#111';
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.moveTo(180, 500);
ctx.bezierCurveTo(180, 280, 250, 220, 320, 220); // Left body
ctx.lineTo(330, 130); // Left ear tip
ctx.lineTo(370, 190); // Left ear base
ctx.quadraticCurveTo(400, 180, 430, 190); // Forehead
ctx.lineTo(470, 130); // Right ear tip
ctx.lineTo(480, 220); // Right ear base
ctx.bezierCurveTo(550, 220, 620, 280, 620, 500); // Right body
ctx.lineTo(180, 500); // Close bottom
ctx.fill();
ctx.stroke();
// Face features
ctx.fillStyle = '#111';
ctx.beginPath(); ctx.arc(360, 290, 7, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(440, 290, 7, 0, Math.PI * 2); ctx.fill();
// Cheeks
ctx.fillStyle = '#FFAAA6';
ctx.globalAlpha = 0.5;
ctx.beginPath(); ctx.arc(330, 310, 12, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(470, 310, 12, 0, Math.PI * 2); ctx.fill();
ctx.globalAlpha = 1.0;
// Mouth (opens wide if smashing)
ctx.strokeStyle = '#111';
ctx.lineWidth = 4;
if (isSmash) {
ctx.fillStyle = '#111';
ctx.beginPath();
ctx.arc(400, 310, 15, 0, Math.PI, false);
ctx.fill();
} else {
ctx.beginPath();
ctx.moveTo(385, 310);
ctx.quadraticCurveTo(392, 320, 400, 310);
ctx.quadraticCurveTo(408, 320, 415, 310);
ctx.stroke();
}
ctx.restore();
// --- TABLE ---
// Covering everything below Y=420 to give depth!
ctx.fillStyle = tableColor;
ctx.fillRect(0, 420, 800, 180);
ctx.beginPath();
ctx.moveTo(0, 420);
ctx.lineTo(800, 420);
ctx.lineWidth = 6;
ctx.strokeStyle = '#111';
ctx.stroke();
// --- ORIGINAL IMAGE AS BONGO ---
ctx.save();
ctx.shadowColor = 'rgba(0,0,0,0.15)';
ctx.shadowBlur = 15;
ctx.shadowOffsetY = 10;
ctx.fillStyle = '#fff';
ctx.fillRect(imgX - 8, imgY - 8, w + 16, h + 16);
ctx.shadowColor = 'transparent'; // stop shadow for stroke & image
ctx.lineWidth = 4;
ctx.strokeStyle = '#111';
ctx.strokeRect(imgX - 8, imgY - 8, w + 16, h + 16);
// Paint over the white Polaroid-like background
ctx.drawImage(originalImg, imgX, imgY, w, h);
ctx.restore();
// --- MUSICAL NOTES GENERATION ---
if (Math.random() < 0.15) {
notes.push({
x: imgX + w / 2 + (Math.random() * w - w / 2),
y: imgY,
vy: -2 - Math.random() * 2,
vx: (Math.random() - 0.5) * 2,
type: Math.random() > 0.5 ? '♪' : '♫',
life: 1.0
});
}
ctx.save();
ctx.fillStyle = '#111';
ctx.font = 'bold 26px Arial';
for (let i = notes.length - 1; i >= 0; i--) {
let n = notes[i];
n.x += n.vx;
n.y += n.vy;
n.life -= 0.012;
if (n.life <= 0) {
notes.splice(i, 1);
continue;
}
ctx.globalAlpha = n.life;
ctx.fillText(n.type, n.x, n.y);
}
ctx.restore();
// --- PAWS & IMPACTS ---
if (leftDown) {
drawPaw(ctx, leftDownX, leftDownY, leftDownAngle, true);
drawImpact(ctx, leftDownX, leftDownY);
} else {
drawPaw(ctx, leftUpX, leftUpY, leftUpAngle, false);
}
if (rightDown) {
drawPaw(ctx, rightDownX, rightDownY, rightDownAngle, true);
drawImpact(ctx, rightDownX, rightDownY);
} else {
drawPaw(ctx, rightUpX, rightUpY, rightUpAngle, false);
}
requestAnimationFrame(render);
}
render();
return canvas;
}
Apply Changes