You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
spellTitle = "Ancient Incantation",
spellText = "Hoc est exemplar textūs pro cantibus et incantationibus. Verba hic scripta potentiam arcanam evocant, et symbola in pagina dispersa energias mysticas amplificant. Cave ne incaute legas, nam antiqui spiritus observant.",
fontFamilyName = "MedievalSharp",
textColor = "#3a2d1d", // Dark Brown
paperColor = "#f5f5dc", // Beige / Old Paper
stainColor = "rgba(160, 82, 45, 0.15)", // Sienna with alpha for stains
imageBorderColor = "#5d4037", // Darker Brown
imageBorderWidth = 3,
titleFontSize = 40,
textFontSize = 18,
pagePadding = 40,
canvasWidth = 700,
canvasHeight = 1000
) {
const fontMap = {
"MedievalSharp": "https://fonts.gstatic.com/s/medievalsharp/v26/EvOJzAlL3oU5AQl2mP5KdgptvezUpanEwBM.woff2",
"Uncial Antiqua": "https://fonts.gstatic.com/s/uncialantiqua/v22/N0bM2S5WOex4OUbESzoESK-i-MfX4zk.woff2",
"Cinzel Decorative": "https://fonts.gstatic.com/s/cinzeldecorative/v19/daaHSScvJGqLYhGmdBAKDGNocIWomJwMAKSK.woff2"
};
async function loadWebFont(fontFamily, url) {
if (!url) return false;
for (const font of document.fonts) {
if (font.family === fontFamily && font.status === 'loaded') {
return true;
}
}
if (document.fonts.check(`12px "${fontFamily}"`)) {
try {
await document.fonts.load(`12px "${fontFamily}"`);
return true;
} catch (e) { /* Will try FontFace loading next */ }
}
const fontFace = new FontFace(fontFamily, `url(${url}) format('woff2')`);
try {
await fontFace.load();
document.fonts.add(fontFace);
return true;
} catch (e) {
console.error(`Failed to load font ${fontFamily} from ${url}:`, e);
return false;
}
}
let actualFontFamily = "serif";
const fontUrl = fontMap[fontFamilyName];
if (fontUrl) {
const fontLoaded = await loadWebFont(fontFamilyName, fontUrl);
if (fontLoaded) {
actualFontFamily = `"${fontFamilyName}"`;
} else {
console.warn(`Failed to load web font ${fontFamilyName}. Using fallback '${actualFontFamily}'.`);
}
} else {
const generics = ["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui"];
if (generics.includes(fontFamilyName.toLowerCase())) {
actualFontFamily = fontFamilyName;
} else {
actualFontFamily = `"${fontFamilyName}"`;
if (!document.fonts.check(`12px ${actualFontFamily}`)) {
console.warn(`Font ${actualFontFamily} might not be available. Browser will attempt to use it or fall back.`);
}
}
}
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
ctx.fillStyle = paperColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
function drawRandomStains(context, count, color, maxRadius, pageW, pageH) {
context.fillStyle = color;
for (let i = 0; i < count; i++) {
const x = Math.random() * pageW;
const y = Math.random() * pageH;
const radiusX = Math.random() * maxRadius + 10;
const radiusY = Math.random() * maxRadius + 10;
const rotation = Math.random() * Math.PI * 2;
context.beginPath();
context.ellipse(x, y, radiusX, radiusY, rotation, 0, Math.PI * 2);
context.fill();
}
}
drawRandomStains(ctx, 20, stainColor, canvasWidth / 10, canvas.width, canvas.height);
let currentY = pagePadding;
const contentAreaWidth = canvas.width - 2 * pagePadding;
const contentAreaX = pagePadding;
const pageBottomMargin = canvas.height - pagePadding;
function getWrappedTextLines(context, text, maxWidth, fontStyleForCalc, baseFontSize) {
const words = String(text).split(' ');
const lines = [];
let currentLine = "";
context.font = fontStyleForCalc; // Set font for measurements
let M_height_approx = baseFontSize;
const tm = context.measureText("M"); // Metrics for a capital M
if (tm.actualBoundingBoxAscent && tm.actualBoundingBoxDescent) {
M_height_approx = tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent;
} else if (tm.fontBoundingBoxAscent && tm.fontBoundingBoxDescent) {
M_height_approx = tm.fontBoundingBoxAscent + tm.fontBoundingBoxDescent;
}
M_height_approx = Math.max(M_height_approx, baseFontSize * 0.8); // Ensure some sensible minimum
for (let i = 0; i < words.length; i++) {
const word = words[i];
const testLine = currentLine === "" ? word : currentLine + " " + word;
const metrics = context.measureText(testLine);
if (metrics.width > maxWidth && currentLine !== "") {
lines.push({ text: currentLine, height: M_height_approx });
currentLine = word;
} else {
currentLine = testLine;
}
}
if (currentLine !== "") {
lines.push({ text: currentLine, height: M_height_approx });
}
return lines;
}
// --- Spell Title ---
ctx.fillStyle = textColor;
ctx.textAlign = 'center';
const titleFont = `bold ${titleFontSize}px ${actualFontFamily}`;
currentY += titleFontSize; // Initial baseline for the first line
const titleLines = getWrappedTextLines(ctx, spellTitle, contentAreaWidth, titleFont, titleFontSize);
ctx.font = titleFont; // Set font for drawing title
titleLines.forEach(line => {
if (currentY < pageBottomMargin){
ctx.fillText(line.text, canvas.width / 2, currentY);
currentY += line.height * 1.2;
}
});
currentY += titleFontSize * 0.3; // Spacing after title block
// --- Image ---
const spaceForImageAndText = pageBottomMargin - currentY;
const imgMaxW = contentAreaWidth;
// Allocate roughly 60% of remaining vertical space for image, ensure some min height
const imgMaxH = Math.max(50, spaceForImageAndText * 0.55);
const aspectRatio = originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0 ?
originalImg.naturalWidth / originalImg.naturalHeight : 1;
let dispW = imgMaxW;
let dispH = dispW / aspectRatio;
if (dispH > imgMaxH) {
dispH = imgMaxH;
dispW = dispH * aspectRatio;
}
if (dispW > imgMaxW) {
dispW = imgMaxW;
dispH = dispW / aspectRatio;
}
dispW = Math.max(10, dispW); // Ensure min dimensions
dispH = Math.max(10, dispH);
const imgX = (canvas.width - dispW) / 2;
const imgY = currentY; // Image top aligns with currentY
// Check if image fits before drawing
if (imgY + dispH < pageBottomMargin - (textFontSize * 2)) { // Reserve space for at least 2 lines of text
if (originalImg.complete && originalImg.naturalWidth > 0) { // Ensure image is loaded
if (imageBorderWidth > 0) {
ctx.strokeStyle = imageBorderColor;
ctx.lineWidth = imageBorderWidth;
ctx.strokeRect(
imgX - imageBorderWidth / 2,
imgY - imageBorderWidth / 2,
dispW + imageBorderWidth,
dispH + imageBorderWidth
);
}
ctx.drawImage(originalImg, imgX, imgY, dispW, dispH);
}
currentY += dispH; // Advance Y to bottom of image
}
currentY += textFontSize; // currentY becomes baseline for the first line of spell text
// --- Spell Text ---
ctx.fillStyle = textColor;
ctx.textAlign = 'left';
const textFont = `${textFontSize}px ${actualFontFamily}`;
const textStartX = contentAreaX + 10;
const spellTextLines = getWrappedTextLines(ctx, spellText, contentAreaWidth - 20, textFont, textFontSize);
ctx.font = textFont; // Set font for drawing text
spellTextLines.forEach(line => {
if (currentY < pageBottomMargin) {
ctx.fillText(line.text, textStartX, currentY);
currentY += line.height * 1.4;
}
});
return canvas;
}
Apply Changes