You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
titleText = "День рождения Зайца!",
subtitleText = "С Днем Рождения!",
themeColor = "#ffebf0",
textColor = "#ffffff",
textStrokeColor = "#ff69b4"
) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// Set output image size
const width = 800;
const height = 800;
canvas.width = width;
canvas.height = height;
// Helper: Draw rounded rectangle path
function roundRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
}
// 1. Draw festive gradient background
const grad = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, width);
grad.addColorStop(0, "#ffffff");
grad.addColorStop(1, themeColor);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
// 2. Draw colorful confetti
const confettiColors = ["#ff6347", "#ffa500", "#32cd32", "#1e90ff", "#ff69b4", "#ffd700", "#9370db"];
// Seeded-like randomness to keep the layout pretty
let seed = 12345;
function random() {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280;
}
for (let i = 0; i < 200; i++) {
ctx.save();
ctx.translate(random() * width, random() * height);
ctx.rotate(random() * Math.PI * 2);
ctx.fillStyle = confettiColors[Math.floor(random() * confettiColors.length)];
if (random() > 0.5) {
ctx.fillRect(-6, -12, 12, 24); // Rectangular confetti
} else {
ctx.beginPath();
ctx.arc(0, 0, 7, 0, Math.PI * 2); // Circular confetti
ctx.fill();
}
ctx.restore();
}
// 3. Draw Balloons on sides
function drawBalloon(bx, by, bColor) {
ctx.save();
// Balloon string
ctx.beginPath();
ctx.moveTo(bx, by + 40);
ctx.bezierCurveTo(bx - 15, by + 80, bx + 15, by + 120, bx - 10, by + 200);
ctx.strokeStyle = "rgba(0,0,0,0.3)";
ctx.lineWidth = 1.5;
ctx.stroke();
// Main balloon shape
ctx.beginPath();
ctx.ellipse(bx, by, 35, 50, 0, 0, Math.PI * 2);
ctx.fillStyle = bColor;
ctx.fill();
// Balloon tie
ctx.beginPath();
ctx.moveTo(bx, by + 50);
ctx.lineTo(bx - 8, by + 60);
ctx.lineTo(bx + 8, by + 60);
ctx.closePath();
ctx.fill();
// Glossy highlight
ctx.beginPath();
ctx.ellipse(bx - 12, by - 22, 6, 18, Math.PI / 8, 0, Math.PI * 2);
ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
ctx.fill();
ctx.restore();
}
// Call balloons setup
drawBalloon(80, 250, "#ff69b4");
drawBalloon(160, 180, "#1e90ff");
drawBalloon(720, 280, "#32cd32");
drawBalloon(640, 190, "#ffd700");
// Dimensions for central photo frame
const frameSize = 460;
const frameX = (width - frameSize) / 2;
const frameY = 220;
const cx = frameX + frameSize / 2;
// 4. Draw Cute Bunny Ears (Behind Frame)
function drawEar(x, y, tilt) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(tilt);
// Outer white ear
ctx.beginPath();
ctx.ellipse(0, -110, 45, 140, 0, 0, Math.PI * 2);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.lineWidth = 3;
ctx.strokeStyle = "#e0e0e0";
ctx.stroke();
// Inner pink ear
ctx.beginPath();
ctx.ellipse(0, -100, 22, 105, 0, 0, Math.PI * 2);
ctx.fillStyle = "#ffb6c1";
ctx.fill();
ctx.restore();
}
// Position ears on the frame top
drawEar(cx - 110, frameY + 30, -0.25);
drawEar(cx + 110, frameY + 30, 0.25);
// 5. White Photo Border (The Frame itself)
ctx.save();
ctx.shadowColor = "rgba(0,0,0,0.2)";
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 10;
ctx.fillStyle = "#ffffff";
roundRect(ctx, frameX - 16, frameY - 16, frameSize + 32, frameSize + 32, 24);
ctx.fill();
ctx.restore();
// 6. Draw User Image inside restricted bounds
ctx.save();
roundRect(ctx, frameX, frameY, frameSize, frameSize, 12);
ctx.clip();
// Scale image to COVER the inner frame seamlessly
const scale = Math.max(frameSize / originalImg.width, frameSize / originalImg.height);
const scaledW = originalImg.width * scale;
const scaledH = originalImg.height * scale;
const dx = frameX + (frameSize - scaledW) / 2;
const dy = frameY + (frameSize - scaledH) / 2;
ctx.drawImage(originalImg, dx, dy, scaledW, scaledH);
ctx.restore();
// 7. Add cute Bunny Snout & Whiskers on the bottom edge of the frame
const noseX = cx;
const noseY = frameY + frameSize;
ctx.save();
// Rounded base for snout
ctx.beginPath();
ctx.ellipse(noseX, noseY, 50, 35, 0, 0, Math.PI * 2);
ctx.fillStyle = "#ffffff";
ctx.shadowColor = "rgba(0,0,0,0.1)";
ctx.shadowBlur = 5;
ctx.shadowOffsetY = 3;
ctx.fill();
ctx.shadowColor = "transparent"; // Reset shadow
// Pink triangular nose
ctx.beginPath();
ctx.moveTo(noseX - 12, noseY - 6);
ctx.lineTo(noseX + 12, noseY - 6);
ctx.lineTo(noseX, noseY + 8);
ctx.closePath();
ctx.fillStyle = "#ff69b4";
ctx.fill();
// Mouth lines
ctx.beginPath();
ctx.moveTo(noseX, noseY + 8);
ctx.lineTo(noseX, noseY + 16);
ctx.quadraticCurveTo(noseX - 15, noseY + 25, noseX - 25, noseY + 15);
ctx.moveTo(noseX, noseY + 16);
ctx.quadraticCurveTo(noseX + 15, noseY + 25, noseX + 25, noseY + 15);
ctx.strokeStyle = "#555";
ctx.lineWidth = 2;
ctx.stroke();
// Whiskers
ctx.beginPath();
// Left side
ctx.moveTo(noseX - 25, noseY - 5); ctx.lineTo(noseX - 70, noseY - 15);
ctx.moveTo(noseX - 30, noseY); ctx.lineTo(noseX - 75, noseY);
ctx.moveTo(noseX - 25, noseY + 5); ctx.lineTo(noseX - 70, noseY + 15);
// Right side
ctx.moveTo(noseX + 25, noseY - 5); ctx.lineTo(noseX + 70, noseY - 15);
ctx.moveTo(noseX + 30, noseY); ctx.lineTo(noseX + 75, noseY);
ctx.moveTo(noseX + 25, noseY + 5); ctx.lineTo(noseX + 70, noseY + 15);
ctx.strokeStyle = "#444";
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.restore();
// 8. Texts (Titles)
ctx.save();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// Reusable text dropping function with nice dual stroke+shadow for readability
function drawPartyText(text, yPos, fontSize) {
ctx.font = `bold ${fontSize}px "Comic Sans MS", "Arial Rounded MT Bold", cursive, sans-serif`;
ctx.lineWidth = 8;
ctx.strokeStyle = textStrokeColor;
ctx.lineJoin = "round";
ctx.strokeText(text, width / 2, yPos);
ctx.lineWidth = 4;
ctx.strokeStyle = "rgba(0,0,0,0.1)";
ctx.strokeText(text, width / 2, yPos + 3);
ctx.fillStyle = textColor;
ctx.fillText(text, width / 2, yPos);
}
drawPartyText(titleText, 80, 56);
drawPartyText(subtitleText, 145, 36);
ctx.restore();
return canvas;
}
Apply Changes