You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
stardate = "Stardate 77492.1",
location = "Alpha Centauri System",
subject = "First Contact Protocol",
logEntry = "Log entry: Encountered an unidentified vessel displaying non-hostile patterns. Attempting to establish communication. The visual data from the main screen has been archived.",
panelColor = "#00FFFF", // Cyan for borders, lines
strongTextColor = "#FFFFFF", // White for labels like "STARDATE:"
textColor = "#90EE90", // Light green for values and main text
backgroundColor = "#0A0A2A", // Very dark blue for background
fontName = "Orbitron", // Primary font family name
baseFontSize = 16 // Base font size in pixels
) {
// Helper function to load web font (defined inside to be self-contained)
async function loadWebFont(fontFamilyToLoad, fontUrl) {
if (!document.fonts) {
// console.warn("FontFace API not supported. Cannot dynamically load font.");
return false; // FontFace API not supported
}
try {
// Check if font is already available using document.fonts.check()
if (document.fonts.check(`1em "${fontFamilyToLoad}"`)) {
// console.log(`Font "${fontFamilyToLoad}" is already available or pre-loaded.`);
return true;
}
} catch (e) {
// console.warn("Error checking existing fonts with document.fonts.check():", e);
}
// Fallback or more detailed check: Iterate existing FontFace objects
// This step might be redundant if document.fonts.check() is reliable.
try {
for (const fontFace of document.fonts) {
if (fontFace.family === fontFamilyToLoad && fontFace.status === 'loaded') {
// console.log(`Font "${fontFamilyToLoad}" found in document.fonts as loaded.`);
return true;
}
}
} catch (e) {
// console.warn("Error iterating document.fonts:", e);
}
// If not loaded, try to load it via FontFace API
const font = new FontFace(fontFamilyToLoad, `url(${fontUrl})`);
try {
await font.load();
document.fonts.add(font);
// console.log(`Font "${fontFamilyToLoad}" loaded and added from ${fontUrl}.`);
return true;
} catch (e) {
console.error(`Failed to load font "${fontFamilyToLoad}" from ${fontUrl}:`, e);
return false;
}
}
// Helper function for text wrapping (defined inside)
function getWrappedTextLines(context, text, maxWidth, lineHeight) {
const words = text.split(' ');
let line = '';
const linesArray = [];
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = context.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0 && line.length > 0) { // line.length > 0 to handle very long first word
linesArray.push(line.trim());
line = words[n] + ' ';
} else {
line = testLine;
}
}
if (line.trim().length > 0) { // Push any remaining text
linesArray.push(line.trim());
}
const totalHeight = linesArray.length * lineHeight;
return { lines: linesArray, totalHeight: totalHeight };
}
const orbitronFontCdnUrl = "https://cdn.jsdelivr.net/fontsource/fonts/orbitron@5.0.3/latin-400-normal.woff2";
let actualFontFamilyString = `"${fontName}", Consolas, 'Courier New', monospace`;
if (fontName.toLowerCase() === "orbitron") {
const loaded = await loadWebFont("Orbitron", orbitronFontCdnUrl);
if (!loaded) {
// console.warn("Orbitron font failed to load, using fallbacks.");
// actualFontFamilyString will already include fallbacks.
}
}
// --- Layout Constants ---
const mainPadding = 20;
const sectionSpacing = 15; // Spacing between header/image/log sections
const smallGap = 5; // Small gap (e.g., between title and text)
const imageBorderThickness = 2;
const minContentWidthForText = 300; // Min width for text readability
const MAX_ALLOWED_IMG_WIDTH = 600;
const MAX_ALLOWED_IMG_HEIGHT = 400;
// --- Calculate dynamic dimensions (first pass) ---
// Image dimensions
let scale = 1.0;
if (originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0) {
scale = Math.min(MAX_ALLOWED_IMG_WIDTH / originalImg.naturalWidth, MAX_ALLOWED_IMG_HEIGHT / originalImg.naturalHeight, 1.0);
} else {
scale = 0; // Invalid image, will result in 0x0 display size
}
const imgDispWidth = originalImg.naturalWidth * scale;
const imgDispHeight = originalImg.naturalHeight * scale;
const contentWidth = Math.max(imgDispWidth, minContentWidthForText);
const imgXOffset = (contentWidth - imgDispWidth) / 2;
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
const headerFontSize = baseFontSize;
tempCtx.font = `${headerFontSize}px ${actualFontFamilyString}`;
const headerLineHeight = headerFontSize * 1.4;
let currentTotalHeight = mainPadding;
currentTotalHeight += headerLineHeight * 3; // Stardate, Location, Subject
currentTotalHeight += sectionSpacing;
currentTotalHeight += imgDispHeight;
currentTotalHeight += sectionSpacing;
tempCtx.font = `${headerFontSize}px ${actualFontFamilyString}`;
currentTotalHeight += headerLineHeight; // For "LOG ENTRY:" title
currentTotalHeight += smallGap;
const logEntryFontSize = Math.round(baseFontSize * 0.9);
tempCtx.font = `${logEntryFontSize}px ${actualFontFamilyString}`;
const logEntryLineHeight = logEntryFontSize * 1.4;
const wrappedLogEntry = getWrappedTextLines(tempCtx, logEntry, contentWidth, logEntryLineHeight);
currentTotalHeight += wrappedLogEntry.totalHeight;
currentTotalHeight += mainPadding;
// --- Final Canvas Setup ---
const canvas = document.createElement('canvas');
canvas.width = contentWidth + mainPadding * 2;
canvas.height = Math.max(currentTotalHeight, 200); // Ensure minimum height
const ctx = canvas.getContext('2d');
// --- Drawing Operations (second pass) ---
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = panelColor;
ctx.lineWidth = 1;
ctx.strokeRect(mainPadding / 4, mainPadding / 4, canvas.width - mainPadding / 2, canvas.height - mainPadding / 2);
let currentY = mainPadding;
const textX = mainPadding;
ctx.font = `${headerFontSize}px ${actualFontFamilyString}`;
const drawHeaderLine = (label, value) => {
ctx.fillStyle = strongTextColor;
ctx.fillText(label, textX, currentY);
const labelWidth = ctx.measureText(label).width;
ctx.fillStyle = textColor;
ctx.fillText(value, textX + labelWidth, currentY);
currentY += headerLineHeight;
};
drawHeaderLine("STARDATE: ", stardate);
drawHeaderLine("LOCATION: ", location);
drawHeaderLine("SUBJECT: ", subject);
currentY += sectionSpacing;
const imgActualX = textX + imgXOffset;
const imgActualY = currentY;
if (imgDispWidth > 0 && imgDispHeight > 0) {
try {
ctx.drawImage(originalImg, imgActualX, imgActualY, imgDispWidth, imgDispHeight);
ctx.strokeStyle = panelColor;
ctx.lineWidth = imageBorderThickness;
ctx.strokeRect(
imgActualX - imageBorderThickness / 2,
imgActualY - imageBorderThickness / 2,
imgDispWidth + imageBorderThickness,
imgDispHeight + imageBorderThickness
);
} catch (e) {
console.error("Error drawing image:", e);
// Optionally draw a placeholder if image fails
ctx.fillStyle = textColor;
ctx.fillText("[Image Error]", imgActualX + imgDispWidth/2 - ctx.measureText("[Image Error]").width/2, imgActualY + imgDispHeight/2);
}
}
currentY += imgDispHeight;
currentY += sectionSpacing;
ctx.font = `${headerFontSize}px ${actualFontFamilyString}`;
ctx.fillStyle = strongTextColor;
ctx.fillText("LOG ENTRY:", textX, currentY);
currentY += headerLineHeight;
currentY += smallGap;
ctx.font = `${logEntryFontSize}px ${actualFontFamilyString}`;
ctx.fillStyle = textColor;
wrappedLogEntry.lines.forEach(line => {
if (currentY + logEntryLineHeight < canvas.height - mainPadding / 2) { // Prevent drawing outside canvas
ctx.fillText(line, textX, currentY);
}
currentY += logEntryLineHeight;
});
return canvas;
}
Apply Changes