Please bookmark this page to avoid losing your image tool!

Image Double Exposure Filter

(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, overlayImgUrl = "https://picsum.photos/seed/doubleexposure/800/600", blendMode = "overlay", overlayOpacity = 0.5) {

    // Helper function to ensure an image is loaded
    const _ensureImageLoaded = function(imageObject, imageDescription) {
        return new Promise((resolve, reject) => {
            if (!imageObject.src && !(imageObject instanceof HTMLImageElement && imageObject.currentSrc)) { // check if src or currentSrc (for img tags in DOM) is missing
                reject(new Error(`${imageDescription} has no src attribute set.`));
                return;
            }

            if (imageObject.complete && imageObject.naturalWidth !== 0) {
                resolve();
                return;
            }
            
            if (imageObject.complete && imageObject.naturalWidth === 0) {
                 // This can happen if src is invalid, image is broken, or for an img tag that had an error but complete is true
                reject(new Error(`${imageDescription} is broken, not a valid image, or failed to load. URL: ${imageObject.src || imageObject.currentSrc}`));
                return;
            }

            let loadListener, errorListener;
            const cleanup = () => {
                imageObject.removeEventListener('load', loadListener);
                imageObject.removeEventListener('error', errorListener);
            };

            loadListener = () => {
                cleanup();
                if (imageObject.naturalWidth === 0) {
                    reject(new Error(`${imageDescription} loaded but has zero width/height (likely an error or empty image). URL: ${imageObject.src || imageObject.currentSrc}`));
                } else {
                    resolve();
                }
            };

            errorListener = (err) => {
                cleanup();
                reject(new Error(`${imageDescription} failed to load. URL: ${imageObject.src || imageObject.currentSrc}. Error: ${err}`));
            };

            imageObject.addEventListener('load', loadListener);
            imageObject.addEventListener('error', errorListener);
        });
    };

    // 1. Parameter validation
    let numOpacity = Number(overlayOpacity);
    if (isNaN(numOpacity)) {
        console.warn(`Invalid overlayOpacity: "${overlayOpacity}". Using default 0.5.`);
        overlayOpacity = 0.5;
    } else if (numOpacity < 0 || numOpacity > 1) {
        console.warn(`overlayOpacity ${numOpacity} out of range [0,1]. Clamping value.`);
        overlayOpacity = Math.max(0, Math.min(1, numOpacity));
    }
    // Ensure overlayOpacity is the number after validation
    overlayOpacity = Number(overlayOpacity);

    blendMode = String(blendMode).toLowerCase();
    overlayImgUrl = String(overlayImgUrl);

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

    // 2. Load originalImg
    try {
        await _ensureImageLoaded(originalImg, "Original image");
        canvas.width = originalImg.naturalWidth;
        canvas.height = originalImg.naturalHeight;
        ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    } catch (err) {
        console.error(err.message);
        // Fallback canvas for original image load error
        canvas.width = originalImg.width || 300; // Use HTML attribute width or default
        canvas.height = originalImg.height || 150; // Use HTML attribute height or default
        ctx.fillStyle = 'lightgray';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.font = "16px Arial";
        ctx.fillStyle = "black";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText("Original image error.", canvas.width / 2, canvas.height / 2);
        return canvas;
    }

    // 3. Load overlay image
    if (!overlayImgUrl || overlayImgUrl.trim() === "") {
        console.warn("Overlay image URL is empty. Proceeding without overlay.");
        return canvas; // Return canvas with only original image
    }

    const overlayImage = new Image();
    if (!overlayImgUrl.startsWith('data:')) {
        overlayImage.crossOrigin = "Anonymous";
    }
    overlayImage.src = overlayImgUrl;

    try {
        await _ensureImageLoaded(overlayImage, "Overlay image");
    } catch (err) {
        console.warn(err.message + " Proceeding without overlay.");
        return canvas; // Return canvas with only original image
    }
    
    if (overlayImage.naturalWidth === 0 || overlayImage.naturalHeight === 0) {
        console.warn("Overlay image loaded but has zero dimensions. Proceeding without overlay.");
        return canvas;
    }

    // 4. Apply blend and draw overlay
    ctx.globalAlpha = overlayOpacity;

    const validBlendModes = [
        "source-over", "source-in", "source-out", "source-atop",
        "destination-over", "destination-in", "destination-out", "destination-atop",
        "lighter", "copy", "xor", "multiply", "screen", "overlay", "darken",
        "lighten", "color-dodge", "color-burn", "hard-light", "soft-light",
        "difference", "exclusion", "hue", "saturation", "color", "luminosity"
    ];

    if (validBlendModes.includes(blendMode)) {
        ctx.globalCompositeOperation = blendMode;
    } else {
        console.warn(`Invalid blendMode: "${blendMode}". Using default 'overlay'.`);
        ctx.globalCompositeOperation = 'overlay';
    }

    // Calculate "cover" dimensions for overlay image
    // This will make the overlay image cover the canvas, preserving aspect ratio, cropping if necessary.
    const targetWidth = canvas.width;
    const targetHeight = canvas.height;
    const sourceWidth = overlayImage.naturalWidth;
    const sourceHeight = overlayImage.naturalHeight;

    const targetAspectRatio = targetWidth / targetHeight;
    const sourceAspectRatio = sourceWidth / sourceHeight;

    let sWidth, sHeight, sx, sy;

    if (sourceAspectRatio > targetAspectRatio) { // Overlay is wider than canvas aspect ratio (needs horizontal crop)
        sHeight = sourceHeight;
        sWidth = sourceHeight * targetAspectRatio;
        sx = (sourceWidth - sWidth) / 2;
        sy = 0;
    } else { // Overlay is taller or same aspect ratio (needs vertical crop or no crop)
        sWidth = sourceWidth;
        sHeight = sourceWidth / targetAspectRatio;
        sx = 0;
        sy = (sourceHeight - sHeight) / 2;
    }
    
    ctx.drawImage(overlayImage, sx, sy, sWidth, sHeight, 0, 0, targetWidth, targetHeight);

    // Reset canvas context properties to defaults
    ctx.globalAlpha = 1.0;
    ctx.globalCompositeOperation = 'source-over';

    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 Double Exposure Filter tool allows users to create a double exposure effect by blending two images together. Users can upload a main image and an overlay image, which can be adjusted for opacity and selected from various blend modes. This tool is useful for artistic photography, graphic design, and social media content creation, enabling users to create visually striking images that combine elements from both source images.

Leave a Reply

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