You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, caption = "Воссоединение друзей!", friendsNames = "Саша, Маша, Паша", themeColor = "#2c3e50") {
// Determine output dimensions
const canvas = document.createElement('canvas');
const width = 1000;
const height = 1000;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Draw elegant background
// Create a radial gradient for a realistic focus lighting effect on the backboard
const grad = ctx.createRadialGradient(width / 2, height / 2, 100, width / 2, height / 2, width * 0.8);
grad.addColorStop(0, themeColor);
grad.addColorStop(1, '#1a252f'); // Darker vignette around edges
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
// 2. Add festive confetti to celebrate the reunion
const colors = ['#ff4757', '#2ed573', '#1e90ff', '#ffa502', '#ffffff', '#eccc68'];
for (let j = 0; j < 150; j++) {
ctx.save();
ctx.fillStyle = colors[Math.floor(Math.random() * colors.length)];
let cx = Math.random() * width;
let cy = Math.random() * height;
ctx.translate(cx, cy);
ctx.rotate(Math.random() * Math.PI * 2);
ctx.globalAlpha = 0.5 + Math.random() * 0.4;
// Mix between circles and tiny rectangles
if (Math.random() > 0.5) {
ctx.beginPath();
ctx.arc(0, 0, Math.random() * 4 + 2, 0, Math.PI * 2);
ctx.fill();
} else {
ctx.fillRect(-4, -8, 8, 16);
}
ctx.restore();
}
// 3. Parse friends' names and prepare photo calculation
let names = friendsNames.split(',').map(n => n.trim()).filter(n => n);
if (names.length === 0) names = ["Друг 1", "Друг 2", "Друг 3"];
const numPhotos = names.length;
// Dynamically scale photo sizes so they fit well depending on how many friends there are
const scaleFactor = Math.max(1.5, Math.sqrt(numPhotos));
const maxImgSide = 450 / (scaleFactor / 1.5);
const scale = Math.min(maxImgSide / originalImg.width, maxImgSide / originalImg.height);
const imgW = originalImg.width * scale;
const imgH = originalImg.height * scale;
const padding = 20;
const bottomPadding = 75;
const pw = imgW + 2 * padding;
const ph = imgH + padding + bottomPadding;
// 4. Draw photos arranged in a scattered circle (creating a collage on a board)
for (let i = 0; i < numPhotos; i++) {
ctx.save();
// Spread logic for layout
const radius = Math.min(270, 80 * numPhotos);
const angleLayout = (Math.PI * 2 / numPhotos) * i - Math.PI / 2;
const cx = width / 2 + Math.cos(angleLayout) * radius + (Math.random() - 0.5) * 60;
const cy = height / 2 + Math.sin(angleLayout) * radius + (Math.random() - 0.5) * 60 + 50; // Shift down slightly
ctx.translate(cx, cy);
// Gives each photo a realistic "tossed onto a table" rotation
const angle = (Math.random() - 0.5) * 0.5; // ~ ±14 degrees tilt
ctx.rotate(angle);
// Polaroid drop shadow
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 18;
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 8;
// Draw Polaroid white frame
ctx.fillStyle = '#fefefe';
ctx.fillRect(-pw / 2, -ph / 2, pw, ph);
// Turn off shadows to draw the image without shadow bleeding over the frame
ctx.shadowColor = 'transparent';
// Apply a warm reunion/nostalgia filter
ctx.filter = "sepia(0.20) contrast(1.1) brightness(1.05)";
ctx.drawImage(originalImg, -imgW / 2, -ph / 2 + padding, imgW, imgH);
ctx.filter = "none";
// Draw Friend's Name as a handwritten marker text at the bottom
ctx.fillStyle = "#2c3e50";
ctx.font = "bold 26px 'Comic Sans MS', 'Caveat', cursive, sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(names[i] || "Друг", 0, ph / 2 - bottomPadding / 2 + 5);
// Add a piece of holding tape to the top margin of the photo
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
ctx.translate(0, -ph / 2 + 10);
ctx.rotate((Math.random() - 0.5) * 0.2); // tape is also slightly angled
ctx.fillRect(-35, -12, 70, 24);
// Slight tape borders
ctx.strokeStyle = 'rgba(0,0,0,0.05)';
ctx.lineWidth = 1;
ctx.strokeRect(-35, -12, 70, 24);
ctx.restore();
}
// 5. Draw the festive Title Banner at the top
ctx.save();
ctx.shadowColor = 'rgba(0,0,0,0.6)';
ctx.shadowBlur = 20;
ctx.shadowOffsetY = 10;
// Ribbon banner shape
ctx.fillStyle = '#ff4757';
ctx.beginPath();
const bWidth = Math.min(800, width - 80);
ctx.moveTo(width / 2 - bWidth / 2, 30);
ctx.lineTo(width / 2 + bWidth / 2, 30);
ctx.lineTo(width / 2 + bWidth / 2 - 25, 70);
ctx.lineTo(width / 2 + bWidth / 2, 110);
ctx.lineTo(width / 2 - bWidth / 2, 110);
ctx.lineTo(width / 2 - bWidth / 2 + 25, 70);
ctx.closePath();
ctx.fill();
// Inner decorative dashed/solid line on the banner
ctx.shadowColor = 'transparent';
ctx.strokeStyle = '#ff6b81';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(width / 2 - bWidth / 2 + 20, 38);
ctx.lineTo(width / 2 + bWidth / 2 - 20, 38);
ctx.lineTo(width / 2 + bWidth / 2 - 38, 70);
ctx.lineTo(width / 2 + bWidth / 2 - 20, 102);
ctx.lineTo(width / 2 - bWidth / 2 + 20, 102);
ctx.lineTo(width / 2 - bWidth / 2 + 38, 70);
ctx.closePath();
ctx.stroke();
// Banner Text rendering
ctx.fillStyle = '#ffffff';
// Dynamic text sizing based on length limits overflow
let fontSize = 44;
if (caption.length > 25) fontSize = 34;
if (caption.length > 40) fontSize = 26;
ctx.font = `bold ${fontSize}px 'Arial', sans-serif`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(caption, width / 2, 72);
ctx.restore();
return canvas;
}
Apply Changes