Please bookmark this page to avoid losing your image tool!

Image Text Behind Overlay 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,
    text = "TEXT BEHIND",
    fontFamily = "Impact",
    fontSize = 60, // Should be a number
    textColor = "black",
    textAlign = "center",
    textBaseline = "middle",
    textX = "center", // Can be "center", a number, or a string parseable to a number
    textY = "center"  // Can be "center", a number, or a string parseable to a number
) {
    // Validate fontSize type, as it's crucial for fontspec and calculations
    if (typeof fontSize !== 'number') {
        console.warn(`fontSize should be a number. Received ${typeof fontSize}. Using default 60.`);
        fontSize = 60;
    }
    
    // Promise for image loading. Handles various states of the Image object.
    const imageLoadPromise = new Promise((resolve, reject) => {
        // Case 1: Image already loaded and has valid dimensions
        if (originalImg.complete && originalImg.naturalWidth !== 0 && originalImg.naturalHeight !== 0) {
            resolve();
            return;
        }
        // Case 2: Image marked 'complete' but is broken or empty (0 dimensions)
        // This can happen for various reasons, e.g. invalid src, empty src, network error that has already completed.
        if (originalImg.complete && (originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0)) {
            reject(new Error(`Image (from ${originalImg.src || 'unknown source'}) is loaded but has zero dimensions. It might be an empty or broken image.`));
            return;
        }
        
        // Case 3: Image not yet loaded (or still loading)
        // Set up event listeners.
        originalImg.onload = () => {
            if (originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
                reject(new Error(`Image (from ${originalImg.src || 'unknown source'}) loaded successfully but has zero dimensions.`));
            } else {
                resolve();
            }
        };
        originalImg.onerror = () => {
            // onerror event doesn't always provide detailed error object.
            reject(new Error(`Failed to load image (from ${originalImg.src || 'unknown source'}). Check network access and URL correctness.`));
        };

        // Case 4: If src is not set and image is not 'complete', it cannot load.
        // (e.g. `new Image()` without `img.src = ...` being called yet).
        // If `originalImg.complete` is true here, it implies 0 dimensions (covered by Case 2) or non-0 (Case 1).
        if (!originalImg.src && !originalImg.complete) {
            reject(new Error("Image source (src) is not set, and the image is not marked as complete. Cannot load image."));
        }
        // If originalImg.src IS set, the browser attempts to load it. The onload/onerror handlers above will fire.
    });

    // Promise for font loading (best effort using document.fonts API)
    // The fontSpec string format is like "italic bold 16px Arial"
    const fontSpec = `${fontSize}px ${fontFamily}`;
    const fontLoadPromise = document.fonts.load(fontSpec)
        .catch(err => {
            console.warn(`Font '${fontFamily}' (using spec '${fontSpec}') might not be available or loadable via document.fonts.load(). Error: ${err}. The browser will attempt to use a fallback font.`);
            // Resolve even in case of error to not break Promise.all if image is fine.
            // document.fonts.load() resolves with an array of FontFace objects.
            return []; 
        });

    // Wait for both image loading (critical) and font loading (best-effort)
    try {
        await Promise.all([imageLoadPromise, fontLoadPromise]);
    } catch (error) {
        // This catch will primarily be for imageLoadPromise rejections,
        // as fontLoadPromise's errors are handled internally.
        console.error("Failed to prepare image for processing:", error);
        throw error; // Re-throw to signal failure to the caller
    }

    // If we reach here, image is loaded and has dimensions. Font is loaded or fallback will be used.
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    canvas.width = originalImg.naturalWidth;
    canvas.height = originalImg.naturalHeight;

    // Determine actual X coordinate for the text
    let actualTextX;
    if (textX === "center") {
        actualTextX = canvas.width / 2;
    } else if (typeof textX === 'string') {
        const parsedX = parseFloat(textX);
        if (isNaN(parsedX)) {
            console.warn(`Invalid string value for textX: "${textX}". Defaulting to canvas center (horizontal).`);
            actualTextX = canvas.width / 2;
        } else {
            actualTextX = parsedX;
        }
    } else if (typeof textX === 'number') {
        actualTextX = textX;
    } else {
        console.warn(`Unsupported type for textX: ${typeof textX}. Defaulting to canvas center (horizontal).`);
        actualTextX = canvas.width / 2;
    }

    // Determine actual Y coordinate for the text
    let actualTextY;
    if (textY === "center") {
        actualTextY = canvas.height / 2;
    } else if (typeof textY === 'string') {
        const parsedY = parseFloat(textY);
        if (isNaN(parsedY)) {
            console.warn(`Invalid string value for textY: "${textY}". Defaulting to canvas center (vertical).`);
            actualTextY = canvas.height / 2;
        } else {
            actualTextY = parsedY;
        }
    } else if (typeof textY === 'number') {
        actualTextY = textY;
    } else {
        console.warn(`Unsupported type for textY: ${typeof textY}. Defaulting to canvas center (vertical).`);
        actualTextY = canvas.height / 2;
    }
    
    // 1. Draw the text
    ctx.font = fontSpec; // Use the same fontSpec used for loading
    ctx.fillStyle = textColor;
    ctx.textAlign = textAlign;
    ctx.textBaseline = textBaseline;
    ctx.fillText(text, actualTextX, actualTextY);

    // 2. Draw the original image on top of the text
    // This is what makes the text appear "behind" the opaque parts of the image.
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

    return canvas; // The async function will return Promise<HTMLCanvasElement>
}

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 Text Behind Overlay Tool allows users to add customizable text to images, creating a layered effect where the text appears behind the main image components. Users can specify the text content, font family, size, color, alignment, and positioning on the image. This tool is ideal for creating visually appealing graphics for social media posts, promotional materials, or custom artworks, enabling individuals and businesses to enhance their images with personalized text overlays.

Leave a Reply

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