Please bookmark this page to avoid losing your image tool!

Image Meme Generator

(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, 
    topText = "TOP TEXT", 
    bottomText = "BOTTOM TEXT", 
    fontFamily = "Anton, Impact, Arial, sans-serif", 
    fontSize = 40, 
    fontColor = "white", 
    strokeColor = "black", 
    strokeWidth = 2) {

    // Initialize a simple font manager on the function object itself to persist loaded fonts state
    // This avoids reloading fonts on subsequent calls if the environment reuses the function object.
    if (typeof processImage._fontManager === 'undefined') {
        processImage._fontManager = {
            loadedFonts: new Set(),
            loadFont: async function(fontFamilyName, fontUrl) {
                // If font already loaded or attempted, don't try again
                if (this.loadedFonts.has(fontFamilyName.toLowerCase())) {
                    return;
                }
                const fontFace = new FontFace(fontFamilyName, `url(${fontUrl})`);
                try {
                    await fontFace.load();
                    document.fonts.add(fontFace);
                    this.loadedFonts.add(fontFamilyName.toLowerCase()); // Track successful loads
                    // console.log(`${fontFamilyName} font loaded from ${fontUrl}`);
                } catch (e) {
                    console.error(`${fontFamilyName} font failed to load from ${fontUrl}:`, e);
                    this.loadedFonts.add(fontFamilyName.toLowerCase()); // Also track failures to avoid retries
                    // Fallback fonts specified in the CSS font-family string will be used by the browser
                }
            }
        };
    }

    // Dynamically load "Anton" font if it's part of the fontFamily string and not yet loaded.
    // Anton is a common alternative to Impact and available on Google Fonts.
    if (fontFamily.toLowerCase().includes("anton")) {
        const antonFontUrl = 'https://fonts.gstatic.com/s/anton/v23/1Ptgg87LROyfkjunu-IOtA.woff2'; // WOFF2 URL for Anton
        await processImage._fontManager.loadFont('Anton', antonFontUrl);
    }

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

    // Use naturalWidth/Height for actual image dimensions
    canvas.width = originalImg.naturalWidth;
    canvas.height = originalImg.naturalHeight;

    // Handle cases where image dimensions might be invalid
    if (canvas.width === 0 || canvas.height === 0) {
        console.error("Image has zero width or height. Cannot process.");
        // Return an empty (0x0) canvas or a small placeholder canvas
        canvas.width = Math.max(1, canvas.width); // Ensure at least 1px to avoid issues with 0-dim canvas
        canvas.height = Math.max(1, canvas.height);
        return canvas; 
    }

    // Draw the original image onto the canvas
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    // Validate and prepare font/style parameters
    const MIN_ALLOWED_FONT_SIZE = 8; // Smallest font size we'll render
    const actualFontSize = Math.max(MIN_ALLOWED_FONT_SIZE, Number(fontSize));
    const actualStrokeWidth = Math.max(0, Number(strokeWidth));

    ctx.fillStyle = fontColor;
    ctx.strokeStyle = strokeColor;
    ctx.lineWidth = actualStrokeWidth;
    ctx.textAlign = 'center';
    // Using `letterSpacing` can improve readability for some fonts like Impact/Anton
    // ctx.letterSpacing = "1px"; // Example, can be made a parameter

    // Internal helper function to draw text.
    // It converts text to uppercase, adjusts font size to fit maxWidth, and draws stroke then fill.
    function drawText(textToDraw, x, y, textMaxWidth, targetFontSize) {
        textToDraw = textToDraw.toUpperCase();
        
        let currentFontSize = targetFontSize;
        ctx.font = `bold ${currentFontSize}px ${fontFamily}`;
        let textMetrics = ctx.measureText(textToDraw);

        // Iteratively reduce font size until text fits within textMaxWidth or min font size is reached
        while (textMetrics.width > textMaxWidth && currentFontSize > MIN_ALLOWED_FONT_SIZE) {
            currentFontSize--;
            ctx.font = `bold ${currentFontSize}px ${fontFamily}`;
            textMetrics = ctx.measureText(textToDraw);
        }
        
        // Apply stroke if strokeWidth is positive
        if (actualStrokeWidth > 0) {
            ctx.strokeText(textToDraw, x, y);
        }
        // Fill the text
        ctx.fillText(textToDraw, x, y);
    }

    // Calculate maximum width for the text (e.g., canvas width minus some padding)
    const textSidePadding = Math.min(10, Math.round(canvas.width * 0.02)); // 2% or 10px padding on each side
    const textMaxWidth = Math.max(MIN_ALLOWED_FONT_SIZE, canvas.width - (textSidePadding * 2));


    // Determine vertical padding for the text from image edges
    const edgePadding = Math.max(textSidePadding, Math.round(canvas.height * 0.05)); // 5% height or at least side padding

    // Draw top text
    if (topText && topText.trim() !== "") {
        ctx.textBaseline = 'top';
        const topY = edgePadding;
        // Ensure text is drawable (e.g. image not too short for text)
        if (topY < canvas.height - edgePadding) { // Basic check: enough space between top and bottom expected lines
             drawText(topText, canvas.width / 2, topY, textMaxWidth, actualFontSize);
        }
    }

    // Draw bottom text
    if (bottomText && bottomText.trim() !== "") {
        ctx.textBaseline = 'bottom';
        const bottomY = canvas.height - edgePadding;
        // Ensure text is drawable and doesn't severely overlap with an already drawn top text on very short images
        // This check assumes top text (if any) started at `edgePadding` and used `actualFontSize`
        // A more complex check would measure top text's actual drawn height.
        if (bottomY > edgePadding + (topText && topText.trim() !== "" ? actualFontSize * 0.5 : 0) ) { // Basic check for overlap
            drawText(bottomText, canvas.width / 2, bottomY, textMaxWidth, actualFontSize);
        } else if (!(topText && topText.trim() !== "")) { // if no top text, draw bottom text anyway if space for itself.
             if (bottomY > MIN_ALLOWED_FONT_SIZE) {
                drawText(bottomText, canvas.width / 2, bottomY, textMaxWidth, actualFontSize);
             }
        }
    }
    
    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 Meme Generator allows users to create custom memes by adding text to images. Users can specify top and bottom text for their memes, choose the font family, adjust font size, and select colors for the text and its outline. This tool is ideal for social media enthusiasts, marketers, or anyone looking to share humorous or insightful messages through images. The generated memes can be utilized for personal sharing, promotional content, or creating engaging social media posts.

Leave a Reply

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