You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
title = "The Japanese\nCarry Trade",
content = "Investors borrow Yen at near 0% interest to buy higher-yielding assets globally. When the Yen strengthens abruptly, this massive leveraged trade violently unwinds across global markets.",
slideIndex = 1,
totalSlides = 5,
accentColor = "#FF3344",
brandHandle = "@macro.finance"
) {
// 1. Setup Canvas (Instagram Carousel Portrait Size)
const width = 1080;
const height = 1350;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 2. Load Google Fonts
const fontFamily = 'Space Grotesk';
if (!document.getElementById('carousel-font')) {
const link = document.createElement('link');
link.id = 'carousel-font';
link.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(' ', '+')}:wght@400;700;800&display=swap`;
link.rel = 'stylesheet';
document.head.appendChild(link);
}
try {
await Promise.race([
document.fonts.load(`800 100px "${fontFamily}"`),
document.fonts.load(`400 48px "${fontFamily}"`),
new Promise(resolve => setTimeout(resolve, 2000)) // 2s timeout fallback
]);
} catch (e) {
console.warn("Font loading error/timeout. Falling back to sans-serif.");
}
const fontBase = `"${fontFamily}", sans-serif`;
// 3. Draw Deep Background (Dark Modern Finance aesthetic)
ctx.fillStyle = '#090A0F';
ctx.fillRect(0, 0, width, height);
// 4. Draw Background Image and Blend
if (originalImg && originalImg.width > 0) {
ctx.globalAlpha = 0.15; // Low opacity for darkness
ctx.globalCompositeOperation = 'luminosity'; // Grayscale-like atmospheric blend
const scale = Math.max(width / originalImg.width, height / originalImg.height);
const imgW = originalImg.width * scale;
const imgH = originalImg.height * scale;
const x = (width - imgW) / 2;
const y = (height - imgH) / 2;
ctx.drawImage(originalImg, x, y, imgW, imgH);
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
}
// 5. Draw Decorative Elements (Trading/Japanese Motif)
// Abstract Candle Stick Motif
ctx.globalAlpha = 0.04;
ctx.fillStyle = '#FFFFFF';
for(let i=0; i<18; i++) {
let cx = 50 + i * 60;
let cY = 800 + Math.sin(i) * 150 - 50;
let cHeight = 80 + Math.cos(i*2) * 100;
// Wick
ctx.fillRect(cx + 8, cY - 30, 4, cHeight + 60);
// Body (Red or Green concept, but white for wireframe look)
ctx.fillRect(cx, cY, 20, cHeight);
}
ctx.globalAlpha = 1.0;
// Glowing Accent Circle (Subtle Japanese Sun)
ctx.globalAlpha = 0.1;
let gradient = ctx.createRadialGradient(width - 100, 250, 0, width - 100, 250, 400);
gradient.addColorStop(0, accentColor);
gradient.addColorStop(1, 'transparent');
ctx.fillStyle = gradient;
ctx.fillRect(width - 500, 0, 500, 650);
ctx.globalAlpha = 1.0;
// Giant faint Yen symbol
ctx.fillStyle = 'rgba(255, 255, 255, 0.02)';
ctx.font = `800 800px ${fontBase}`;
ctx.fillText("¥", width - 600, height - 100);
// Minimalist finance Grid
ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
ctx.lineWidth = 1;
for(let i = 0; i <= width; i += 120) {
ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, height); ctx.stroke();
}
for(let i = 0; i <= height; i += 120) {
ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(width, i); ctx.stroke();
}
// 6. Draw UI Elements (Carousel Navigator)
// Swipe indicator text
ctx.fillStyle = accentColor;
ctx.font = `800 24px ${fontBase}`;
ctx.letterSpacing = "4px"; // Standard canvas API support varies, manual spacing simulation if needed, but modern browsers support
if (ctx.letterSpacing !== undefined) ctx.letterSpacing = "4px";
const navText = `SLIDE ${String(slideIndex).padStart(2, '0')}`;
ctx.fillText(navText, 100, 160);
if (ctx.letterSpacing !== undefined) ctx.letterSpacing = "0px";
// Progress bar components
const barWidth = 880;
const barY = 190;
ctx.fillStyle = 'rgba(255, 255, 255, 0.15)';
ctx.fillRect(100, barY, barWidth, 4);
ctx.fillStyle = accentColor;
const progressWidth = barWidth * (slideIndex / totalSlides);
ctx.fillRect(100, barY, progressWidth, 4);
// Total slides dots or markers
ctx.fillStyle = '#090A0F';
for(let i = 1; i < totalSlides; i++) {
let markerX = 100 + (barWidth * (i / totalSlides));
ctx.fillRect(markerX - 2, barY, 4, 4);
}
// 7. Typography (Text Layout Helper)
function wrapText(context, text, x, y, maxWidth, lineHeight, isTitle = false) {
let paragraphs = text.split('\n');
let currentY = y;
for (let p = 0; p < paragraphs.length; p++) {
let words = paragraphs[p].trim().split(' ');
let line = '';
for (let n = 0; n < words.length; n++) {
let testLine = line + words[n] + ' ';
let metrics = context.measureText(testLine);
let testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
context.fillText(line, x, currentY);
line = words[n] + ' ';
currentY += lineHeight;
} else {
line = testLine;
}
}
context.fillText(line, x, currentY);
currentY += lineHeight;
}
return currentY;
}
// Draw Title
ctx.fillStyle = '#FFFFFF';
ctx.font = `800 90px ${fontBase}`;
let titleEndY = wrapText(ctx, title, 100, 360, 880, 105, true);
// Decorative Separator Line
ctx.fillStyle = accentColor;
ctx.fillRect(100, titleEndY + 20, 150, 8);
// Draw Content Body
ctx.fillStyle = '#B0B5C0'; // Modern cool-gray
ctx.font = `400 44px ${fontBase}`;
wrapText(ctx, content, 100, titleEndY + 120, 880, 68);
// 8. Footer (Brand Handle and Save Icon)
const footerY = height - 100;
// Handle
ctx.fillStyle = '#FFFFFF';
ctx.font = `700 28px ${fontBase}`;
ctx.fillText(String(brandHandle), 100, footerY);
// "Swipe" or "Save" prompt depending on slide index
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
const ctaText = slideIndex === totalSlides ? "SAVE POST" : "SWIPE";
ctx.font = `700 24px ${fontBase}`;
if (ctx.letterSpacing !== undefined) ctx.letterSpacing = "2px";
ctx.fillText(ctaText, width - 260, footerY);
if (ctx.letterSpacing !== undefined) ctx.letterSpacing = "0px";
// Draw Save Ribbon/Icon
ctx.beginPath();
const iconX = width - 110;
const iconY = footerY - 22;
ctx.moveTo(iconX, iconY);
ctx.lineTo(iconX + 24, iconY);
ctx.lineTo(iconX + 24, iconY + 32);
ctx.lineTo(iconX + 12, iconY + 22);
ctx.lineTo(iconX, iconY + 32);
ctx.closePath();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 2.5;
ctx.lineJoin = 'round';
ctx.stroke();
return canvas;
}
Apply Changes