Please bookmark this page to avoid losing your image tool!

Image Recipe And Ingredients Guide

(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 = "Delicious Dish Recipe",
    ingredientsText = "• 200g All-purpose flour\n• 100g Sugar\n• 2 Large eggs\n• 100ml Milk\n• 1 tsp Vanilla extract",
    instructionsText = "1. Preheat oven to 180°C (350°F).\n2. In a large bowl, mix together the flour and sugar.\n3. In another bowl, whisk the eggs, then add the milk and vanilla extract.\n4. Pour the wet ingredients into the dry ingredients and mix until just combined.\n5. Pour the batter into a greased baking pan and bake for 25-30 minutes.",
    fontFamily = "Lora",
    textColor = "#333333",
    backgroundColor = "#f7f5f2",
    baseFontSize = 16
) {
    const titleFontSize = baseFontSize * 2.2;
    const headingFontSize = baseFontSize * 1.4;
    const bodyFontSize = baseFontSize;

    /**
     * Dynamically loads a font from Google Fonts and waits for it to be available.
     * @param {string} fontFamily - The name of the font family.
     * @returns {Promise<void>} - A promise that resolves when the font is loaded.
     */
    const loadAndInjectFont = async (fontFamily) => {
        const sanitizedFontFamily = fontFamily.replace(/ /g, '+');
        const fontUrl = `https://fonts.googleapis.com/css2?family=${sanitizedFontFamily}:wght@400;700&display=swap`;
        const fontId = `google-font-${sanitizedFontFamily}`;

        if (document.getElementById(fontId)) {
            try {
                await document.fonts.load(`1em "${fontFamily}"`);
                return;
            } catch (e) {
                console.error(`Font "${fontFamily}" was requested but failed to load.`, e);
            }
        }

        return new Promise((resolve, reject) => {
            const link = document.createElement('link');
            link.id = fontId;
            link.rel = 'stylesheet';
            link.href = fontUrl;
            link.onload = () => {
                document.fonts.load(`1em "${fontFamily}"`)
                    .then(() => resolve())
                    .catch(e => {
                         console.error(`Failed to load font face for ${fontFamily}`, e);
                         // Resolve anyway so the app doesn't break, will use fallback font
                         resolve();
                    });
            };
            link.onerror = (e) => {
                console.error(`Failed to load stylesheet for font: ${fontFamily}`, e);
                // Resolve anyway so the app doesn't break, will use fallback font
                resolve();
            }
            document.head.appendChild(link);
        });
    };

    /**
     * Measures the required height for a given text block with line wrapping.
     * @param {CanvasRenderingContext2D} context - The canvas rendering context.
     * @param {string} text - The text content to measure.
     * @param {number} maxWidth - The maximum width for a line.
     * @param {number} lineHeight - The height of each line.
     * @returns {number} - The total calculated height.
     */
    const measureWrappedTextHeight = (context, text, maxWidth, lineHeight) => {
        if (!text || text.trim() === '') return 0;
        let totalHeight = 0;
        const paragraphs = text.split('\n');

        paragraphs.forEach(paragraph => {
            if (paragraph.trim() === '') {
                totalHeight += lineHeight * 0.5; // Smaller gap for empty lines
                return;
            }
            let currentLine = '';
            const words = paragraph.split(' ');
            for (let i = 0; i < words.length; i++) {
                const testLine = currentLine + words[i] + ' ';
                if (context.measureText(testLine).width > maxWidth && i > 0) {
                    totalHeight += lineHeight;
                    currentLine = words[i] + ' ';
                } else {
                    currentLine = testLine;
                }
            }
            totalHeight += lineHeight; // Add height for the last line of the paragraph
        });
        return totalHeight;
    };

    /**
     * Draws wrapped text onto the canvas.
     * @param {CanvasRenderingContext2D} context - The canvas rendering context.
     * @param {string} text - The text content to draw.
     * @param {number} x - The starting X coordinate.
     * @param {number} y - The starting Y coordinate.
     * @param {number} maxWidth - The maximum width for a line.
     * @param {number} lineHeight - The height of each line.
     * @returns {number} The new Y position after drawing the text.
     */
    const drawWrappedText = (context, text, x, y, maxWidth, lineHeight) => {
        if (!text || text.trim() === '') return y;
        let currentY = y;
        const paragraphs = text.split('\n');

        paragraphs.forEach(paragraph => {
            if (paragraph.trim() === '') {
                currentY += lineHeight * 0.5;
                return;
            }
            let currentLine = '';
            const words = paragraph.split(' ');
            for (let i = 0; i < words.length; i++) {
                const testLine = currentLine + words[i] + ' ';
                if (context.measureText(testLine).width > maxWidth && i > 0) {
                    context.fillText(currentLine.trim(), x, currentY);
                    currentY += lineHeight;
                    currentLine = words[i] + ' ';
                } else {
                    currentLine = testLine;
                }
            }
            context.fillText(currentLine.trim(), x, currentY);
            currentY += lineHeight;
        });
        return currentY;
    };

    // --- Main Function Logic ---

    await loadAndInjectFont(fontFamily);

    const IMAGE_PANEL_WIDTH = 800;
    const TEXT_PANEL_WIDTH = 500;
    const PADDING = 40;
    const GAP = 25;

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const imageAspectRatio = originalImg.width / originalImg.height;
    const scaledImageWidth = IMAGE_PANEL_WIDTH;
    const scaledImageHeight = scaledImageWidth / imageAspectRatio;

    const textContentWidth = TEXT_PANEL_WIDTH - 2 * PADDING;
    let requiredTextHeight = PADDING;

    // Measure Title
    ctx.font = `700 ${titleFontSize}px "${fontFamily}", serif`;
    requiredTextHeight += measureWrappedTextHeight(ctx, recipeTitle, textContentWidth, titleFontSize * 1.2);
    requiredTextHeight += GAP;

    // Measure Ingredients text block
    ctx.font = `700 ${headingFontSize}px "${fontFamily}", serif`;
    requiredTextHeight += measureWrappedTextHeight(ctx, "Ingredients", textContentWidth, headingFontSize * 1.2);
    requiredTextHeight += GAP * 0.5;
    ctx.font = `400 ${bodyFontSize}px "${fontFamily}", serif`;
    requiredTextHeight += measureWrappedTextHeight(ctx, ingredientsText, textContentWidth, bodyFontSize * 1.5);
    requiredTextHeight += GAP;

    // Measure Instructions text block
    ctx.font = `700 ${headingFontSize}px "${fontFamily}", serif`;
    requiredTextHeight += measureWrappedTextHeight(ctx, "Instructions", textContentWidth, headingFontSize * 1.2);
    requiredTextHeight += GAP * 0.5;
    ctx.font = `400 ${bodyFontSize}px "${fontFamily}", serif`;
    requiredTextHeight += measureWrappedTextHeight(ctx, instructionsText, textContentWidth, bodyFontSize * 1.5);
    requiredTextHeight += PADDING;

    canvas.width = IMAGE_PANEL_WIDTH + TEXT_PANEL_WIDTH;
    canvas.height = Math.ceil(Math.max(scaledImageHeight, requiredTextHeight));

    // --- Drawing Phase ---

    ctx.fillStyle = backgroundColor;
    ctx.fillRect(IMAGE_PANEL_WIDTH, 0, TEXT_PANEL_WIDTH, canvas.height);

    const imageY = (canvas.height - scaledImageHeight) / 2;
    ctx.drawImage(originalImg, 0, Math.max(0, imageY), scaledImageWidth, scaledImageHeight);

    ctx.fillStyle = textColor;
    ctx.textAlign = 'left';
    ctx.textBaseline = 'top';
    const textX = IMAGE_PANEL_WIDTH + PADDING;
    let currentY = PADDING;

    // Draw Title
    ctx.font = `700 ${titleFontSize}px "${fontFamily}", serif`;
    currentY = drawWrappedText(ctx, recipeTitle, textX, currentY, textContentWidth, titleFontSize * 1.2);
    currentY += GAP;

    // Draw Ingredients
    ctx.font = `700 ${headingFontSize}px "${fontFamily}", serif`;
    currentY = drawWrappedText(ctx, "Ingredients", textX, currentY, textContentWidth, headingFontSize * 1.2);
    currentY += GAP * 0.5;
    ctx.font = `400 ${bodyFontSize}px "${fontFamily}", serif`;
    currentY = drawWrappedText(ctx, ingredientsText, textX, currentY, textContentWidth, bodyFontSize * 1.5);
    currentY += GAP;

    // Draw Instructions
    ctx.font = `700 ${headingFontSize}px "${fontFamily}", serif`;
    currentY = drawWrappedText(ctx, "Instructions", textX, currentY, textContentWidth, headingFontSize * 1.2);
    currentY += GAP * 0.5;
    ctx.font = `400 ${bodyFontSize}px "${fontFamily}", serif`;
    currentY = drawWrappedText(ctx, instructionsText, textX, currentY, textContentWidth, bodyFontSize * 1.5);

    return canvas;
}

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 And Ingredients Guide tool allows users to create visually appealing recipe cards by combining an uploaded image with customized text. Users can input a recipe title, a list of ingredients, and step-by-step instructions. The tool generates a canvas that displays the image alongside the recipe information in a structured layout. It is suitable for home cooks looking to share their culinary creations on social media, food bloggers designing content for their websites, or anyone wanting to create personalized recipe cards for events or gifts.

Leave a Reply

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