Please bookmark this page to avoid losing your image tool!

Image Recipe Reference Tool

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
async function processImage(
    originalImg,
    recipeTitle = "Classic Lemon Tart",
    description = "A delightful balance of sweet and tangy, this classic lemon tart features a buttery shortcrust pastry and a rich, zesty lemon curd filling. Perfect for any occasion.",
    ingredients = "For the pastry:\n1 1/4 cups all-purpose flour\n1/2 cup cold unsalted butter\n1/4 cup powdered sugar\n1 large egg yolk\n\nFor the filling:\n4 large eggs\n1 cup granulated sugar\n2/3 cup fresh lemon juice\n1/2 cup melted unsalted butter",
    instructions = "Make the pastry: Combine flour, butter, and sugar until crumbly. Mix in yolk and water. Chill for 30 mins.\nRoll out and fit into a 9-inch tart pan. Bake at 375°F (190°C) for 15-20 mins.\nMake the filling: Whisk eggs and sugar. Stir in lemon juice, zest, and melted butter until smooth.\nPour the filling into the crust. Bake for another 25-30 minutes until the center is set.\nCool completely before serving.",
    backgroundColor = "#FDFCF7",
    textColor = "#3D3D3D",
    accentColor = "#D4A269",
    fontFamily = "Lora"
) {

    /**
     * Dynamically loads a specified Google Font into the document.
     * @param {string} font The name of the font family (e.g., 'Lora', 'Roboto').
     * @param {string} weightsAndStyles The weights and styles to load (e.g., 'wght@400;700').
     */
    const loadFont = async (font, weightsAndStyles) => {
        const fontFace = font.replace(/ /g, '+');
        const url = `https://fonts.googleapis.com/css2?family=${fontFace}:${weightsAndStyles}&display=swap`;

        // Avoid adding the same stylesheet multiple times
        if (!document.querySelector(`link[href="${url}"]`)) {
            const link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = url;
            document.head.appendChild(link);
            await new Promise((resolve) => {
                link.onload = resolve;
                link.onerror = () => {
                    console.warn(`Could not load stylesheet for font: ${font}`);
                    resolve();
                };
            });
        }
        // Wait for the font to be ready for use
        try {
            await document.fonts.load(`12px "${font}"`);
            await document.fonts.load(`bold 12px "${font}"`);
        } catch (e) {
            console.warn(`Font face for "${font}" could not be loaded or rendered in time.`);
        }
    };

    /**
     * Wraps text to fit within a max width and draws it on the canvas.
     * @returns {number} The new Y-coordinate after drawing the text.
     */
    const drawWrappedText = (context, text, x, y, maxWidth, lineHeight) => {
        const paragraphs = text.split('\n');
        context.textBaseline = 'top';

        for (const paragraph of paragraphs) {
            if (paragraph.trim() === '') {
                y += lineHeight; // Respect empty lines
                continue;
            }
            const words = paragraph.split(' ');
            let currentLine = words[0] || '';

            for (let i = 1; i < words.length; i++) {
                const word = words[i];
                const testLine = currentLine + ' ' + word;
                if (context.measureText(testLine).width > maxWidth) {
                    context.fillText(currentLine, x, y);
                    y += lineHeight;
                    currentLine = word;
                } else {
                    currentLine = testLine;
                }
            }
            context.fillText(currentLine, x, y);
            y += lineHeight;
        }
        return y;
    };

    // --- 1. SETUP & FONT LOADING ---
    await loadFont(fontFamily, 'wght@400;700');

    const canvasWidth = 800;
    const padding = 50;
    const contentWidth = canvasWidth - 2 * padding;
    let y = padding;

    // Use a temporary canvas with a large height to draw on.
    // This simplifies layouting, as we don't need to pre-calculate the final height.
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = canvasWidth;
    tempCanvas.height = 5000; // A sufficiently large height
    const ctx = tempCanvas.getContext('2d');
    
    // --- 2. DRAWING ON TEMP CANVAS ---

    // Background
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);

    // Image
    const imageHeight = (originalImg.height / originalImg.width) * contentWidth;
    ctx.drawImage(originalImg, padding, y, contentWidth, imageHeight);
    y += imageHeight + 40;

    // Title
    ctx.font = `700 40px "${fontFamily}", serif`;
    ctx.fillStyle = textColor;
    y = drawWrappedText(ctx, recipeTitle, padding, y, contentWidth, 50);
    y += 10;

    // Description
    ctx.font = `400 17px "${fontFamily}", serif`;
    ctx.fillStyle = textColor;
    y = drawWrappedText(ctx, description, padding, y, contentWidth, 25);
    y += 40;
    
    const headerFont = `700 24px "${fontFamily}", serif`;
    const bodyFont = `400 16px "${fontFamily}", serif`;
    const bodyLineHeight = 24;
    const listIndent = 25;
    const listContentWidth = contentWidth - listIndent;

    // Ingredients
    ctx.font = headerFont;
    ctx.fillStyle = accentColor;
    y = drawWrappedText(ctx, "Ingredients", padding, y, contentWidth, 30);
    y += 15;
    ctx.font = bodyFont;
    ctx.fillStyle = textColor;
    const ingredientsList = ingredients.split('\n').filter(line => line.trim() !== '');
    ingredientsList.forEach(item => {
        ctx.fillStyle = accentColor;
        ctx.fillText("•", padding, y); // Bullet point
        ctx.fillStyle = textColor;
        y = drawWrappedText(ctx, item, padding + listIndent, y, listContentWidth, bodyLineHeight);
    });
    y += 40;

    // Instructions
    ctx.font = headerFont;
    ctx.fillStyle = accentColor;
    y = drawWrappedText(ctx, "Instructions", padding, y, contentWidth, 30);
    y += 15;
    ctx.font = bodyFont;
    ctx.fillStyle = textColor;
    const instructionsList = instructions.split('\n').filter(line => line.trim() !== '');
    instructionsList.forEach((item, index) => {
        ctx.font = `700 16px "${fontFamily}", serif`;
        ctx.fillStyle = accentColor;
        ctx.fillText(`${index + 1}.`, padding, y); // Step number
        ctx.font = `400 16px "${fontFamily}", serif`;
        ctx.fillStyle = textColor;
        y = drawWrappedText(ctx, item, padding + listIndent, y, listContentWidth, bodyLineHeight);
        y += 8; // Extra spacing between steps
    });

    // --- 3. FINAL CANVAS CREATION ---
    const finalHeight = y + padding - 8; // Adjust for final padding
    const finalCanvas = document.createElement('canvas');
    finalCanvas.width = canvasWidth;
    finalCanvas.height = finalHeight;
    const finalCtx = finalCanvas.getContext('2d');

    // Copy the drawn content from the temp canvas to the correctly-sized final canvas
    finalCtx.drawImage(tempCanvas, 0, 0, canvasWidth, finalHeight, 0, 0, canvasWidth, finalHeight);

    return finalCanvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Recipe Reference Tool allows users to create visually appealing recipe cards by overlaying recipe information onto images. It can be particularly useful for food bloggers, chefs, or anyone who wants to share their culinary creations in a professional format. Users can upload an image, input a recipe title, description, ingredients, and instructions, and customize the design with chosen colors and fonts. This tool is perfect for creating engaging social media posts, printable recipe cards, or digital cookbooks.

Leave a Reply

Your email address will not be published. Required fields are marked *