You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg,
titleText = "GALACTIC WARRIORS",
titleSize = 48,
titleColor = "#FFFF00",
titleOutlineColor = "#000000",
titleOutlineWidth = 5,
platformText = "OMEGA SYSTEM",
platformTextSize = 20,
platformTextColor = "#FFFFFF",
platformBackgroundColor = "#D92C20",
taglineText = "A NEW DIMENSION OF ARCADE ACTION!",
taglineSize = 16,
taglineColor = "#FFFFFF",
ratingSymbol = "R P",
ratingBoxSize = 40,
ratingSymbolColor = "#FFFFFF",
ratingBackgroundColor = "#000000",
fontFamily = "Press Start 2P",
mainBackgroundColor = "#0E1745",
outerBorderColor = "#000000",
outerBorderWidth = 10,
enableScanlines = 1,
scanlineOpacity = 0.1,
enableNoise = 1,
noiseOpacity = 0.05
) {
const C_WIDTH = 600;
const C_HEIGHT = 800;
const FONT_URL_PRESS_START_2P = 'https://fonts.gstatic.com/s/pressstart2p/v15/e3t4euO8T-267oIAQAubmspOFF安定gb9JLCnb_SQ.woff2';
async function _ensureFontLoaded(fontNameToLoad, fontUrl) {
if (fontUrl && !document.fonts.check(`12px "${fontNameToLoad}"`)) {
const fontFace = new FontFace(fontNameToLoad, `url(${fontUrl}) format('woff2')`);
try {
await fontFace.load();
document.fonts.add(fontFace);
} catch (e) {
console.error(`Font ${fontNameToLoad} could not be loaded from ${fontUrl}:`, e);
throw new Error(`Required font ${fontNameToLoad} failed to load.`);
}
}
}
let actualFontFamily = fontFamily;
if (fontFamily === "Press Start 2P") {
try {
await _ensureFontLoaded("Press Start 2P", FONT_URL_PRESS_START_2P);
} catch (e) {
console.warn("Failed to load 'Press Start 2P'. Falling back to 'monospace'.");
actualFontFamily = "monospace";
}
}
const canvas = document.createElement('canvas');
canvas.width = C_WIDTH;
canvas.height = C_HEIGHT;
const ctx = canvas.getContext('2d');
// Overall Background
ctx.fillStyle = mainBackgroundColor;
ctx.fillRect(0, 0, C_WIDTH, C_HEIGHT);
// Content area (inside border)
const contentX = outerBorderWidth;
const contentY = outerBorderWidth;
const contentWidth = C_WIDTH - 2 * outerBorderWidth;
const contentHeight = C_HEIGHT - 2 * outerBorderWidth;
// Layout Zones
const titleAreaHeight = contentHeight * 0.18;
const footerAreaHeight = contentHeight * 0.15; // Increased slightly for better spacing
const imageAreaHeight = contentHeight - titleAreaHeight - footerAreaHeight;
const titleAreaY = contentY;
const imageAreaY = contentY + titleAreaHeight;
const footerAreaY = contentY + titleAreaHeight + imageAreaHeight;
// 1. Draw Main Image (in its zone)
ctx.imageSmoothingEnabled = false; // Crucial for retro pixel art style
const imgDestX = contentX;
const imgDestY = imageAreaY;
const imgDestWidth = contentWidth;
const imgDestHeight = imageAreaHeight;
const imgAspectRatio = originalImg.width / originalImg.height;
const destAspectRatio = imgDestWidth / imgDestHeight;
let sWidth = originalImg.width;
let sHeight = originalImg.height;
let sx = 0;
let sy = 0;
if (imgAspectRatio > destAspectRatio) { // Image is wider than destination (need to crop sides)
sWidth = originalImg.height * destAspectRatio;
sx = (originalImg.width - sWidth) / 2;
} else if (imgAspectRatio < destAspectRatio) { // Image is taller than destination (need to crop top/bottom)
sHeight = originalImg.width / destAspectRatio;
sy = (originalImg.height - sHeight) / 2;
}
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, imgDestX, imgDestY, imgDestWidth, imgDestHeight);
// 2. Draw Title
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const titleActualSize = Math.min(titleSize, titleAreaHeight * 0.6, contentWidth / (titleText.length * 0.6)); // Auto-adjust if text too long or area too small
ctx.font = `${titleActualSize}px "${actualFontFamily}"`;
const titleX = contentX + contentWidth / 2;
const titleY = titleAreaY + titleAreaHeight / 2;
if (titleOutlineWidth > 0 && titleOutlineColor) {
ctx.strokeStyle = titleOutlineColor;
ctx.lineWidth = titleOutlineWidth;
ctx.strokeText(titleText, titleX, titleY);
}
ctx.fillStyle = titleColor;
ctx.fillText(titleText, titleX, titleY);
// 3. Draw Footer Elements
const footerPadding = 10; // Padding inside the footer area for elements
// 3a. Rating Symbol (bottom-left of footer)
const ratingActualBoxSize = Math.min(ratingBoxSize, footerAreaHeight - 2 * footerPadding);
const ratingTextSize = ratingActualBoxSize * 0.5;
const ratingBoxX = contentX + footerPadding;
const ratingBoxY = footerAreaY + (footerAreaHeight - ratingActualBoxSize) / 2;
ctx.fillStyle = ratingBackgroundColor;
ctx.fillRect(ratingBoxX, ratingBoxY, ratingActualBoxSize, ratingActualBoxSize);
// Optional: border for rating box
ctx.strokeStyle = ratingSymbolColor; // Use symbol color for border for contrast with dark bg
ctx.lineWidth = Math.max(1, ratingActualBoxSize * 0.05);
ctx.strokeRect(ratingBoxX, ratingBoxY, ratingActualBoxSize, ratingActualBoxSize);
ctx.font = `${ratingTextSize}px "${actualFontFamily}"`;
ctx.fillStyle = ratingSymbolColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(ratingSymbol, ratingBoxX + ratingActualBoxSize / 2, ratingBoxY + ratingActualBoxSize / 2 + ratingTextSize*0.1); // Small optical adjustment for font
// 3b. Platform Text (bottom-right of footer)
const platformActualTextSize = Math.min(platformTextSize, footerAreaHeight * 0.4);
ctx.font = `${platformActualTextSize}px "${actualFontFamily}"`;
const platformTextMetrics = ctx.measureText(platformText);
const platformBoxWidth = platformTextMetrics.width + platformActualTextSize; // Padding around text
const platformBoxHeight = platformActualTextSize * 1.8;
const platformBoxMaxHeight = footerAreaHeight - 2 * footerPadding;
const platformFinalBoxHeight = Math.min(platformBoxHeight, platformBoxMaxHeight);
// If box height was scaled down, scale text size proportionally to fit.
const platformFinalTextSize = platformActualTextSize * (platformFinalBoxHeight / platformBoxHeight);
const platformBoxX = contentX + contentWidth - platformBoxWidth - footerPadding;
const platformBoxY = footerAreaY + (footerAreaHeight - platformFinalBoxHeight) / 2;
ctx.fillStyle = platformBackgroundColor;
ctx.fillRect(platformBoxX, platformBoxY, platformBoxWidth, platformFinalBoxHeight);
ctx.font = `${platformFinalTextSize}px "${actualFontFamily}"`;
ctx.fillStyle = platformTextColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(platformText, platformBoxX + platformBoxWidth / 2, platformBoxY + platformFinalBoxHeight / 2 + platformFinalTextSize*0.1);
// 3c. Tagline Text (centered in footer, above rating/platform if space, or between them)
// Position tagline in the vertical center of footer, between rating and platform boxes horizontally.
const taglineAvailableWidth = (platformBoxX - (ratingBoxX + ratingActualBoxSize)) - (2 * footerPadding) ;
const taglineActualSize = Math.min(taglineSize, footerAreaHeight * 0.3, taglineAvailableWidth / (taglineText.length * 0.6));
ctx.font = `${taglineActualSize}px "${actualFontFamily}"`;
ctx.fillStyle = taglineColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Center X between rating and platform box
const taglineX = (ratingBoxX + ratingActualBoxSize) + taglineAvailableWidth / 2 + footerPadding;
const taglineY = footerAreaY + footerAreaHeight / 2 + taglineActualSize*0.1; // Centered vertically in footer
if (taglineText.length > 0 && taglineActualSize > 5) { // Only draw if there's text and size is reasonable
ctx.fillText(taglineText, taglineX, taglineY);
}
// 4. Outer Border (drawn on top of everything else at edges)
if (outerBorderWidth > 0) {
ctx.strokeStyle = outerBorderColor;
ctx.lineWidth = outerBorderWidth;
ctx.strokeRect(outerBorderWidth / 2, outerBorderWidth / 2, C_WIDTH - outerBorderWidth, C_HEIGHT - outerBorderWidth);
}
// 5. Scanlines effect
if (enableScanlines > 0 && scanlineOpacity > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanlineOpacity})`;
for (let y = 0; y < C_HEIGHT; y += 3) { // 1px scanline every 3 pixels
ctx.fillRect(0, y, C_WIDTH, 1);
}
}
// 6. Noise effect
if (enableNoise > 0 && noiseOpacity > 0) {
const noiseCanvasTemp = document.createElement('canvas');
noiseCanvasTemp.width = C_WIDTH;
noiseCanvasTemp.height = C_HEIGHT;
const noiseCtx = noiseCanvasTemp.getContext('2d');
const imageData = noiseCtx.createImageData(C_WIDTH, C_HEIGHT);
const pixels = imageData.data;
for (let i = 0; i < pixels.length; i += 4) {
const val = Math.floor(Math.random() * 255);
pixels[i] = val; // R
pixels[i+1] = val; // G
pixels[i+2] = val; // B
pixels[i+3] = 255; // Alpha
}
noiseCtx.putImageData(imageData, 0, 0);
ctx.save();
ctx.globalAlpha = noiseOpacity;
// Consider composite operation 'overlay' or 'soft-light' for different noise feel
//ctx.globalCompositeOperation = 'overlay';
ctx.drawImage(noiseCanvasTemp, 0, 0);
ctx.restore();
}
return canvas;
}
Apply Changes