You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
bgColor = "rgb(245, 240, 230)", // Aged paper background
frameColor = "rgb(80, 60, 40)", // Dark sepia/brown for frame lines
textColor = "rgb(60, 40, 20)", // Darker brown for text
pageMargin = 30, // Margin around the entire framed content
imagePadding = 20, // Padding between inner frame line and image
outerBorderWidth = 3, // Thickness of the outermost frame line
innerBorderWidth = 1, // Thickness of the inner frame line
borderGap = 4, // Gap between outer and inner frame lines
labelBoxHeight = 70, // Height for the caption area below the image
labelBoxInternalPadding = 10, // Padding inside label box for text
titleText = "PLATE VII.", // Text above the frame
figureText = "Fig. 3.", // Figure number text in label box
captionText = "A curious specimen observed in the field.", // Caption text in label box
fontFamily = "Georgia, serif", // Font for all text
baseFontSize = 16 // Base font size for scaling title and caption
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imgW = originalImg.width;
const imgH = originalImg.height;
// Calculate text-related heights
const effectiveTitleFontSize = baseFontSize * 1.2;
let titleAreaHeight = 0;
if (titleText && titleText.trim() !== "") {
titleAreaHeight = effectiveTitleFontSize + 15; // Font size + spacing
} else {
titleAreaHeight = 5; // Minimal top spacing if no title
}
const effectiveCaptionFontSize = baseFontSize * 0.85;
// Calculate dimensions from the inside out
// 1. Area for image content (image + its padding)
const imgPaddedW = imgW + 2 * imagePadding;
const imgPaddedH = imgH + 2 * imagePadding;
// 2. Total content area width (this is consistent for the frame parts)
// This is the width *inside* the innermost border line.
const contentAreaW = imgPaddedW;
// Total content area height (image part + label box part)
// This is the height *inside* the innermost border line.
let contentAreaH = imgPaddedH;
if (labelBoxHeight > 0 && (figureText.trim() !== "" || captionText.trim() !== "")) {
contentAreaH += labelBoxHeight;
} else if (labelBoxHeight > 0) { // reserve space even if text is empty but height is given
contentAreaH += labelBoxHeight;
}
// 3. Dimensions including borders and gaps, building outwards
let currentTotalContentW = contentAreaW;
let currentTotalContentH = contentAreaH;
// Add inner border thickness
currentTotalContentW += 2 * innerBorderWidth;
currentTotalContentH += 2 * innerBorderWidth;
// Add gap between borders
currentTotalContentW += 2 * borderGap;
currentTotalContentH += 2 * borderGap;
// Add outer border thickness
currentTotalContentW += 2 * outerBorderWidth;
currentTotalContentH += 2 * outerBorderWidth;
const totalFramedW = currentTotalContentW; // Full width of the framed element
const totalFramedH = currentTotalContentH; // Full height of the framed element
// 4. Calculate final canvas dimensions
canvas.width = totalFramedW + 2 * pageMargin;
canvas.height = totalFramedH + 2 * pageMargin + titleAreaHeight;
// --- Start Drawing ---
// 1. Fill canvas background
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. Draw Title Text (if any)
if (titleText && titleText.trim() !== "") {
ctx.fillStyle = textColor;
ctx.font = `${effectiveTitleFontSize}px ${fontFamily}`;
ctx.textAlign = "center";
const titleX = canvas.width / 2;
const titleY = pageMargin + effectiveTitleFontSize; // Baseline for text
ctx.fillText(titleText, titleX, titleY);
}
// 3. Draw the Frame
// Initial top-left for drawing the outermost frame element
let frameBoundaryX = pageMargin;
let frameBoundaryY = pageMargin + titleAreaHeight;
let frameBoundaryW = totalFramedW;
let frameBoundaryH = totalFramedH;
ctx.strokeStyle = frameColor;
// Draw Outer Border Line
ctx.lineWidth = outerBorderWidth;
ctx.strokeRect(
frameBoundaryX + outerBorderWidth / 2, // Offset to keep line within bounds
frameBoundaryY + outerBorderWidth / 2,
frameBoundaryW - outerBorderWidth,
frameBoundaryH - outerBorderWidth
);
// Move inwards past this border
frameBoundaryX += outerBorderWidth;
frameBoundaryY += outerBorderWidth;
frameBoundaryW -= 2 * outerBorderWidth;
frameBoundaryH -= 2 * outerBorderWidth;
// Account for Gap (this area remains bgColor)
frameBoundaryX += borderGap;
frameBoundaryY += borderGap;
frameBoundaryW -= 2 * borderGap;
frameBoundaryH -= 2 * borderGap;
// Draw Inner Border Line
ctx.lineWidth = innerBorderWidth;
ctx.strokeRect(
frameBoundaryX + innerBorderWidth / 2,
frameBoundaryY + innerBorderWidth / 2,
frameBoundaryW - innerBorderWidth,
frameBoundaryH - innerBorderWidth
);
// Move inwards past this border to get the content area
frameBoundaryX += innerBorderWidth;
frameBoundaryY += innerBorderWidth;
// frameBoundaryW -= 2 * innerBorderWidth; // This is now contentAreaW
// frameBoundaryH -= 2 * innerBorderWidth; // This is now contentAreaH
const finalContentX = frameBoundaryX;
const finalContentY = frameBoundaryY;
// 4. Draw the Image
const imgDestX = finalContentX + imagePadding;
const imgDestY = finalContentY + imagePadding;
ctx.drawImage(originalImg, imgDestX, imgDestY, imgW, imgH);
// 5. Draw Separator Line and Label Box Texts (if labelBoxHeight is specified and text exists)
if (labelBoxHeight > 0 && (figureText.trim() !== "" || captionText.trim() !== "")) {
const separatorY = finalContentY + imgPaddedH; // Y-coordinate for the line
ctx.strokeStyle = frameColor;
ctx.lineWidth = innerBorderWidth; // Separator line same as inner border
ctx.beginPath();
ctx.moveTo(finalContentX, separatorY);
ctx.lineTo(finalContentX + contentAreaW, separatorY); // Line spans content width
ctx.stroke();
// Prepare for Label Box Text
ctx.fillStyle = textColor;
ctx.font = `${effectiveCaptionFontSize}px ${fontFamily}`;
ctx.textAlign = "center"; // Default alignment for label text
let textCurrentY = separatorY + labelBoxInternalPadding + effectiveCaptionFontSize; // Baseline for first line
const textCenterX = finalContentX + contentAreaW / 2; // Centered horizontally
if (figureText && figureText.trim() !== "") {
ctx.fillText(figureText, textCenterX, textCurrentY);
textCurrentY += effectiveCaptionFontSize + 5; // Move Y for next line (5px spacing)
}
if (captionText && captionText.trim() !== "") {
const maxWidthForCaption = contentAreaW - 2 * labelBoxInternalPadding;
const words = captionText.split(' ');
let currentLine = '';
for (let i = 0; i < words.length; i++) {
const testLine = currentLine + words[i] + ' ';
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidthForCaption && i > 0) {
if (textCurrentY + effectiveCaptionFontSize <= separatorY + labelBoxHeight - labelBoxInternalPadding) {
ctx.fillText(currentLine.trim(), textCenterX, textCurrentY);
currentLine = words[i] + ' ';
textCurrentY += effectiveCaptionFontSize + 4; // Line height (font size + spacing)
} else { // Not enough vertical space for more lines
currentLine += "..."; // Indication of truncation
break;
}
} else {
currentLine = testLine;
}
}
if (textCurrentY + effectiveCaptionFontSize <= separatorY + labelBoxHeight - labelBoxInternalPadding) {
ctx.fillText(currentLine.trim(), textCenterX, textCurrentY);
} else if (!currentLine.endsWith("...")) { // if truncated by overflow
// attempt to draw the last bit or indicate truncation more clearly
const lastLineTest = currentLine.substring(0, currentLine.length > 20 ? 20 : currentLine.length) + "...";
ctx.fillText(lastLineTest.trim(), textCenterX, textCurrentY - (effectiveCaptionFontSize+4)); // overwrite previous potentially
}
}
}
return canvas;
}
Apply Changes