You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, size = 400, emotion = 'neutral', borderWidth = 8, eyeSize = 30) {
// Parse parameters to correct types
const canvasSize = parseInt(size, 10) || 400;
const borderW = parseInt(borderWidth, 10) || 8;
const eyeSz = parseInt(eyeSize, 10) || 30;
const currentEmotion = String(emotion).toLowerCase();
// Setup Canvas
const canvas = document.createElement('canvas');
canvas.width = canvasSize;
canvas.height = canvasSize;
const ctx = canvas.getContext('2d');
// Canvas styling properties
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
// Ball dimensions and positions
const cx = canvasSize / 2;
const cy = canvasSize / 2 + canvasSize * 0.05; // Slightly lower to account for the squashed shape
const rx = canvasSize * 0.45;
const ry = canvasSize * 0.40; // Imperfect circle (squashed) for authentic look
// 1. Draw floor shadow
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
ctx.beginPath();
ctx.ellipse(cx, cy + ry * 0.95, rx * 0.8, ry * 0.15, 0, 0, Math.PI * 2);
ctx.fill();
// 2. Draw the ball base with clipped image (the flag texture)
ctx.save();
ctx.beginPath();
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
ctx.clip(); // Mask following drawings to the squircle
// We stretch the flag over the squircle bounds exactly so all elements of
// the flag remain fully visible and mapped onto the "ball".
ctx.drawImage(originalImg, cx - rx, cy - ry, rx * 2, ry * 2);
// 3. Volumetric Shading for a 3D ball effect
const shadowGrad = ctx.createRadialGradient(
cx - rx * 0.3, cy - ry * 0.3, rx * 0.1,
cx, cy, rx
);
shadowGrad.addColorStop(0, 'rgba(255, 255, 255, 0.4)'); // Highlight
shadowGrad.addColorStop(0.5, 'rgba(0, 0, 0, 0)');
shadowGrad.addColorStop(1, 'rgba(0, 0, 0, 0.65)'); // Deep shadow
ctx.fillStyle = shadowGrad;
ctx.fill();
ctx.restore(); // Remove clipping mask
// 4. Draw Ball Outline
ctx.beginPath();
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
ctx.lineWidth = borderW;
ctx.strokeStyle = '#1a1a1a'; // Almost black
ctx.stroke();
// 5. Add a glossy reflection spot (optional touch for shininess)
ctx.beginPath();
ctx.ellipse(cx - rx * 0.4, cy - ry * 0.55, rx * 0.18, ry * 0.08, -Math.PI / 6, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)';
ctx.fill();
// Nested helper function to draw country ball style eyes
function drawEye(x, y, size, emo, isLeft, bWidth) {
ctx.save();
ctx.translate(x, y);
const w = size;
const h = size * 1.15; // Eyes are typically slightly taller than wide
ctx.lineWidth = bWidth * 0.75;
ctx.strokeStyle = '#1a1a1a';
ctx.fillStyle = '#ffffff';
ctx.beginPath();
if (emo === 'happy') {
// Happy closed eyes (^ ^)
ctx.moveTo(-w * 0.8, h * 0.3);
ctx.quadraticCurveTo(0, -h * 0.8, w * 0.8, h * 0.3);
ctx.stroke();
ctx.restore();
return; // No fill needed
}
else if (emo === 'surprised') {
// Wide circular shape
ctx.ellipse(0, 0, w * 0.7, h * 0.9, 0, 0, Math.PI * 2);
}
else if (emo === 'angry') {
// Flat top angled inwards
ctx.rotate(isLeft ? 0.35 : -0.35);
ctx.moveTo(-w, -h * 0.2);
ctx.lineTo(w, -h * 0.2);
ctx.bezierCurveTo(w, h, -w, h, -w, -h * 0.2);
}
else if (emo === 'sad') {
// Flat top angled outwards
ctx.rotate(isLeft ? -0.3 : 0.3);
ctx.moveTo(-w, -h * 0.2);
ctx.lineTo(w, -h * 0.2);
ctx.bezierCurveTo(w, h, -w, h, -w, -h * 0.2);
}
else {
// 'neutral' or default - drawn as a slight bezier squircle for authentic hand-drawn feel
ctx.ellipse(0, 0, w, h, (isLeft ? 0.1 : -0.1), 0, Math.PI * 2);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
}
// 6. Calculate eye placement and draw eyes
const eyeSpacing = rx * 0.45;
const eyeY = cy - ry * 0.15;
const eyeXLeft = cx - eyeSpacing / 2;
const eyeXRight = cx + eyeSpacing / 2;
drawEye(eyeXLeft, eyeY, eyeSz, currentEmotion, true, borderW);
drawEye(eyeXRight, eyeY, eyeSz, currentEmotion, false, borderW);
return canvas;
}
Apply Changes