Please bookmark this page to avoid losing your image tool!

Image Dehazing 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.
function processImage(originalImg, omega = 0.95, t0 = 0.1, patchSize = 15, airlightPercentile = 0.001, transmissionBlurRadius = 0) {
    // Ensure numeric parameters are valid
    omega = Number(omega);
    t0 = Number(t0);
    patchSize = Math.max(1, Math.floor(Number(patchSize)));
    airlightPercentile = Number(airlightPercentile);
    transmissionBlurRadius = Math.max(0, Math.floor(Number(transmissionBlurRadius)));

    const canvas = document.createElement('canvas');
    // Use { willReadFrequently: true } for potential performance optimization hint
    const ctx = canvas.getContext('2d', { willReadFrequently: true });
    
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;

    if (canvas.width === 0 || canvas.height === 0) {
        return canvas; // Return empty canvas for zero-dimension image
    }

    ctx.drawImage(originalImg, 0, 0);

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const { data, width, height } = imageData;
    const numPixels = width * height;

    // Helper: Min filter for a 2D array (single channel map)
    // filterRadius: kernel extends 'filterRadius' pixels from center (kernel size (2*R+1)x(2*R+1))
    function minFilter(inputMap, w, h, filterRadius) {
        const outputMap = new Float32Array(w * h);
        if (filterRadius <= 0) { // Kernel 1x1, no actual filtering
            for(let i=0; i < inputMap.length; ++i) outputMap[i] = inputMap[i];
            return outputMap;
        }

        for (let y = 0; y < h; y++) {
            for (let x = 0; x < w; x++) {
                let minVal = Infinity;
                for (let j = -filterRadius; j <= filterRadius; j++) {
                    for (let i = -filterRadius; i <= filterRadius; i++) {
                        const currentY = Math.min(h - 1, Math.max(0, y + j));
                        const currentX = Math.min(w - 1, Math.max(0, x + i));
                        minVal = Math.min(minVal, inputMap[currentY * w + currentX]);
                    }
                }
                outputMap[y * w + x] = minVal;
            }
        }
        return outputMap;
    }

    // Helper: Box blur for a 2D array
    // blurRadius: same meaning as filterRadius for minFilter.
    function boxBlur(inputMap, w, h, blurRadius) {
        if (blurRadius <= 0) { // No blurring
            const outputMap = new Float32Array(w * h);
            for(let i=0; i<inputMap.length; ++i) outputMap[i] = inputMap[i];
            return outputMap;
        }
        
        const outputMap = new Float32Array(w * h);
        const kernelPixelCount = (2 * blurRadius + 1) * (2 * blurRadius + 1);

        for (let y = 0; y < h; y++) {
            for (let x = 0; x < w; x++) {
                let sum = 0;
                for (let j = -blurRadius; j <= blurRadius; j++) {
                    for (let i = -blurRadius; i <= blurRadius; i++) {
                        const currentY = Math.min(h - 1, Math.max(0, y + j));
                        const currentX = Math.min(w - 1, Math.max(0, x + i));
                        sum += inputMap[currentY * w + currentX];
                    }
                }
                outputMap[y * w + x] = sum / kernelPixelCount;
            }
        }
        return outputMap;
    }

    // 1. Dark Channel Calculation (for airlight estimation)
    const darkChannel = new Float32Array(numPixels);
    for (let i = 0; i < numPixels; i++) {
        darkChannel[i] = Math.min(data[i * 4], data[i * 4 + 1], data[i * 4 + 2]);
    }

    // 2. Airlight Estimation (A = Ar, Ag, Ab)
    const numAirlightPixelsToConsider = Math.max(1, Math.floor(numPixels * airlightPercentile));
    
    const sortedDarkChannelPixels = Array.from(darkChannel)
        .map((val, originalIndex) => ({ val, originalIndex }))
        .sort((a, b) => b.val - a.val); // Sort descending by dark channel value

    let Ar_sum = 0, Ag_sum = 0, Ab_sum = 0;
    let numEffectiveAirlightPixels = 0;

    for (let i = 0; i < numAirlightPixelsToConsider; i++) {
        if (i >= sortedDarkChannelPixels.length) break;
        
        const pixelOriginalIndex = sortedDarkChannelPixels[i].originalIndex;
        Ar_sum += data[pixelOriginalIndex * 4];
        Ag_sum += data[pixelOriginalIndex * 4 + 1];
        Ab_sum += data[pixelOriginalIndex * 4 + 2];
        numEffectiveAirlightPixels++;
    }

    let Ar, Ag, Ab;
    if (numEffectiveAirlightPixels > 0) {
        Ar = Ar_sum / numEffectiveAirlightPixels;
        Ag = Ag_sum / numEffectiveAirlightPixels;
        Ab = Ab_sum / numEffectiveAirlightPixels;
    } else { 
        // Fallback if no pixels considered (e.g. tiny image, extreme percentile)
        Ar = 220; Ag = 220; Ab = 220; // Default to a bright grey
    }
    
    // Ensure airlight components are not too small to avoid division issues.
    Ar = Math.max(1.0, Ar); 
    Ag = Math.max(1.0, Ag); 
    Ab = Math.max(1.0, Ab);

    // 3. Transmission Map Estimation (t(x))
    const minNormalizedChannels = new Float32Array(numPixels);
    for (let i = 0; i < numPixels; i++) {
        minNormalizedChannels[i] = Math.min(
            data[i * 4] / Ar, 
            data[i * 4 + 1] / Ag, 
            data[i * 4 + 2] / Ab
        );
    }
    
    const cfgMinFilterRadius = Math.floor(patchSize / 2); 
    const darkChannelOfNormalizedPatched = minFilter(minNormalizedChannels, width, height, cfgMinFilterRadius);

    const transmission = new Float32Array(numPixels);
    for (let i = 0; i < numPixels; i++) {
        let term = darkChannelOfNormalizedPatched[i];
        // Clamp term: min_patch(min_c(Ic/Ac)) is an estimate for (1 - true_transmission), so should be [0,1]
        term = Math.max(0.0, Math.min(term, 1.0)); 
        transmission[i] = 1.0 - omega * term;
        // Clamp transmission itself to [0,1]
        transmission[i] = Math.max(0.0, Math.min(transmission[i], 1.0));
    }

    // 4. Transmission Map Refinement
    let refinedTransmission = transmission; 
    if (transmissionBlurRadius > 0) {
        refinedTransmission = boxBlur(transmission, width, height, transmissionBlurRadius);
    }

    // 5. Recover Scene Radiance (J)
    const outputImageData = ctx.createImageData(width, height);
    for (let i = 0; i < numPixels; i++) {
        const t_val = Math.max(refinedTransmission[i], t0); 

        outputImageData.data[i * 4]     = Math.max(0, Math.min(255, (data[i * 4]     - Ar) / t_val + Ar));
        outputImageData.data[i * 4 + 1] = Math.max(0, Math.min(255, (data[i * 4 + 1] - Ag) / t_val + Ag));
        outputImageData.data[i * 4 + 2] = Math.max(0, Math.min(255, (data[i * 4 + 2] - Ab) / t_val + Ab));
        outputImageData.data[i * 4 + 3] = data[i * 4 + 3]; // Preserve alpha
    }

    ctx.putImageData(outputImageData, 0, 0);
    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 Dehazing Tool is designed to enhance the clarity of photographs that are affected by fog, mist, or haze. By utilizing advanced algorithms that estimate airlight and transmission maps, this tool effectively restores contrast and visibility to obscured images. It can be particularly useful for outdoor photography, enhancing landscape images, improving visibility in images taken in poor weather conditions, and restoring details in photographs that lack clarity due to atmospheric interference. Users can adjust parameters for more customized results, making it suitable for both casual photographers and professional users looking to improve image quality.

Leave a Reply

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