Please bookmark this page to avoid losing your image tool!

Image Split Comparison Creator With Text Overlay

(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, rightImgSrc = '', overlayText = 'Jollof rice vs. Mac and cheese: Which one’s the real MVP?', fontFamily = 'Bangers', fontSize = 80, textColor = '#FFFFFF', strokeColor = '#000000', strokeWidth = 6) {

    /**
     * Dynamically loads a font from Google Fonts.
     * @param {string} family - The font family name.
     * @param {string} url - The URL to the font file (e.g., woff2).
     */
    const loadFont = async (family, url) => {
        const font = new FontFace(family, `url(${url})`);
        try {
            await font.load();
            document.fonts.add(font);
        } catch (e) {
            console.error(`Font loading failed: ${family}`, e);
        }
    };

    /**
     * Loads an image from a source (like a data URL).
     * @param {string} src - The image source.
     * @returns {Promise<Image|null>} A promise that resolves with the loaded Image object or null.
     */
    const loadImage = (src) => {
        if (!src) {
            return Promise.resolve(null);
        }
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(img);
            img.onerror = (err) => {
                console.error("Failed to load right image.", err);
                resolve(null); // Resolve with null on error to not break the flow
            };
            img.src = src;
        });
    };

    /**
     * Wraps text to fit within a maximum width.
     * @param {CanvasRenderingContext2D} ctx - The canvas rendering context.
     * @param {string} text - The text to wrap.
     * @param {number} maxWidth - The maximum width for a line of text.
     * @returns {string[]} An array of text lines.
     */
    const wrapText = (ctx, text, maxWidth) => {
        const words = text.split(' ');
        const lines = [];
        let currentLine = words[0] || '';

        for (let i = 1; i < words.length; i++) {
            const word = words[i];
            const testLine = currentLine + ' ' + word;
            if (ctx.measureText(testLine).width < maxWidth) {
                currentLine = testLine;
            } else {
                lines.push(currentLine);
                currentLine = word;
            }
        }
        lines.push(currentLine);
        return lines;
    };
    
    /**
     * Draws a simple pepper doodle.
     */
    const drawPepperDoodle = (ctx, x, y) => {
        ctx.save();
        ctx.fillStyle = '#2E8B57'; // SeaGreen for stem
        ctx.beginPath();
        ctx.rect(x + 18, y, 5, 10);
        ctx.fill();

        ctx.fillStyle = '#DC143C'; // Crimson for body
        ctx.beginPath();
        ctx.moveTo(x, y + 10);
        ctx.bezierCurveTo(x - 5, y + 40, x + 45, y + 40, x + 40, y + 10);
        ctx.bezierCurveTo(x + 40, y + 25, x, y + 25, x, y + 10);
        ctx.fill();
        ctx.restore();
    };

    /**
     * Draws a simple cheese doodle.
     */
    const drawCheeseDoodle = (ctx, x, y) => {
        ctx.save();
        ctx.fillStyle = '#FFD700'; // Gold for cheese
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x + 40, y + 30);
        ctx.lineTo(x, y + 30);
        ctx.closePath();
        ctx.fill();

        // Holes
        ctx.globalCompositeOperation = 'destination-out';
        ctx.fillStyle = 'black'; // Color doesn't matter for destination-out
        ctx.beginPath();
        ctx.arc(x + 10, y + 22, 4, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(x + 25, y + 25, 3, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
    };


    // --- Main execution ---

    // 1. Load external resources (font and right image) in parallel
    await Promise.all([
        loadFont(fontFamily, 'https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5k.woff2'),
        loadImage(rightImgSrc)
    ]).then(([_, loadedRightImg]) => {
        // This 'then' block is just to name the loaded image result.
        // The rest of the function will execute after this promise resolves.
        
        // 2. Setup Canvas
        const canvas = document.createElement('canvas');
        const leftWidth = originalImg.naturalWidth;
        const leftHeight = originalImg.naturalHeight;
        const canvasWidth = leftWidth * 2;
        canvas.width = canvasWidth;
        canvas.height = leftHeight;
        const ctx = canvas.getContext('2d');

        // 3. Draw images
        ctx.drawImage(originalImg, 0, 0, leftWidth, leftHeight);
        
        if (loadedRightImg) {
            // Draw the right image, scaled to match the left image's dimensions
            ctx.drawImage(loadedRightImg, leftWidth, 0, leftWidth, leftHeight);
        } else {
            // Draw a placeholder if the right image is missing
            ctx.fillStyle = '#222';
            ctx.fillRect(leftWidth, 0, leftWidth, leftHeight);
            ctx.fillStyle = '#fff';
            ctx.font = '24px sans-serif';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText('Right Image Here', leftWidth + leftWidth / 2, leftHeight / 2);
        }

        // 4. Draw faint doodles
        ctx.save();
        ctx.globalAlpha = 0.25;
         for (let i = 0; i < 8; i++) {
            const x = Math.random() * (leftWidth - 60) + 10;
            const y = Math.random() * (leftHeight - 50) + 10;
            drawPepperDoodle(ctx, x, y);
            
            const x2 = Math.random() * (leftWidth - 60) + leftWidth + 10;
            const y2 = Math.random() * (leftHeight - 50) + 10;
            drawCheeseDoodle(ctx, x2, y2);
        }
        ctx.restore();

        // 5. Draw text overlay
        ctx.font = `${fontSize}px "${fontFamily}", comic sans ms, sans-serif`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';

        const lines = wrapText(ctx, overlayText, canvasWidth * 0.9);
        const lineHeight = fontSize * 1.1;
        const totalTextHeight = (lines.length - 1) * lineHeight;
        const startY = (canvas.height / 2) - (totalTextHeight / 2);

        lines.forEach((line, index) => {
            const y = startY + (index * lineHeight);
            ctx.strokeStyle = strokeColor;
            ctx.lineWidth = strokeWidth;
            ctx.strokeText(line, canvas.width / 2, y);

            ctx.fillStyle = textColor;
            ctx.fillText(line, canvas.width / 2, y);
        });
        
        // This is inside the .then, so we have to handle the return value carefully.
        // It's better to just use the variable from the outer scope.
    });
    
    // `processImage` is async, so we need to create the canvas outside the .then
    // for it to be returned. Let's refactor slightly.
    
    const [_, loadedRightImg] = await Promise.all([
        loadFont(fontFamily, 'https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5k.woff2'),
        loadImage(rightImgSrc)
    ]);
    
    const canvas = document.createElement('canvas');
    const leftWidth = originalImg.naturalWidth;
    const leftHeight = originalImg.naturalHeight;
    const canvasWidth = leftWidth * 2;
    canvas.width = canvasWidth;
    canvas.height = leftHeight;
    const ctx = canvas.getContext('2d');

    // Draw images
    ctx.drawImage(originalImg, 0, 0, leftWidth, leftHeight);
    
    if (loadedRightImg) {
        ctx.drawImage(loadedRightImg, leftWidth, 0, leftWidth, leftHeight);
    } else {
        ctx.fillStyle = '#222';
        ctx.fillRect(leftWidth, 0, leftWidth, leftHeight);
        ctx.fillStyle = '#fff';
        ctx.font = '30px sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText('Right Image Here', leftWidth + leftWidth / 2, leftHeight / 2);
    }

    // Draw doodles
    ctx.save();
    ctx.globalAlpha = 0.25;
    for (let i = 0; i < 8; i++) {
        const x = Math.random() * (leftWidth - 60) + 10;
        const y = Math.random() * (leftHeight - 50) + 10;
        drawPepperDoodle(ctx, x, y);
        
        const x2 = Math.random() * (leftWidth - 60) + leftWidth + 10;
        const y2 = Math.random() * (leftHeight - 50) + 10;
        drawCheeseDoodle(ctx, x2, y2);
    }
    ctx.restore();

    // Draw text
    ctx.font = `${fontSize}px "${fontFamily}", comic sans ms, sans-serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';

    const lines = wrapText(ctx, overlayText, canvasWidth * 0.9);
    const lineHeight = fontSize * 1.1;
    const totalTextHeight = (lines.length - 1) * lineHeight;
    const startY = (canvas.height / 2) - (totalTextHeight / 2);

    lines.forEach((line, index) => {
        const y = startY + (index * lineHeight);
        ctx.strokeStyle = strokeColor;
        ctx.lineWidth = strokeWidth;
        ctx.strokeText(line, canvas.width / 2, y);

        ctx.fillStyle = textColor;
        ctx.fillText(line, canvas.width / 2, y);
    });

    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 Split Comparison Creator with Text Overlay is a versatile tool designed to create side-by-side comparisons of two images. It allows users to input an original image and a secondary image, along with custom overlay text. The tool automatically formats and overlays the specified text onto the created canvas, which features both images displayed adjacently. This tool is particularly useful for visual comparisons, such as showcasing food items, product features, or before-and-after images in various contexts like cooking, retail, or health and fitness. With the ability to customize font, size, and color, users can enhance their presentations, making them more engaging and informative.

Leave a Reply

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