You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
title = "THE CASE OF THE CODER",
author = "A. N. WRITER",
tagline1 = "He wrote the code that could crash the world...",
tagline2 = "...now he had to debug his own life!",
price = "35¢",
publisherPrefix = "A GALAXY NOVEL",
publisherSuffix = "EXCITING NEW MYSTERY!",
titleColor = "#FFDD00", // Bright Yellow
titleOutlineColor = "#D90429", // Deep Red
titleOutlineThickness = 4,
authorColor = "#FFFFFF", // White
authorOutlineColor = "#000000", // Black outline for author
authorOutlineThickness = 3,
taglineColor = "#FFFFFF", // White
taglineOutlineColor = "#000000", // Black
taglineOutlineThickness = 2,
priceBgColor = "#D90429", // Deep Red for price box
priceTextColor = "#FFFFFF", // White for price text
publisherPrefixColor = "#FFFFFF",
publisherPrefixOutlineColor = "#000000",
publisherPrefixOutlineThickness = 1,
publisherSuffixColor = "#FFFFFF",
publisherSuffixOutlineColor = "#000000",
publisherSuffixOutlineThickness = 1,
imageDesaturation = 0.3, // 0 (no change) to 1 (full desaturation to grayscale)
vintageOverlayColor = "rgba(100, 50, 0, 0.15)" // Brownish overlay for aging
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const W = 600;
const H = 900;
canvas.width = W;
canvas.height = H;
const P = 20; // General padding
// Helper function to load fonts
async function loadFont(fontFamily, fontUrl) {
if (Array.from(document.fonts).some(f => f.family === fontFamily && f.status === 'loaded')) {
return;
}
if (!loadFont.promises) {
loadFont.promises = {};
}
if (loadFont.promises[fontFamily]) {
return loadFont.promises[fontFamily];
}
const fontFace = new FontFace(fontFamily, `url(${fontUrl})`);
try {
const promise = fontFace.load().then(loadedFace => {
document.fonts.add(loadedFace);
}).catch(e => {
console.error(`Font loading failed for ${fontFamily}:`, e);
// Browser will use fallback fonts specified in ctx.font
});
loadFont.promises[fontFamily] = promise;
return promise;
} catch (e) {
console.error(`Error creating FontFace for ${fontFamily}:`, e);
return Promise.reject(e);
}
}
// Helper function to draw text with a "fake" outline
function drawTextWithFakeOutline(ctx, text, x, y, textColor, outlineColor, outlineThickness) {
if (outlineThickness > 0) {
ctx.fillStyle = outlineColor;
const off = outlineThickness;
ctx.fillText(text, x - off, y - off);
ctx.fillText(text, x, y - off);
ctx.fillText(text, x + off, y - off);
ctx.fillText(text, x - off, y);
ctx.fillText(text, x + off, y);
ctx.fillText(text, x - off, y + off);
ctx.fillText(text, x, y + off);
ctx.fillText(text, x + off, y + off);
}
ctx.fillStyle = textColor;
ctx.fillText(text, x, y);
}
// Load necessary fonts
await Promise.all([
loadFont('Bangers', 'https://fonts.gstatic.com/s/bangers/v20/FeVQS0BTqb0h60ACL5la-A.woff2'),
loadFont('Anton', 'https://fonts.gstatic.com/s/anton/v25/1Ptgg87LROyAm0KujaR2BQ.woff2')
]);
// 1. Draw Background Image + Effects
ctx.save(); // Save context state before applying filters
if (imageDesaturation > 0) {
// Apply desaturation: if 1, 100% grayscale & 0% saturate. If 0, 0% grayscale & 100% saturate.
ctx.filter = `grayscale(${Math.min(1, imageDesaturation) * 100}%) saturate(${(1 - Math.min(1, imageDesaturation)) * 100}%)`;
}
// Draw image to cover canvas (centered crop)
if (originalImg && originalImg.width > 0 && originalImg.height > 0) {
const imgAspect = originalImg.width / originalImg.height;
const canvasAspect = W / H;
let sx = 0, sy = 0, sWidth = originalImg.width, sHeight = originalImg.height;
if (imgAspect > canvasAspect) { // Image is wider than canvas
sWidth = originalImg.height * canvasAspect;
sx = (originalImg.width - sWidth) / 2;
} else if (imgAspect < canvasAspect) { // Image is taller than canvas
sHeight = originalImg.width / canvasAspect;
sy = (originalImg.height - sHeight) / 2;
}
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, W, H);
} else { // Fallback if image is not loaded or invalid
ctx.fillStyle = '#555555'; // Dark gray fallback
ctx.fillRect(0,0,W,H);
ctx.fillStyle = '#FFFFFF';
ctx.textAlign = 'center';
ctx.font = "20px Arial";
ctx.fillText("Image Error", W/2, H/2);
}
ctx.restore(); // Restore context filtering
// Apply vintage overlay color
if (vintageOverlayColor && vintageOverlayColor !== "none") {
ctx.fillStyle = vintageOverlayColor;
ctx.fillRect(0, 0, W, H);
}
// Set common text properties
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 2. Author Name (Top)
const authorY = H * 0.15;
ctx.font = `bold 70px 'Anton', Impact, sans-serif`;
drawTextWithFakeOutline(ctx, author.toUpperCase(), W / 2, authorY, authorColor, authorOutlineColor, authorOutlineThickness);
// 3. Publisher Prefix (Above Title)
const publisherPrefixY = H * 0.27; // Adjusted for better spacing
ctx.font = `bold 22px 'Arial Narrow', 'Helvetica Neue Condensed', sans-serif`;
drawTextWithFakeOutline(ctx, publisherPrefix.toUpperCase(), W / 2, publisherPrefixY, publisherPrefixColor, publisherPrefixOutlineColor, publisherPrefixOutlineThickness);
// 4. Main Title (Middle-Upper)
const titleY = H * 0.38; // Adjusted for better spacing
ctx.font = `120px 'Bangers', 'Arial Black', Impact, fantasy`;
drawTextWithFakeOutline(ctx, title.toUpperCase(), W / 2, titleY, titleColor, titleOutlineColor, titleOutlineThickness);
// 5. Price Block (Top-Right)
const priceBoxWidth = 90;
const priceBoxHeight = 60;
const priceBoxX = W - priceBoxWidth - P;
const priceBoxY = P;
ctx.fillStyle = priceBgColor;
ctx.fillRect(priceBoxX, priceBoxY, priceBoxWidth, priceBoxHeight);
ctx.font = `bold 28px Arial, sans-serif`;
ctx.fillStyle = priceTextColor;
ctx.fillText(price, priceBoxX + priceBoxWidth / 2, priceBoxY + priceBoxHeight / 2);
// 6. Taglines (Bottom area)
ctx.font = `italic bold 22px Georgia, serif`;
const tagline1Y = H * 0.82;
drawTextWithFakeOutline(ctx, tagline1.toUpperCase(), W / 2, tagline1Y, taglineColor, taglineOutlineColor, taglineOutlineThickness);
const tagline2Y = H * 0.87;
if (tagline2) {
drawTextWithFakeOutline(ctx, tagline2.toUpperCase(), W / 2, tagline2Y, taglineColor, taglineOutlineColor, taglineOutlineThickness);
}
// 7. Publisher Suffix (Bottom center)
ctx.font = `bold 16px Arial, sans-serif`;
ctx.textBaseline = 'bottom'; // Align to bottom for precise placement from canvas edge
const publisherSuffixY = H - P + 5; // Place it P from bottom, adjust for baseline
drawTextWithFakeOutline(ctx, publisherSuffix.toUpperCase(), W / 2, publisherSuffixY, publisherSuffixColor, publisherSuffixOutlineColor, publisherSuffixOutlineThickness);
return canvas;
}
Apply Changes