Please bookmark this page to avoid losing your image tool!

Image Merger 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, additionalImageUrls = "", mergeDirection = "horizontal", gap = 0, alignment = "top") {

    // Helper function to load an image from a URL
    function loadImageFromUrl(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            // Attempt to enable CORS for images from other domains.
            // This is necessary to avoid tainting the canvas if images are from different origins,
            // which would restrict further canvas operations like toDataURL().
            img.crossOrigin = "Anonymous"; 
            img.onload = () => resolve(img);
            img.onerror = () => {
                // console.error(`Failed to load image: ${url}`);
                reject(new Error(`Failed to load image at URL: ${url}`));
            };
            img.src = url;
        });
    }

    const images = [];
    // Add originalImg to the list if it's a valid, loaded image.
    // We rely on naturalWidth/naturalHeight, which are 0 for unloaded/invalid images.
    if (originalImg && typeof originalImg.naturalWidth === 'number' && originalImg.naturalWidth > 0 && typeof originalImg.naturalHeight === 'number' && originalImg.naturalHeight > 0) {
        images.push(originalImg);
    } else if (originalImg && (originalImg.complete === false || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0)) {
        // originalImg might be an Image object that hasn't loaded or is invalid
        // We could try to wait for it if it has a src, but the premise is it's an "Image object"
        // implying it should be ready. If not, it's considered invalid for merging for now.
        console.warn("Original image (originalImg) is not fully loaded or has zero dimensions. It will be excluded from the merge.");
    }


    // Load additional images if URLs are provided
    if (additionalImageUrls && additionalImageUrls.trim() !== "") {
        const urls = additionalImageUrls.split(',')
            .map(url => url.trim())
            .filter(url => url.length > 0); // Filter out empty strings

        if (urls.length > 0) {
            const additionalImagePromises = urls.map(loadImageFromUrl);
            try {
                const loadedAdditionalImages = await Promise.all(additionalImagePromises);
                // Filter again for successfully loaded additional images (naturalWidth > 0)
                loadedAdditionalImages.forEach(img => {
                    if (img && typeof img.naturalWidth === 'number' && img.naturalWidth > 0 && typeof img.naturalHeight === 'number' && img.naturalHeight > 0) {
                        images.push(img);
                    } else {
                         console.warn(`An additional image was not loaded correctly or has zero dimensions and will be excluded.`);
                    }
                });
            } catch (error) {
                console.error("Error loading one or more additional images:", error);
                const errorCanvas = document.createElement('canvas');
                errorCanvas.width = 350;
                errorCanvas.height = 100;
                const ctx = errorCanvas.getContext('2d');
                ctx.fillStyle = 'black'; // Changed from 'red' for better readability sometimes
                ctx.font = 'bold 14px Arial';
                ctx.fillText('Error loading additional images:', 10, 30);
                ctx.font = '14px Arial';
                const errorMessage = error.message || "Unknown error.";
                ctx.fillText(errorMessage.length > 45 ? errorMessage.substring(0, 42) + '...' : errorMessage, 10, 60);
                return errorCanvas;
            }
        }
    }
    
    // At this point, `images` contains originalImg (if valid) and successfully loaded additional images.
    // Re-assign to validImages for clarity.
    const validImages = images;

    if (validImages.length === 0) {
        console.warn("No valid images to merge.");
        const infoCanvas = document.createElement('canvas');
        infoCanvas.width = 300;
        infoCanvas.height = 50;
        const ctx = infoCanvas.getContext('2d');
        ctx.fillStyle = 'black';
        ctx.font = "12px Arial";
        ctx.fillText("No valid images available to merge.", 5, 25);
        return infoCanvas;
    }

    let canvasWidth = 0;
    let canvasHeight = 0;

    // Calculate final canvas dimensions
    if (mergeDirection === "horizontal") {
        // Height is the max height of all images
        canvasHeight = Math.max(...validImages.map(img => img.naturalHeight));
        // Width is sum of all image widths + gaps between them
        canvasWidth = validImages.reduce((sum, img) => sum + img.naturalWidth, 0);
        if (validImages.length > 1) {
            canvasWidth += (validImages.length - 1) * gap;
        }
    } else if (mergeDirection === "vertical") {
        // Width is the max width of all images
        canvasWidth = Math.max(...validImages.map(img => img.naturalWidth));
        // Height is sum of all image heights + gaps between them
        canvasHeight = validImages.reduce((sum, img) => sum + img.naturalHeight, 0);
        if (validImages.length > 1) {
            canvasHeight += (validImages.length - 1) * gap;
        }
    } else {
        // Fallback for invalid mergeDirection string
        console.warn(`Invalid mergeDirection: "${mergeDirection}". Defaulting to horizontal.`);
        mergeDirection = "horizontal"; // Ensure drawing logic uses a valid direction
        canvasHeight = Math.max(...validImages.map(img => img.naturalHeight));
        canvasWidth = validImages.reduce((sum, img) => sum + img.naturalWidth, 0);
        if (validImages.length > 1) {
            canvasWidth += (validImages.length - 1) * gap;
        }
    }
    
    // Ensure canvas dimensions are at least 1x1 to avoid errors creating the canvas.
    if (canvasWidth <= 0) canvasWidth = 1;
    if (canvasHeight <= 0) canvasHeight = 1;

    const canvas = document.createElement('canvas');
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    const ctx = canvas.getContext('2d');

    let currentX = 0; // Tracks the x-coordinate for drawing the next image in horizontal merge
    let currentY = 0; // Tracks the y-coordinate for drawing the next image in vertical merge

    validImages.forEach((img, index) => {
        const imgWidth = img.naturalWidth;
        const imgHeight = img.naturalHeight;
        let drawX, drawY; // Coordinates to draw the current image at

        if (mergeDirection === "horizontal") {
            drawX = currentX;
            switch (alignment) {
                case "middle":
                    drawY = (canvasHeight - imgHeight) / 2;
                    break;
                case "bottom":
                    drawY = canvasHeight - imgHeight;
                    break;
                case "top":
                default: // Default to "top" alignment
                    drawY = 0;
                    break;
            }
            ctx.drawImage(img, drawX, drawY, imgWidth, imgHeight);
            currentX += imgWidth;
            if (index < validImages.length - 1) { // Add gap if not the last image
                currentX += gap;
            }
        } else { // Vertical merge
            drawY = currentY;
            switch (alignment) {
                case "center":
                    drawX = (canvasWidth - imgWidth) / 2;
                    break;
                case "right":
                    drawX = canvasWidth - imgWidth;
                    break;
                case "left":
                default: // Default to "left" alignment
                    drawX = 0;
                    break;
            }
            ctx.drawImage(img, drawX, drawY, imgWidth, imgHeight);
            currentY += imgHeight;
            if (index < validImages.length - 1) { // Add gap if not the last image
                currentY += gap;
            }
        }
    });

    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 Merger Tool allows users to combine multiple images into a single image. Users can specify images by providing an original image along with additional image URLs. The tool supports both horizontal and vertical merging directions, and it offers options for setting gaps between images as well as their alignment within the merged output. This tool is useful for creating collages, combining screenshots, or assembling visual elements for presentations and social media posts.

Leave a Reply

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