You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, captionText = "Путешественница", frameColor = "#FDFCF8") {
const fontName = 'Caveat';
// Load custom handwriting Google font for a realistic polaroid look
if (!document.getElementById('traveler-font')) {
const style = document.createElement('style');
style.id = 'traveler-font';
style.innerHTML = `@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@700&display=swap');`;
document.head.appendChild(style);
}
try {
// Wait for font with a timeout fallback
await Promise.race([
document.fonts.load(`700 30px ${fontName}`),
new Promise(resolve => setTimeout(resolve, 2000))
]);
} catch (e) {
// Fallback to defaults quietly
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Scale to a reasonable size ensuring good performance and readable text
let scale = 1;
const maxDim = 1000;
if (originalImg.width > maxDim || originalImg.height > maxDim) {
scale = maxDim / Math.max(originalImg.width, originalImg.height);
}
const imgW = originalImg.width * scale;
const imgH = originalImg.height * scale;
const mTop = Math.max(30, imgW * 0.06);
const mSide = mTop;
const mBot = Math.max(120, imgH * 0.25);
canvas.width = imgW + mSide * 2;
canvas.height = imgH + mTop + mBot;
// Draw polaroid paper back
ctx.fillStyle = frameColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Subtle edge shadowing
ctx.strokeStyle = "rgba(0, 0, 0, 0.05)";
ctx.lineWidth = 4;
ctx.strokeRect(2, 2, canvas.width - 4, canvas.height - 4);
// Draw the source user image
ctx.drawImage(originalImg, mSide, mTop, imgW, imgH);
// Outline separating photo from the frame
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)";
ctx.lineWidth = 1;
ctx.strokeRect(mSide, mTop, imgW, imgH);
// --- 1. Draw Travel Postcard Stamp Overlay (Top Right) ---
const stampX = mSide + imgW - (imgW * 0.08);
const stampY = mTop + (imgW * 0.08);
const stampRadius = Math.max(30, imgW * 0.06);
ctx.save();
ctx.translate(stampX, stampY);
ctx.rotate(Math.PI / 7);
ctx.strokeStyle = "rgba(200, 50, 50, 0.75)";
ctx.lineWidth = Math.max(2, stampRadius * 0.04);
ctx.beginPath(); ctx.arc(0, 0, stampRadius, 0, Math.PI * 2); ctx.stroke();
ctx.beginPath(); ctx.arc(0, 0, stampRadius * 0.75, 0, Math.PI * 2); ctx.stroke();
ctx.font = `bold ${stampRadius * 0.22}px "Courier New", Courier, monospace`;
ctx.fillStyle = "rgba(200, 50, 50, 0.75)";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("TRAVELER", 0, -stampRadius * 0.35);
const dateStr = new Date().toLocaleDateString('en-GB', {day: '2-digit', month: 'short', year: 'numeric'}).toUpperCase();
ctx.fillText(dateStr, 0, stampRadius * 0.35);
ctx.restore();
// Cancellation wavy lines
ctx.save();
ctx.translate(stampX - stampRadius * 1.5, stampY);
ctx.rotate(Math.PI / 12);
ctx.strokeStyle = "rgba(200, 50, 50, 0.6)";
ctx.lineWidth = Math.max(1.5, stampRadius * 0.03);
ctx.beginPath();
for (let i = -2; i <= 2; i++) {
const yLine = i * (stampRadius * 0.25);
ctx.moveTo(0, yLine);
for(let x = 0; x < stampRadius * 4; x += 5) {
ctx.lineTo(x, yLine + Math.sin(x * 0.2) * (stampRadius * 0.15));
}
}
ctx.stroke();
ctx.restore();
// --- 2. Draw Navigation Compass Overlay (Bottom Left) ---
const compassX = mSide + (imgW * 0.1);
const compassY = mTop + imgH - (imgH * 0.1);
const compassR = Math.max(25, imgW * 0.07);
ctx.save();
ctx.translate(compassX, compassY);
ctx.beginPath(); ctx.arc(0, 0, compassR * 1.1, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; ctx.fill();
ctx.beginPath(); ctx.arc(0, 0, compassR, 0, Math.PI * 2);
ctx.strokeStyle = '#333'; ctx.lineWidth = compassR * 0.05; ctx.stroke();
const drawPoint = (angle, length, width, color1, color2) => {
ctx.save();
ctx.rotate(angle);
ctx.beginPath(); ctx.moveTo(0, -length); ctx.lineTo(width, 0); ctx.lineTo(0, width * 0.2);
ctx.fillStyle = color1; ctx.fill();
ctx.beginPath(); ctx.moveTo(0, -length); ctx.lineTo(-width, 0); ctx.lineTo(0, width * 0.2);
ctx.fillStyle = color2; ctx.fill();
ctx.restore();
};
for (let i = 0; i < 4; i++) {
drawPoint((Math.PI/2) * i + Math.PI/4, compassR * 0.6, compassR * 0.15, '#666', '#999');
}
for (let i = 0; i < 4; i++) {
const primary = i === 0 ? '#cc0000' : '#444'; // point north red
const secondary = i === 0 ? '#ff4d4d' : '#777';
drawPoint((Math.PI/2) * i, compassR * 0.95, compassR * 0.2, primary, secondary);
}
ctx.beginPath(); ctx.arc(0, 0, compassR * 0.15, 0, Math.PI * 2);
ctx.fillStyle = '#fff'; ctx.fill();
ctx.strokeStyle = '#333'; ctx.stroke();
ctx.restore();
// --- 3. Draw Travel Map Location Pin (Bottom Right) ---
const pinX = mSide + imgW - (imgW * 0.1);
const pinY = mTop + imgH + (imgH * 0.05); // Pinners point slightly below image border
const pinScale = Math.max(0.5, (imgW * 0.05) / 20);
ctx.save();
ctx.translate(pinX, pinY);
ctx.scale(pinScale, pinScale);
// Pin Drop shadow
ctx.beginPath();
ctx.ellipse(0, 0, 15, 5, 0, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.fill();
// Pin shape
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.bezierCurveTo(-15, -15, -25, -25, -25, -45);
ctx.arc(0, -45, 25, Math.PI, Math.PI*2);
ctx.bezierCurveTo(25, -25, 15, -15, 0, 0);
ctx.fillStyle = '#e63946'; // Red pin color
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = '#a8dadc';
ctx.stroke();
// Pin hollow center
ctx.beginPath();
ctx.arc(0, -45, 10, 0, Math.PI*2);
ctx.fillStyle = '#f1faee';
ctx.fill();
ctx.restore();
// --- 4. Draw Handwriting Caption at the Bottom ---
let isFontLoaded = document.fonts.check(`700 30px ${fontName}`);
let fontFamily = isFontLoaded ? `'${fontName}', cursive` : `'Comic Sans MS', cursive, sans-serif`;
let fontSize = Math.max(30, mBot * 0.4);
ctx.font = `700 ${fontSize}px ${fontFamily}`;
// Scale down text if it's too wide
let textMetrics = ctx.measureText(captionText);
if (textMetrics.width > canvas.width * 0.85) {
fontSize = fontSize * ((canvas.width * 0.85) / textMetrics.width);
ctx.font = `700 ${fontSize}px ${fontFamily}`;
}
ctx.fillStyle = "#1d3557"; // Deep navy ink
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const textX = canvas.width / 2;
const textY = mTop + imgH + (mBot / 2);
ctx.save();
ctx.translate(textX, textY);
ctx.rotate(-Math.PI / 180); // Rotate roughly -1 degree for organic hand written look
ctx.fillText(captionText, 0, 0);
ctx.restore();
// --- 5. Add universal film texture/grain ---
try {
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
const noise = (Math.random() - 0.5) * 8; // Slight perturbation
data[i] = Math.min(255, Math.max(0, data[i] + noise));
data[i+1] = Math.min(255, Math.max(0, data[i+1] + noise));
data[i+2] = Math.min(255, Math.max(0, data[i+2] + noise));
}
ctx.putImageData(imgData, 0, 0);
} catch(e) {
// Proceed silently if cross-origin policy prevents pixel graining, everything else drew fine!
}
return canvas;
}
Apply Changes