You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, rightImgSrc = '', overlayText = 'Jollof rice vs. Mac and cheese: Which one’s the real MVP?', fontFamily = 'Bangers', fontSize = 80, textColor = '#FFFFFF', strokeColor = '#000000', strokeWidth = 6) {
/**
* Dynamically loads a font from Google Fonts.
* @param {string} family - The font family name.
* @param {string} url - The URL to the font file (e.g., woff2).
*/
const loadFont = async (family, url) => {
const font = new FontFace(family, `url(${url})`);
try {
await font.load();
document.fonts.add(font);
} catch (e) {
console.error(`Font loading failed: ${family}`, e);
}
};
/**
* Loads an image from a source (like a data URL).
* @param {string} src - The image source.
* @returns {Promise<Image|null>} A promise that resolves with the loaded Image object or null.
*/
const loadImage = (src) => {
if (!src) {
return Promise.resolve(null);
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (err) => {
console.error("Failed to load right image.", err);
resolve(null); // Resolve with null on error to not break the flow
};
img.src = src;
});
};
/**
* Wraps text to fit within a maximum width.
* @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
* @param {string} text - The text to wrap.
* @param {number} maxWidth - The maximum width for a line of text.
* @returns {string[]} An array of text lines.
*/
const wrapText = (ctx, text, maxWidth) => {
const words = text.split(' ');
const lines = [];
let currentLine = words[0] || '';
for (let i = 1; i < words.length; i++) {
const word = words[i];
const testLine = currentLine + ' ' + word;
if (ctx.measureText(testLine).width < maxWidth) {
currentLine = testLine;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
return lines;
};
/**
* Draws a simple pepper doodle.
*/
const drawPepperDoodle = (ctx, x, y) => {
ctx.save();
ctx.fillStyle = '#2E8B57'; // SeaGreen for stem
ctx.beginPath();
ctx.rect(x + 18, y, 5, 10);
ctx.fill();
ctx.fillStyle = '#DC143C'; // Crimson for body
ctx.beginPath();
ctx.moveTo(x, y + 10);
ctx.bezierCurveTo(x - 5, y + 40, x + 45, y + 40, x + 40, y + 10);
ctx.bezierCurveTo(x + 40, y + 25, x, y + 25, x, y + 10);
ctx.fill();
ctx.restore();
};
/**
* Draws a simple cheese doodle.
*/
const drawCheeseDoodle = (ctx, x, y) => {
ctx.save();
ctx.fillStyle = '#FFD700'; // Gold for cheese
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + 40, y + 30);
ctx.lineTo(x, y + 30);
ctx.closePath();
ctx.fill();
// Holes
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'black'; // Color doesn't matter for destination-out
ctx.beginPath();
ctx.arc(x + 10, y + 22, 4, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(x + 25, y + 25, 3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
// --- Main execution ---
// 1. Load external resources (font and right image) in parallel
await Promise.all([
loadFont(fontFamily, 'https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5k.woff2'),
loadImage(rightImgSrc)
]).then(([_, loadedRightImg]) => {
// This 'then' block is just to name the loaded image result.
// The rest of the function will execute after this promise resolves.
// 2. Setup Canvas
const canvas = document.createElement('canvas');
const leftWidth = originalImg.naturalWidth;
const leftHeight = originalImg.naturalHeight;
const canvasWidth = leftWidth * 2;
canvas.width = canvasWidth;
canvas.height = leftHeight;
const ctx = canvas.getContext('2d');
// 3. Draw images
ctx.drawImage(originalImg, 0, 0, leftWidth, leftHeight);
if (loadedRightImg) {
// Draw the right image, scaled to match the left image's dimensions
ctx.drawImage(loadedRightImg, leftWidth, 0, leftWidth, leftHeight);
} else {
// Draw a placeholder if the right image is missing
ctx.fillStyle = '#222';
ctx.fillRect(leftWidth, 0, leftWidth, leftHeight);
ctx.fillStyle = '#fff';
ctx.font = '24px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Right Image Here', leftWidth + leftWidth / 2, leftHeight / 2);
}
// 4. Draw faint doodles
ctx.save();
ctx.globalAlpha = 0.25;
for (let i = 0; i < 8; i++) {
const x = Math.random() * (leftWidth - 60) + 10;
const y = Math.random() * (leftHeight - 50) + 10;
drawPepperDoodle(ctx, x, y);
const x2 = Math.random() * (leftWidth - 60) + leftWidth + 10;
const y2 = Math.random() * (leftHeight - 50) + 10;
drawCheeseDoodle(ctx, x2, y2);
}
ctx.restore();
// 5. Draw text overlay
ctx.font = `${fontSize}px "${fontFamily}", comic sans ms, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const lines = wrapText(ctx, overlayText, canvasWidth * 0.9);
const lineHeight = fontSize * 1.1;
const totalTextHeight = (lines.length - 1) * lineHeight;
const startY = (canvas.height / 2) - (totalTextHeight / 2);
lines.forEach((line, index) => {
const y = startY + (index * lineHeight);
ctx.strokeStyle = strokeColor;
ctx.lineWidth = strokeWidth;
ctx.strokeText(line, canvas.width / 2, y);
ctx.fillStyle = textColor;
ctx.fillText(line, canvas.width / 2, y);
});
// This is inside the .then, so we have to handle the return value carefully.
// It's better to just use the variable from the outer scope.
});
// `processImage` is async, so we need to create the canvas outside the .then
// for it to be returned. Let's refactor slightly.
const [_, loadedRightImg] = await Promise.all([
loadFont(fontFamily, 'https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5k.woff2'),
loadImage(rightImgSrc)
]);
const canvas = document.createElement('canvas');
const leftWidth = originalImg.naturalWidth;
const leftHeight = originalImg.naturalHeight;
const canvasWidth = leftWidth * 2;
canvas.width = canvasWidth;
canvas.height = leftHeight;
const ctx = canvas.getContext('2d');
// Draw images
ctx.drawImage(originalImg, 0, 0, leftWidth, leftHeight);
if (loadedRightImg) {
ctx.drawImage(loadedRightImg, leftWidth, 0, leftWidth, leftHeight);
} else {
ctx.fillStyle = '#222';
ctx.fillRect(leftWidth, 0, leftWidth, leftHeight);
ctx.fillStyle = '#fff';
ctx.font = '30px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Right Image Here', leftWidth + leftWidth / 2, leftHeight / 2);
}
// Draw doodles
ctx.save();
ctx.globalAlpha = 0.25;
for (let i = 0; i < 8; i++) {
const x = Math.random() * (leftWidth - 60) + 10;
const y = Math.random() * (leftHeight - 50) + 10;
drawPepperDoodle(ctx, x, y);
const x2 = Math.random() * (leftWidth - 60) + leftWidth + 10;
const y2 = Math.random() * (leftHeight - 50) + 10;
drawCheeseDoodle(ctx, x2, y2);
}
ctx.restore();
// Draw text
ctx.font = `${fontSize}px "${fontFamily}", comic sans ms, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const lines = wrapText(ctx, overlayText, canvasWidth * 0.9);
const lineHeight = fontSize * 1.1;
const totalTextHeight = (lines.length - 1) * lineHeight;
const startY = (canvas.height / 2) - (totalTextHeight / 2);
lines.forEach((line, index) => {
const y = startY + (index * lineHeight);
ctx.strokeStyle = strokeColor;
ctx.lineWidth = strokeWidth;
ctx.strokeText(line, canvas.width / 2, y);
ctx.fillStyle = textColor;
ctx.fillText(line, canvas.width / 2, y);
});
return canvas;
}
Apply Changes