You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, wantedName = "UNKNOWN DESPERADO", reward = "$5,000", crime = "Horse Thievery & General Ruffianism") {
// Helper function to wrap text and return the Y position after the text
// Assumes ctx.textAlign and ctx.textBaseline are set by the caller (e.g., 'center', 'top')
function wrapTextAndGetNextY(ctx, text, x, y, maxWidth, lineHeight) {
const words = String(text).split(' ');
let line = '';
let currentDrawY = y;
for (let n = 0; n < words.length; n++) {
const word = words[n];
let testLine = line + (line ? ' ' : '') + word; // Add space if line is not empty
if (ctx.measureText(testLine).width > maxWidth && line !== '') {
ctx.fillText(line, x, currentDrawY); // Draw the current line
line = word; // Start a new line with the current word
currentDrawY += lineHeight;
} else {
line = testLine; // Add word to current line
}
}
// Draw the last remaining line
if (line.trim() !== '') {
ctx.fillText(line, x, currentDrawY);
}
if (String(text).trim() === '') {
return currentDrawY; // No text, no height increase
}
// Return Y for the *start* of the next text block (after this one's last line + lineHeight)
return currentDrawY + lineHeight;
}
// Helper function to add subtle texture to background
function addBackgroundTexture(ctx, width, height) {
ctx.save();
const numSpeckles = 70000; // Adjust for density
for (let i = 0; i < numSpeckles; i++) {
const speckleX = Math.random() * width;
const speckleY = Math.random() * height;
const size = Math.random() * 1.5 + 0.5;
const r = Math.floor(Math.random() * 30) + 40; // Dark brownish speckles
const g = Math.floor(Math.random() * 30) + 30;
const b = Math.floor(Math.random() * 30) + 20;
const alpha = Math.random() * 0.08 + 0.02; // Very low opacity
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
ctx.fillRect(speckleX, speckleY, size, size);
}
ctx.restore();
}
// 1. Font Loading
const FONT_MAIN = 'Rye';
const FONT_SUB = 'IM Fell English SC'; // Note: Google Fonts serves "IM Fell English SC" correctly
const fontUrl = `https://fonts.googleapis.com/css2?family=${FONT_MAIN.replace(' ', '+')}&family=${FONT_SUB.replace(' ', '+').replace('"', '')}&display=swap`;
const fontStyleId = 'wanted-poster-fonts';
if (!document.getElementById(fontStyleId)) {
const link = document.createElement('link');
link.id = fontStyleId;
link.rel = 'stylesheet';
link.href = fontUrl;
document.head.appendChild(link);
try {
// Wait for fonts to be loaded and ready
await Promise.all([
document.fonts.load(`40px "${FONT_MAIN}"`), // Quotes for safety, though Rye has no spaces
document.fonts.load(`20px "${FONT_SUB}"`) // IM Fell English SC has spaces
]);
} catch (err) {
console.warn("Font loading failed or timed out. Using fallback fonts.", err);
}
}
// 2. Canvas Setup
const canvas = document.createElement('canvas');
const C_WIDTH = 600;
const C_HEIGHT = 900;
canvas.width = C_WIDTH;
canvas.height = C_HEIGHT;
const ctx = canvas.getContext('2d');
// 3. Colors
const BG_COLOR = '#F5E8C9'; // Parchment
const TEXT_COLOR_MAIN = '#4A3B31'; // Dark brown
const TEXT_COLOR_SUB = '#5A4B41'; // Slightly lighter brown
const BORDER_COLOR = '#3A2B21'; // Very dark brown
// 4. Background
ctx.fillStyle = BG_COLOR;
ctx.fillRect(0, 0, C_WIDTH, C_HEIGHT);
addBackgroundTexture(ctx, C_WIDTH, C_HEIGHT);
// 5. Borders
const OUTER_BORDER_WIDTH = 15;
ctx.strokeStyle = BORDER_COLOR;
ctx.lineWidth = OUTER_BORDER_WIDTH;
ctx.strokeRect(OUTER_BORDER_WIDTH / 2, OUTER_BORDER_WIDTH / 2, C_WIDTH - OUTER_BORDER_WIDTH, C_HEIGHT - OUTER_BORDER_WIDTH);
const INNER_BORDER_MARGIN = OUTER_BORDER_WIDTH + 10;
ctx.lineWidth = 2;
ctx.strokeStyle = TEXT_COLOR_MAIN;
ctx.strokeRect(INNER_BORDER_MARGIN, INNER_BORDER_MARGIN, C_WIDTH - 2 * INNER_BORDER_MARGIN, C_HEIGHT - 2 * INNER_BORDER_MARGIN);
// --- Content Layout ---
const contentMarginX = INNER_BORDER_MARGIN + 25;
const contentMarginTop = INNER_BORDER_MARGIN + 20;
const contentWidth = C_WIDTH - 2 * contentMarginX;
let currentY = contentMarginTop;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
// 6. "WANTED" Text
ctx.fillStyle = TEXT_COLOR_MAIN;
const wantedText = "WANTED";
const wantedFontSize = Math.max(20, Math.min(90, contentWidth / Math.max(1, wantedText.length * 0.65)));
ctx.font = `bold ${wantedFontSize}px "${FONT_MAIN}"`;
ctx.fillText(wantedText, C_WIDTH / 2, currentY);
currentY += wantedFontSize * 1.0 + 10;
// 7. "DEAD OR ALIVE" Text
ctx.fillStyle = TEXT_COLOR_SUB;
const deadOrAliveText = "DEAD OR ALIVE";
const deadOrAliveFontSize = Math.max(15, Math.min(30, contentWidth / Math.max(1, deadOrAliveText.length * 0.5)));
ctx.font = `${deadOrAliveFontSize}px "${FONT_SUB}"`;
ctx.fillText(deadOrAliveText, C_WIDTH / 2, currentY);
currentY += deadOrAliveFontSize * 1.0 + 20;
// 8. Image Section
const imgContainerX = contentMarginX;
const imgContainerY = currentY;
const imgContainerWidth = contentWidth;
const imgContainerHeight = 300;
ctx.strokeStyle = BORDER_COLOR;
ctx.lineWidth = 4;
ctx.strokeRect(imgContainerX, imgContainerY, imgContainerWidth, imgContainerHeight);
let imgRenderedSuccessfully = false;
const imgValid = originalImg && typeof originalImg.width === 'number' && originalImg.width > 0 &&
typeof originalImg.height === 'number' && originalImg.height > 0;
if (imgValid) {
const imgPadding = 5;
const availableWidthForImage = imgContainerWidth - 2 * imgPadding;
const availableHeightForImage = imgContainerHeight - 2 * imgPadding;
const imgAspectRatio = originalImg.width / originalImg.height;
let drawWidth = availableWidthForImage;
let drawHeight = drawWidth / imgAspectRatio;
if (drawHeight > availableHeightForImage) {
drawHeight = availableHeightForImage;
drawWidth = drawHeight * imgAspectRatio;
}
drawWidth = Math.max(1, drawWidth);
drawHeight = Math.max(1, drawHeight);
const imgDrawX = imgContainerX + imgPadding + (availableWidthForImage - drawWidth) / 2;
const imgDrawY = imgContainerY + imgPadding + (availableHeightForImage - drawHeight) / 2;
ctx.save();
ctx.filter = 'sepia(0.75) grayscale(0.15) contrast(1.05) brightness(0.95)';
try {
ctx.drawImage(originalImg, imgDrawX, imgDrawY, drawWidth, drawHeight);
imgRenderedSuccessfully = true;
} catch (e) {
console.error("Error drawing image:", e);
}
ctx.restore();
}
if (!imgRenderedSuccessfully) {
const phRectX = imgContainerX + 5;
const phRectY = imgContainerY + 5;
const phRectW = imgContainerWidth - 10;
const phRectH = imgContainerHeight - 10;
ctx.fillStyle = '#D8C8B8'; // Placeholder box color
ctx.fillRect(phRectX, phRectY, phRectW, phRectH);
ctx.fillStyle = TEXT_COLOR_MAIN;
const prevFont = ctx.font; // Save current settings
const prevBaseline = ctx.textBaseline;
const prevAlign = ctx.textAlign;
ctx.font = `22px "${FONT_SUB}"`; // Use sub font for placeholder text
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText("IMAGE UNAVAILABLE", phRectX + phRectW / 2, phRectY + phRectH / 2);
ctx.font = prevFont; // Restore settings
ctx.textBaseline = prevBaseline;
ctx.textAlign = prevAlign;
}
currentY = imgContainerY + imgContainerHeight + 25; // Space after image area
// Restore main text drawing settings (wrapText helper assumes these are set)
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
// 9. Wanted Name
ctx.fillStyle = TEXT_COLOR_MAIN;
const nameFontSize = Math.max(18, Math.min(50, contentWidth / Math.max(1, wantedName.length * 0.35)));
const nameLineHeight = nameFontSize * 1.15;
ctx.font = `${nameFontSize}px "${FONT_MAIN}"`;
currentY = wrapTextAndGetNextY(ctx, wantedName.toUpperCase(), C_WIDTH / 2, currentY, contentWidth, nameLineHeight);
currentY += 15;
// 10. Crime Text
ctx.fillStyle = TEXT_COLOR_SUB;
const crimeFontSize = Math.max(14, Math.min(28, contentWidth / Math.max(1, crime.length * 0.25)));
const crimeLineHeight = crimeFontSize * 1.2;
ctx.font = `${crimeFontSize}px "${FONT_SUB}"`;
const crimeFormatted = `FOR ${crime.toUpperCase()}`;
currentY = wrapTextAndGetNextY(ctx, crimeFormatted, C_WIDTH / 2, currentY, contentWidth * 0.95, crimeLineHeight);
currentY += 30;
// 11. Reward Text
ctx.fillStyle = TEXT_COLOR_MAIN;
const rewardFontSize = Math.max(20, Math.min(60, contentWidth / Math.max(1, reward.length * 0.4)));
const rewardLineHeight = rewardFontSize * 1.0;
ctx.font = `${rewardFontSize}px "${FONT_MAIN}"`;
currentY = wrapTextAndGetNextY(ctx, reward.toUpperCase(), C_WIDTH / 2, currentY, contentWidth, rewardLineHeight);
return canvas;
}
Apply Changes