You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
titleText = "THE PIXEL HERO",
taglineText = "IN A WORLD OF CODE, ONE IMAGE STANDS OUT",
releaseText = "COMING SOON TO YOUR BROWSER",
titleFont = "Bangers",
bodyFont = "Roboto",
titleColor = "#FFFF00", // Bright Yellow
taglineColor = "#FFFFFF", // White
releaseTextColor = "#DDDDDD", // Light Gray
titleStrokeColor = "#000000", // Black
bgGradientStart = "#000033", // Dark Navy Blue
bgGradientEnd = "#101010", // Very Dark Gray / Off-black
imageEffect = "none" // 'none', 'grayscale', 'sepia', 'darken'
) {
const FONT_SOURCES = {
"Bangers": { regular: "https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5la2bxii28.woff2" },
"Roboto": {
regular: "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2",
bold: "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlvAx0.woff2"
},
"Anton": { regular: "https://fonts.gstatic.com/s/anton/v25/1Ptgg87LROyWN4o4AMpKrental.woff2" },
"Luckiest Guy": { regular: "https://fonts.gstatic.com/s/luckiestguy/v18/_gP_1RsWVWG0YO2uTA7uXcrFQRZTbF48QA.woff2" }
};
async function loadFontVariant(fontFamily, weight = 'normal', style = 'normal', url) {
const fontFace = new FontFace(fontFamily, `url(${url})`, { weight, style });
try {
await fontFace.load();
document.fonts.add(fontFace);
} catch (e) {
console.error(`Error loading font variant ${fontFamily} ${weight} ${style} from ${url}:`, e);
// Not re-throwing, to allow fallback to system fonts if loading fails
}
}
async function ensureFontIsAvailable(fontFamilyName) {
const fontData = FONT_SOURCES[fontFamilyName];
if (!fontData) {
if (!document.fonts.check(`12px "${fontFamilyName}"`)) {
console.warn(`Font ${fontFamilyName} is not in predefined list and may not be available. System fallback may occur.`);
}
return;
}
const loadPromises = [];
if (fontData.regular) {
// Check before loading: font specification string includes weight and style if they are non-normal
if (!document.fonts.check(`12px "${fontFamilyName}"`)) {
loadPromises.push(loadFontVariant(fontFamilyName, 'normal', 'normal', fontData.regular));
}
}
if (fontData.bold) {
if (!document.fonts.check(`bold 12px "${fontFamilyName}"`)) {
loadPromises.push(loadFontVariant(fontFamilyName, 'bold', 'normal', fontData.bold));
}
}
// Add more variants (italic, etc.) if needed in FONT_SOURCES
if (loadPromises.length > 0) {
await Promise.all(loadPromises);
}
}
// 1. Load fonts
// Use a Set to avoid loading the same font family multiple times if titleFont and bodyFont are the same
const fontsToLoad = new Set([titleFont, bodyFont]);
const fontLoadingPromises = [];
fontsToLoad.forEach(fontName => {
fontLoadingPromises.push(ensureFontIsAvailable(fontName));
});
await Promise.all(fontLoadingPromises);
// 2. Canvas setup
const canvas = document.createElement('canvas');
const canvasWidth = 600;
const canvasHeight = 900;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
// 3. Background
const bgGradient = ctx.createRadialGradient(
canvasWidth / 2, canvasHeight / 2, 0,
canvasWidth / 2, canvasHeight / 2, Math.max(canvasWidth, canvasHeight) / 1.5
);
bgGradient.addColorStop(0, bgGradientStart);
bgGradient.addColorStop(1, bgGradientEnd);
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// 4. Main Image
const imgContainerMaxW = canvasWidth * 0.90; // Corrected: Removed underscore
const imgContainerMaxH = canvasHeight * 0.50; // Slightly reduced height for more text space
const imgContainerY = canvasHeight * 0.22; // Adjusted Y position for image
let scaledImgW = originalImg.width;
let scaledImgH = originalImg.height;
const aspectRatio = originalImg.width / originalImg.height;
if (scaledImgW > imgContainerMaxW) {
scaledImgW = imgContainerMaxW;
scaledImgH = scaledImgW / aspectRatio;
}
if (scaledImgH > imgContainerMaxH) {
scaledImgH = imgContainerMaxH;
scaledImgW = scaledImgH * aspectRatio;
}
// Ensure it still fits if one dimension was initially smaller but the other got clamped
if (scaledImgW > imgContainerMaxW) {
scaledImgW = imgContainerMaxW;
scaledImgH = scaledImgW / aspectRatio;
}
const imgX = (canvasWidth - scaledImgW) / 2;
const imgY = imgContainerY;
// Apply image effect
let filterString = "none";
if (imageEffect === "grayscale") filterString = "grayscale(100%)";
else if (imageEffect === "sepia") filterString = "sepia(100%)";
else if (imageEffect === "darken") filterString = "brightness(60%)";
ctx.save(); // Save context state before applying filter
if (filterString !== "none") {
ctx.filter = filterString;
}
ctx.drawImage(originalImg, imgX, imgY, scaledImgW, scaledImgH);
// Optional: Add a subtle border to the image
ctx.filter = "none"; // Reset filter before drawing border if border shouldn't be filtered
ctx.strokeStyle = "#444444"; // Dark gray border
ctx.lineWidth = 2;
ctx.strokeRect(imgX, imgY, scaledImgW, scaledImgH);
ctx.restore(); // Restore context state (removes filter)
// 5. Text Elements
ctx.fillStyle = titleColor; // Default for most text operations from here
// --- Title Text ---
const titleFontSize = Math.floor(canvasWidth / (titleText.length > 15 ? 9 : 7)); // Adjust size for longer titles
ctx.font = `${titleFontSize}px "${titleFont}", sans-serif`; // Fallback to sans-serif
ctx.textAlign = "left"; // For calculating position based on measureText correctly
const titleMetrics = ctx.measureText(titleText.toUpperCase());
const titleActualX = (canvasWidth - titleMetrics.width) / 2;
const titleActualY = canvasHeight * 0.17; // Positioned above the image
ctx.strokeStyle = titleStrokeColor;
ctx.lineWidth = Math.max(2, titleFontSize / 18);
ctx.strokeText(titleText.toUpperCase(), titleActualX, titleActualY);
ctx.fillStyle = titleColor;
ctx.fillText(titleText.toUpperCase(), titleActualX, titleActualY);
// Common Y start for text below image
const textBlockStartY = imgY + scaledImgH + canvasHeight * 0.04;
// --- Release Text ---
const releaseFontSize = Math.floor(canvasWidth / 22);
ctx.font = `bold ${releaseFontSize}px "${bodyFont}", sans-serif`;
ctx.fillStyle = releaseTextColor;
ctx.textAlign = "center";
const releaseTextY = textBlockStartY + releaseFontSize;
ctx.fillText(releaseText.toUpperCase(), canvasWidth / 2, releaseTextY);
// --- Tagline Text ---
const taglineFontSize = Math.floor(canvasWidth / 28);
ctx.font = `bold ${taglineFontSize}px "${bodyFont}", sans-serif`; // Using bold body font
ctx.fillStyle = taglineColor;
ctx.textAlign = "center";
const taglineTextY = releaseTextY + taglineFontSize * 1.5 + canvasHeight * 0.02; // Add some space
// Simple line wrapping for tagline
const maxTaglineWidth = canvasWidth * 0.8;
const words = taglineText.toUpperCase().split(' ');
let line = '';
let currentTaglineY = taglineTextY;
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = ctx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxTaglineWidth && n > 0) {
ctx.fillText(line.trim(), canvasWidth / 2, currentTaglineY);
line = words[n] + ' ';
currentTaglineY += taglineFontSize * 1.2; // Line height
} else {
line = testLine;
}
}
ctx.fillText(line.trim(), canvasWidth / 2, currentTaglineY);
// --- Credits Text (Placeholder) ---
const creditsFontSize = Math.floor(canvasWidth / 45);
ctx.font = `${creditsFontSize}px "${bodyFont}", sans-serif`;
ctx.fillStyle = "#999999"; // Dimmer color for credits
ctx.textAlign = "center";
const creditsText = "A JAVASCRIPT PRODUCTION \u2022 PROPERTY OF THE INTERNET";
const creditsY = canvasHeight - creditsFontSize * 1.5; // Corrected: Removed underscore, near bottom
ctx.fillText(creditsText, canvasWidth / 2, creditsY);
return canvas;
}
Apply Changes