You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, secondImageBase64 = "", reunionText = "A Beautiful Reunion", theme = "vintage") {
// Dynamically load Google Font for elegant typography
const fontFamily = 'Playfair Display';
if (!document.getElementById('reunion-font-link')) {
const fontLink = document.createElement('link');
fontLink.id = 'reunion-font-link';
fontLink.rel = 'stylesheet';
fontLink.href = 'https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@1,600&display=swap';
document.head.appendChild(fontLink);
}
try {
await document.fonts.load(`italic 600 50px "${fontFamily}"`);
} catch (err) {
console.warn("Custom font loading failed, using fallback serif.");
}
// Load the second parent/family image or fallback to original if empty or invalid
let img2 = originalImg;
if (secondImageBase64 && secondImageBase64.trim() !== "") {
img2 = await new Promise((resolve) => {
const img = new Image();
img.crossOrigin = "Anonymous";
img.onload = () => resolve(img);
img.onerror = () => resolve(originalImg);
img.src = secondImageBase64;
});
}
// Setup Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1000;
canvas.height = 700;
// Determine theme configurations
let themeProps = {};
switch (theme.toLowerCase()) {
case "modern":
themeProps = {
bgInner: '#ffffff',
bgOuter: '#cdd3d8',
textColor: '#2c3e50',
filter: 'grayscale(0.9)',
tapeColor: 'rgba(255, 255, 255, 0.8)'
};
break;
case "romantic":
themeProps = {
bgInner: '#fff0f5',
bgOuter: '#ffb6c1',
textColor: '#c2185b',
filter: 'contrast(1.1) saturate(1.3)',
tapeColor: 'rgba(255, 220, 230, 0.8)'
};
break;
case "vintage":
default:
themeProps = {
bgInner: '#4a3b32',
bgOuter: '#1e140d',
textColor: '#e8d5b5',
filter: 'sepia(0.7) contrast(1.1)',
tapeColor: 'rgba(245, 222, 179, 0.7)'
};
break;
}
// Draw Radial Background
const bgGradient = ctx.createRadialGradient(
canvas.width / 2, canvas.height / 2, 100,
canvas.width / 2, canvas.height / 2, 700
);
bgGradient.addColorStop(0, themeProps.bgInner);
bgGradient.addColorStop(1, themeProps.bgOuter);
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Helper: Draw image containing object-fit 'cover' aspect ratio logic
function drawImageCover(ctx, img, x, y, w, h) {
const imgRatio = img.width / img.height;
const boxRatio = w / h;
let drawW, drawH, drawX, drawY;
if (imgRatio > boxRatio) {
drawH = h;
drawW = img.width * (h / img.height);
drawX = x + (w - drawW) / 2;
drawY = y;
} else {
drawW = w;
drawH = img.height * (w / img.width);
drawX = x;
drawY = y + (h - drawH) / 2;
}
ctx.drawImage(img, drawX, drawY, drawW, drawH);
}
// Helper: Draw a polaroid style frame with the image
function drawPolaroid(imageToDraw, cx, cy, angleDeg, flipHorizontally = false) {
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(angleDeg * Math.PI / 180);
// Frame Shadow
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 8;
ctx.shadowOffsetY = 12;
// Polaroid Card Base
ctx.fillStyle = '#fefdfb';
ctx.fillRect(-170, -200, 340, 410);
// Remove shadow for inner elements
ctx.shadowColor = 'transparent';
// Inner frame recess (to simulate photo paper boundary)
ctx.fillStyle = '#eaeaea';
ctx.fillRect(-150, -180, 300, 300);
// Optional Flip for creative mirroring if the same image is duplicated
if (flipHorizontally) {
ctx.scale(-1, 1);
}
// Apply theme filters to the image
ctx.filter = themeProps.filter;
// Clip region for drawing the photo
ctx.beginPath();
ctx.rect(flipHorizontally ? -150 : -150, -180, 300, 300);
ctx.clip();
// Draw image inside specific boundaries
drawImageCover(ctx, imageToDraw, flipHorizontally ? -150 : -150, -180, 300, 300);
// Reset clipping and transformations for tape
ctx.restore();
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(angleDeg * Math.PI / 180);
// Draw masking tape attached at the top of the polaroid
ctx.fillStyle = themeProps.tapeColor;
ctx.shadowColor = 'rgba(0,0,0,0.2)';
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
// Introduce slight rotation for the tape itself
ctx.rotate(-2 * Math.PI / 180);
ctx.fillRect(-60, -215, 120, 35);
ctx.restore();
}
// Determine if we are mirroring (if second photo wasn't provided, mirror the original for an artistic union)
const isSameImage = (originalImg === img2);
// Draw Left Polaroid
drawPolaroid(originalImg, 300, 300, -6, isSameImage);
// Draw Right Polaroid
drawPolaroid(img2, 700, 330, 8, false);
// Draw the reunion text banner along the bottom
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = themeProps.textColor;
ctx.font = `italic 600 55px "${fontFamily}", serif`;
// Add graceful text shadow
ctx.shadowColor = 'rgba(0, 0, 0, 0.6)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillText(reunionText, canvas.width / 2, 620);
ctx.restore();
return canvas;
}
Apply Changes