You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, recipeTitle = "Delicious Recipe", ingredients = "1 cup Love\n2 tbsp Patience\n1 pinch of Genius\nA dash of Creativity", instructions = "1. Combine all ingredients in a bowl.\n2. Mix with passion and care.\n3. Bake in the oven of your heart.\n4. Share with friends and family and enjoy the delicious results!", servings = "4-6 people", prepTime = "30 minutes") {
// Helper function to load Google Fonts once
const loadFonts = async () => {
const fontId = 'google-fonts-image-recipe-card';
// Check if fonts are already loaded or the style tag exists
if (window.recipeCardFontsLoaded || document.getElementById(fontId)) {
window.recipeCardFontsLoaded = true;
return;
}
const link = document.createElement('link');
link.id = fontId;
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,700&family=Montserrat:wght@400;600&display=swap';
document.head.appendChild(link);
try {
await document.fonts.load('700 12px Lora');
await document.fonts.load('400 12px Montserrat');
window.recipeCardFontsLoaded = true;
} catch (e) {
console.error("Font loading failed:", e);
// Continue anyway, browser might use fallback fonts
}
};
await loadFonts();
// Setup a temporary canvas with a large height to draw on
const tempCanvas = document.createElement('canvas');
const ctx = tempCanvas.getContext('2d');
// Define layout constants for our recipe card design
const cardWidth = 800;
const padding = 50;
const contentWidth = cardWidth - 2 * padding;
const titleFont = '700 48px Lora';
const headingFont = '600 24px Montserrat';
const bodyFont = '400 20px Montserrat';
const detailsFont = '400 18px Montserrat';
const primaryColor = '#2d2d2d';
const secondaryColor = '#555555';
const backgroundColor = '#FDFBF5';
const bodyLineHeight = 32;
const titleLineHeight = 60;
const sectionSpacing = 40;
const itemSpacing = 20;
tempCanvas.width = cardWidth;
tempCanvas.height = 8000; // Large enough for any reasonable recipe
// Helper to wrap and draw text, returns the Y position after drawing
const wrapAndDrawText = (text, x, y, maxWidth, font, color, lineHeight) => {
ctx.font = font;
ctx.fillStyle = color;
ctx.textBaseline = 'top';
const lines = text.split('\n');
let currentY = y;
for (const line of lines) {
const words = line.trim().split(' ');
let currentLine = '';
for (let i = 0; i < words.length; i++) {
const testLine = currentLine + words[i] + ' ';
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && i > 0) {
ctx.fillText(currentLine.trim(), x, currentY);
currentLine = words[i] + ' ';
currentY += lineHeight;
} else {
currentLine = testLine;
}
}
if (currentLine.trim() !== '') {
ctx.fillText(currentLine.trim(), x, currentY);
currentY += lineHeight;
}
}
return currentY;
};
let currentY = 0;
// 1. Draw Background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
// 2. Draw Image at the top
const imgHeight = (originalImg.height / originalImg.width) * cardWidth;
ctx.drawImage(originalImg, 0, 0, cardWidth, imgHeight);
currentY = imgHeight;
const contentX = padding;
// 3. Draw Recipe Title
currentY += sectionSpacing;
currentY = wrapAndDrawText(recipeTitle, contentX, currentY, contentWidth, titleFont, primaryColor, titleLineHeight);
// 4. Draw Details (Servings & Prep Time)
currentY += itemSpacing;
ctx.font = detailsFont;
ctx.fillStyle = secondaryColor;
ctx.textBaseline = 'top';
const detailsText = `Servings: ${servings} | Prep Time: ${prepTime}`;
ctx.fillText(detailsText, contentX, currentY);
currentY += 25; // Approximate height for the details line
// 5. Draw a decorative separator line
currentY += itemSpacing;
ctx.beginPath();
ctx.moveTo(contentX, currentY);
ctx.lineTo(contentX + contentWidth, currentY);
ctx.strokeStyle = '#D8C8B8';
ctx.lineWidth = 1;
ctx.stroke();
currentY += sectionSpacing;
// 6. Draw Ingredients section
ctx.font = headingFont;
ctx.fillStyle = primaryColor;
ctx.textBaseline = 'top';
ctx.fillText("Ingredients", contentX, currentY);
currentY += 35; // Space after heading
currentY = wrapAndDrawText(ingredients, contentX, currentY, contentWidth, bodyFont, primaryColor, bodyLineHeight);
currentY += sectionSpacing;
// 7. Draw Instructions section
ctx.font = headingFont;
ctx.fillStyle = primaryColor;
ctx.textBaseline = 'top';
ctx.fillText("Instructions", contentX, currentY);
currentY += 35; // Space after heading
currentY = wrapAndDrawText(instructions, contentX, currentY, contentWidth, bodyFont, primaryColor, bodyLineHeight);
// 8. Create the final, correctly sized canvas
const finalHeight = currentY + padding;
const finalCanvas = document.createElement('canvas');
finalCanvas.width = cardWidth;
finalCanvas.height = finalHeight;
const finalCtx = finalCanvas.getContext('2d');
// 9. Copy the rendered content from the temporary canvas to the final canvas
finalCtx.drawImage(tempCanvas, 0, 0, cardWidth, finalHeight, 0, 0, cardWidth, finalHeight);
return finalCanvas;
}
Apply Changes