You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, callerName = "John Doe", callType = "Incoming video call") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Canvas dimensions for a Mac-style floating notification window
const width = 400;
const height = 280;
const dpr = window.devicePixelRatio || 1;
// Set actual and physical dimensions for crisp rendering on high-DPI
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
canvas.style.maxWidth = '100%';
// Mac OS window styling
canvas.style.borderRadius = '14px';
canvas.style.boxShadow = '0 20px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.1) inset';
canvas.style.backgroundColor = '#1e1e1e';
canvas.style.display = 'block';
let time = 0;
function draw() {
// Reset transform and clear
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = '#1e1e1e';
ctx.fillRect(0, 0, width, height);
// 1. Background (Blurred Dark Original Image)
ctx.save();
if (typeof ctx.filter !== 'undefined') {
ctx.filter = 'blur(15px) brightness(0.35)';
} else {
ctx.globalAlpha = 0.3; // Fallback for older browsers
}
// Scale to fill plus some extra margin to avoid blur edge artifacts
let bgScale = Math.max((width + 40) / originalImg.width, (height + 40) / originalImg.height);
let bdw = originalImg.width * bgScale;
let bdh = originalImg.height * bgScale;
ctx.drawImage(originalImg, (width - bdw) / 2, (height - bdh) / 2, bdw, bdh);
ctx.restore();
// 2. Mac Window Controls (Traffic Lights)
ctx.save();
let btnY = 18;
let startX = 20;
let spacing = 20;
ctx.beginPath(); ctx.arc(startX, btnY, 6, 0, Math.PI * 2); ctx.fillStyle = '#ff5f56'; ctx.fill();
ctx.beginPath(); ctx.arc(startX + spacing, btnY, 6, 0, Math.PI * 2); ctx.fillStyle = '#ffbd2e'; ctx.fill();
ctx.beginPath(); ctx.arc(startX + spacing * 2, btnY, 6, 0, Math.PI * 2); ctx.fillStyle = '#27c93f'; ctx.fill();
ctx.restore();
// 3. Caller Avatar
let avSize = 80;
let avX = width / 2;
let avY = 75;
ctx.save();
// Pulse effect for ringing
let pulse = ((Math.sin(time * 0.05) + 1) / 2) * 5;
ctx.beginPath();
ctx.arc(avX, avY, avSize / 2 + pulse, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.15)';
ctx.fill();
// Avatar circle crop
ctx.beginPath();
ctx.arc(avX, avY, avSize / 2, 0, Math.PI * 2);
ctx.clip();
let aScale = Math.max(avSize / originalImg.width, avSize / originalImg.height);
let adw = originalImg.width * aScale;
let adh = originalImg.height * aScale;
ctx.drawImage(originalImg, avX - adw / 2, avY - adh / 2, adw, adh);
ctx.restore();
// Crisp avatar ring
ctx.save();
ctx.beginPath();
ctx.arc(avX, avY, avSize / 2, 0, Math.PI * 2);
ctx.lineWidth = 1.5;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.stroke();
ctx.restore();
// 4. Texts
ctx.save();
ctx.shadowColor = 'rgba(0,0,0,0.7)';
ctx.shadowBlur = 6;
ctx.shadowOffsetY = 1;
ctx.fillStyle = '#ffffff';
ctx.font = '600 21px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(callerName, width / 2, avY + avSize / 2 + 30);
ctx.fillStyle = '#cccccc';
ctx.font = 'normal 15px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
ctx.fillText(callType, width / 2, avY + avSize / 2 + 55);
ctx.restore();
// 5. Call Control Buttons
let btnRadius = 24;
let yBtn = 225;
let acceptX = width / 2 + 65;
let declineX = width / 2 - 65;
// Decline Button (Red / Hangup)
ctx.beginPath();
ctx.arc(declineX, yBtn, btnRadius, 0, Math.PI * 2);
ctx.fillStyle = '#e53935';
ctx.fill();
ctx.save();
ctx.translate(declineX, yBtn);
ctx.fillStyle = '#fff';
// Classical phone receiver icon
ctx.beginPath();
ctx.moveTo(-10, 2);
ctx.quadraticCurveTo(0, -8, 10, 2);
ctx.lineTo(7, 5);
ctx.quadraticCurveTo(0, -3, -7, 5);
ctx.closePath();
ctx.fill();
ctx.beginPath(); ctx.arc(-10, 3, 4, 0, Math.PI * 2); ctx.fill(); // earpiece
ctx.beginPath(); ctx.arc(10, 3, 4, 0, Math.PI * 2); ctx.fill(); // mouthpiece
ctx.restore();
// Accept Button (Green / Video Call Ringing)
ctx.beginPath();
ctx.arc(acceptX, yBtn, btnRadius + pulse * 1.5, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(33, 188, 33, 0.25)';
ctx.fill();
ctx.beginPath();
ctx.arc(acceptX, yBtn, btnRadius, 0, Math.PI * 2);
ctx.fillStyle = '#21bc21';
ctx.fill();
ctx.save();
ctx.translate(acceptX, yBtn);
ctx.fillStyle = '#fff';
// Video camera icon
ctx.beginPath();
if (ctx.roundRect) {
ctx.roundRect(-9, -6, 12, 12, 2.5);
} else {
ctx.rect(-9, -6, 12, 12);
}
ctx.fill();
ctx.beginPath();
ctx.moveTo(3, -2);
ctx.lineTo(9, -6);
ctx.lineTo(9, 6);
ctx.lineTo(3, 2);
ctx.fill();
ctx.restore();
// Subtle glossy glass sheen for classic Mac feel
let grad = ctx.createLinearGradient(0, 0, 0, height/2);
grad.addColorStop(0, 'rgba(255,255,255,0.08)');
grad.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height/2);
}
function animate() {
time++;
draw();
// Automatically stop rendering if unmounted and plenty of time has passed to avoid heavy memory leaking
if (canvas.isConnected || time < 300) {
requestAnimationFrame(animate);
}
}
// Start video call animation
animate();
return canvas;
}
Apply Changes