You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
politeText = "Моё почтение, сударь!",
frameTheme = "gold",
borderThickness = "30",
applyVintageFilter = "1"
) {
// 1. Dynamically load a classy Google Font
const fontName = 'Marck Script'; // Elegant font that supports Cyrillic and Latin
const fontUrl = 'https://fonts.googleapis.com/css2?family=Marck+Script&display=swap';
try {
if (!document.querySelector(`link[href="${fontUrl}"]`)) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = fontUrl;
document.head.appendChild(link);
}
if (document.fonts && document.fonts.load) {
await document.fonts.load(`16px "${fontName}"`);
} else {
// Fallback delay if API is unavailable
await new Promise(r => setTimeout(r, 800));
}
} catch (e) {
console.warn("Could not load manners font. Using fallback.", e);
}
// 2. Base calculations to ensure it looks proportionally "proper" on any size image
const maxDim = Math.max(originalImg.width, originalImg.height);
const scale = Math.max(maxDim / 1000, 0.3); // Responsive scaling, minimum bound
const bSize = parseInt(borderThickness, 10) * scale;
const bottomAreaHeight = 140 * scale;
const totalW = originalImg.width + bSize * 2;
const totalH = originalImg.height + bSize * 2 + bottomAreaHeight;
const canvas = document.createElement('canvas');
canvas.width = totalW;
canvas.height = totalH;
const ctx = canvas.getContext('2d');
// 3. Create elegant frame background
let frameGradient;
if (frameTheme === "silver") {
frameGradient = ctx.createLinearGradient(0, 0, totalW, totalH);
frameGradient.addColorStop(0, "#757F9A");
frameGradient.addColorStop(0.5, "#E0E5EC");
frameGradient.addColorStop(1, "#757F9A");
} else if (frameTheme === "wood") {
frameGradient = ctx.createLinearGradient(0, 0, totalW, totalH);
frameGradient.addColorStop(0, "#3e2723");
frameGradient.addColorStop(0.3, "#5d4037");
frameGradient.addColorStop(0.7, "#795548");
frameGradient.addColorStop(1, "#3e2723");
} else { // default to "gold"
frameGradient = ctx.createLinearGradient(0, 0, totalW, totalH);
frameGradient.addColorStop(0, "#BF953F");
frameGradient.addColorStop(0.25, "#FCF6BA");
frameGradient.addColorStop(0.5, "#B38728");
frameGradient.addColorStop(0.75, "#FBF5B7");
frameGradient.addColorStop(1, "#AA771C");
}
ctx.fillStyle = frameGradient;
ctx.fillRect(0, 0, totalW, totalH);
// Frame 3D Outer Bevels
ctx.lineWidth = 4 * scale;
ctx.strokeStyle = "rgba(255,255,255,0.5)";
ctx.beginPath(); ctx.moveTo(0, totalH); ctx.lineTo(0,0); ctx.lineTo(totalW, 0); ctx.stroke();
ctx.strokeStyle = "rgba(0,0,0,0.7)";
ctx.beginPath(); ctx.moveTo(totalW, 0); ctx.lineTo(totalW, totalH); ctx.lineTo(0, totalH); ctx.stroke();
// Subtle dark groove inner border
ctx.lineWidth = 4 * scale;
ctx.strokeStyle = "rgba(0,0,0,0.9)";
ctx.strokeRect(bSize, bSize, originalImg.width, originalImg.height);
// Subtle bright lip
ctx.strokeStyle = "rgba(255,255,255,0.3)";
ctx.strokeRect(bSize - 4*scale, bSize - 4*scale, originalImg.width + 8*scale, originalImg.height + 8*scale);
// 4. Draw Image with optional "Polite/Distinguished" Vintage Filter
if (String(applyVintageFilter) === "1") {
// Refined sepia and contrast boost to embody proper vintage manners
ctx.filter = 'sepia(0.35) contrast(1.1) brightness(1.05)';
}
ctx.drawImage(originalImg, bSize, bSize);
ctx.filter = 'none'; // reset filter for overlays
// Useful drawing abstraction
const drawRoundRect = (c, x, y, w, h, r) => {
c.beginPath();
c.moveTo(x + r, y);
c.arcTo(x + w, y, x + w, y + h, r);
c.arcTo(x + w, y + h, x, y + h, r);
c.arcTo(x, y + h, x, y, r);
c.arcTo(x, y, x + w, y, r);
c.closePath();
};
// 5. Layout and Draw "Good Manners" Plaque
const plaqueW = totalW * 0.8;
const plaqueH = 80 * scale;
const plaqueX = (totalW - plaqueW) / 2;
// Position vertically centered in the newly created bottom padding space
const plaqueY = originalImg.height + bSize + (bottomAreaHeight / 2) - (plaqueH / 2) + (10 * scale);
// Substantive plaque background
ctx.fillStyle = "#161616";
drawRoundRect(ctx, plaqueX, plaqueY, plaqueW, plaqueH, 12 * scale);
ctx.fill();
ctx.strokeStyle = frameGradient;
ctx.lineWidth = 4 * scale;
ctx.stroke();
// Inner subtle bright engraving line
ctx.strokeStyle = "rgba(255,255,255,0.2)";
ctx.lineWidth = 2 * scale;
drawRoundRect(ctx, plaqueX + 4*scale, plaqueY + 4*scale, plaqueW - 8*scale, plaqueH - 8*scale, 9*scale);
ctx.stroke();
// 6. Sophisticated Dissenting Accessories (Top Hat, Bow Tie, Monocle Watermark)
// --> Dangle a Monocle from the top-left inner frame
const monocleStartX = bSize;
const monocleStartY = bSize;
const monocleDrop = 100 * scale;
const mX = monocleStartX + 50 * scale;
const mY = monocleStartY + monocleDrop;
const mRadius = 24 * scale;
ctx.beginPath(); // Solid gold Monocle Chain
ctx.moveTo(monocleStartX, monocleStartY);
ctx.quadraticCurveTo(monocleStartX + 15*scale, monocleStartY + monocleDrop*0.6, mX, mY - mRadius);
ctx.strokeStyle = "#FFD700";
ctx.lineWidth = 2.5 * scale;
ctx.stroke();
ctx.beginPath(); // Monocle Glass
ctx.arc(mX, mY, mRadius, 0, Math.PI*2);
ctx.fillStyle = "rgba(255, 255, 255, 0.25)";
ctx.fill();
ctx.lineWidth = 4 * scale;
ctx.strokeStyle = "#DAA520";
ctx.stroke();
ctx.beginPath(); // Monocle rim shine reflection
ctx.arc(mX, mY, mRadius - 3*scale, Math.PI*1.1, Math.PI*1.5);
ctx.strokeStyle = "#FFF";
ctx.lineWidth = 2 * scale;
ctx.stroke();
// --> Draw the Bow Tie perfectly centered resting above the Plaque
const tieW = 40 * scale;
const tieH = 24 * scale;
const tieCenterX = totalW / 2;
const tieCenterY = plaqueY; // Sits evenly overlapping top edge
ctx.fillStyle = frameTheme === 'gold' ? '#B8860B' : '#8B0000';
ctx.strokeStyle = "#000";
ctx.lineWidth = 2 * scale;
ctx.beginPath(); // Left flipper
ctx.moveTo(tieCenterX, tieCenterY);
ctx.lineTo(tieCenterX - tieW, tieCenterY - tieH);
ctx.lineTo(tieCenterX - tieW, tieCenterY + tieH);
ctx.closePath();
ctx.fill(); ctx.stroke();
ctx.beginPath(); // Right flipper
ctx.moveTo(tieCenterX, tieCenterY);
ctx.lineTo(tieCenterX + tieW, tieCenterY - tieH);
ctx.lineTo(tieCenterX + tieW, tieCenterY + tieH);
ctx.closePath();
ctx.fill(); ctx.stroke();
ctx.beginPath(); // Tie knot
ctx.ellipse(tieCenterX, tieCenterY, tieW * 0.25, tieH * 0.45, 0, 0, Math.PI * 2);
ctx.fill(); ctx.stroke();
// --> Sit a distinguished Gentleman's Top Hat centering the upper image edge
const hatBaseY = bSize;
const hw = 50 * scale; // Half width representation
const ch = 80 * scale; // Cylinder height
const cw = hw * 0.7; // Cylinder half width
ctx.translate(totalW / 2, hatBaseY);
ctx.fillStyle = "#111"; // Brim Base
ctx.beginPath();
ctx.ellipse(0, 0, hw, 14 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = "#050505";
ctx.stroke();
ctx.fillStyle = "#1C1C1C"; // Cylinder Body
ctx.fillRect(-cw, -ch, cw*2, ch);
ctx.beginPath(); // Cylinder bottom overlap
ctx.ellipse(0, 0, cw, 8 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath(); // Cylinder top cap (3D angle offset)
ctx.ellipse(0, -ch, cw, 8 * scale, 0, 0, Math.PI * 2);
ctx.fill();
const rh = 16 * scale; // Ribbon wrapping the base
ctx.fillStyle = "#6B0000";
ctx.fillRect(-cw, -rh - (6*scale), cw*2, rh);
ctx.beginPath(); // Ribbon base curve mapping
ctx.ellipse(0, -6*scale, cw, 8 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#4A0000";
ctx.beginPath();
ctx.ellipse(0, -rh - (6*scale), cw, 8 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.resetTransform();
// 7. Write polite inscription on plaque
let fSize = 55 * scale;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#FFFFFF";
ctx.font = `${fSize}px "${fontName}", Georgia, serif`;
// Scale text down to prevent overflow and maintain etiquette bounds
let textMetrics = ctx.measureText(politeText);
while (textMetrics.width > plaqueW - 40 * scale && fSize > 12 * scale) {
fSize -= 2 * scale;
ctx.font = `${fSize}px "${fontName}", Georgia, serif`;
textMetrics = ctx.measureText(politeText);
}
ctx.shadowColor = 'rgba(255, 255, 255, 0.2)';
ctx.shadowBlur = 10 * scale;
ctx.fillText(politeText, totalW / 2, plaqueY + (plaqueH / 2) + 2*scale);
ctx.shadowBlur = 0; // Reset Shadow
return canvas;
}
Apply Changes