You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, callerName = "Unknown Caller", callerNumber = "Mobile") {
// Inject the classic Android system font (Roboto) dynamically
if (!document.getElementById('android-roboto-font')) {
const link = document.createElement('link');
link.id = 'android-roboto-font';
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap';
document.head.appendChild(link);
}
// Wait for the font to load for consistent rendering
await document.fonts.ready;
// Define standard portrait smartphone dimensions
const w = 450;
const h = 800;
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// 1. Draw Blurred Background using Object-Fit Strategy mapping
const imgRatio = originalImg.width / originalImg.height;
const canvasRatio = w / h;
let sWidth = originalImg.width;
let sHeight = originalImg.height;
let sx = 0;
let sy = 0;
if (imgRatio > canvasRatio) {
// Image is wider than canvas proportion
sWidth = sHeight * canvasRatio;
sx = (originalImg.width - sWidth) / 2;
} else {
// Image is taller than canvas proportion
sHeight = sWidth / canvasRatio;
sy = (originalImg.height - sHeight) / 2;
}
// Apply high blur, drawing scaled past edges to hide empty blur-bleed limits
ctx.filter = 'blur(20px)';
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, -40, -40, w + 80, h + 80);
ctx.filter = 'none';
// Apply a black overlay for readability
ctx.fillStyle = 'rgba(0, 0, 0, 0.65)';
ctx.fillRect(0, 0, w, h);
// 2. Draw Top Status Bar to mimic an Android display
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, w, 24);
ctx.fillStyle = '#ffffff';
ctx.font = '400 12px "Roboto", sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
const now = new Date();
const timeString = `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`;
ctx.fillText(timeString, 15, 12);
ctx.textAlign = 'right';
ctx.fillRect(w - 25, 8, 12, 8); // Battery Body
ctx.fillRect(w - 12, 10, 2, 4); // Battery Tip
ctx.fillText("100%", w - 35, 12);
// 3. Render In-Call Text information
const cx = w / 2;
ctx.textAlign = 'center';
ctx.font = '300 20px "Roboto", sans-serif';
ctx.fillStyle = '#e0e0e0';
ctx.fillText("Incoming call", cx, 80);
ctx.font = '400 36px "Roboto", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.fillText(callerName, cx, 360, w - 60);
ctx.font = '400 18px "Roboto", sans-serif';
ctx.fillStyle = '#b0b0b0';
ctx.fillText(callerNumber, cx, 400);
// 4. Render Circular Avatar Profile
const cy = 200;
const radius = 75;
const size = Math.min(originalImg.width, originalImg.height);
const sx2 = (originalImg.width - size) / 2;
const sy2 = (originalImg.height - size) / 2;
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.clip(); // Mask drawn layers to a circle
// Exact center crop for the Avatar
ctx.drawImage(originalImg, sx2, sy2, size, size, cx - radius, cy - radius, radius * 2, radius * 2);
ctx.restore();
// Adding subtle white border outline specifically for the Avatar
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
ctx.lineWidth = 3;
ctx.stroke();
// 5. Draw Answer/Decline Call Actions
const btnRadius = 32;
const leftX = w / 3.5;
const rightX = w - (w / 3.5);
const btnY = 640;
// Standard Android/Material SVG Path for a Phone Receiver (Based on 24x24 box)
const phonePath = new Path2D("M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z");
// Render Answer Button (Right Side) - Includes ringing pulse rings
ctx.beginPath();
ctx.arc(rightX, btnY, btnRadius + 25, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(76, 217, 100, 0.15)';
ctx.fill();
ctx.beginPath();
ctx.arc(rightX, btnY, btnRadius + 12, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(76, 217, 100, 0.3)';
ctx.fill();
ctx.beginPath();
ctx.arc(rightX, btnY, btnRadius, 0, Math.PI * 2);
ctx.fillStyle = '#4CD964';
ctx.fill();
ctx.save();
ctx.translate(rightX, btnY);
ctx.scale(1.5, 1.5);
ctx.translate(-12, -12); // Center scale offsets
ctx.fillStyle = '#ffffff';
ctx.fill(phonePath);
ctx.restore();
// Render Decline Button (Left Side) - Red block icon pointing downward
ctx.beginPath();
ctx.arc(leftX, btnY, btnRadius, 0, Math.PI * 2);
ctx.fillStyle = '#FF3B30';
ctx.fill();
ctx.save();
ctx.translate(leftX, btnY);
ctx.rotate(135 * Math.PI / 180); // Rotate 135deg to signify hangup diagonally
ctx.scale(1.5, 1.5);
ctx.translate(-12, -12);
ctx.fillStyle = '#ffffff';
ctx.fill(phonePath);
ctx.restore();
// Render Action Labels
ctx.font = '400 15px "Roboto", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.fillText("Decline", leftX, btnY + 70);
ctx.fillText("Answer", rightX, btnY + 70);
return canvas;
}
Apply Changes