Please bookmark this page to avoid losing your image tool!

Image Wavelet Denoising 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, threshold = 20, waveletType = 'haar') {

    // --- Start Helper Functions ---

    /**
     * Performs a 2D forward Haar wavelet transform on a matrix.
     * The dimensions of the matrix must be powers of 2.
     * This is an in-place transform.
     * @param {number[][]} matrix - The 2D array of data (e.g., a color channel).
     * @param {number} width - The width of the matrix.
     * @param {number} height - The height of the matrix.
     * @returns {number[][]} The transformed matrix.
     */
    const fwt2d = (matrix, width, height) => {
        let w = width, h = height;
        const temp = new Float64Array(Math.max(w, h));

        // The transform is applied level by level to the approximation sub-band (top-left).
        while (w > 1 || h > 1) {
            let halfW = Math.max(1, Math.floor(w / 2));
            let halfH = Math.max(1, Math.floor(h / 2));

            // Transform rows
            if (w > 1) {
                for (let i = 0; i < h; i++) {
                    for (let j = 0; j < halfW; j++) {
                        const a = matrix[i][2 * j];
                        const b = matrix[i][2 * j + 1];
                        temp[j] = (a + b) / 2;
                        temp[j + halfW] = (a - b) / 2;
                    }
                    for (let j = 0; j < w; j++) matrix[i][j] = temp[j];
                }
            }

            // Transform columns
            if (h > 1) {
                for (let i = 0; i < w; i++) {
                    for (let j = 0; j < halfH; j++) {
                        const a = matrix[2 * j][i];
                        const b = matrix[2 * j + 1][i];
                        temp[j] = (a + b) / 2;
                        temp[j + halfH] = (a - b) / 2;
                    }
                    for (let j = 0; j < h; j++) matrix[j][i] = temp[j];
                }
            }
            w = halfW;
            h = halfH;
        }
        return matrix;
    };

    /**
     * Performs a 2D inverse Haar wavelet transform.
     * Reverses the process of fwt2d. This is an in-place transform.
     * @param {number[][]} matrix - Transformed 2D wavelet coefficients.
     * @param {number} width - The original width of the matrix.
     * @param {number} height - The original height of the matrix.
     * @returns {number[][]} The reconstructed matrix.
     */
    const iwt2d = (matrix, width, height) => {
        // Determine the size of the smallest approximation sub-band
        let maxLevels = Math.floor(Math.log2(Math.min(width, height)));
        let w = width / Math.pow(2, maxLevels);
        let h = height / Math.pow(2, maxLevels);

        const temp = new Float64Array(Math.max(width, height));

        // Iteratively apply the inverse transform, doubling the size at each level
        while (w <= width && h <= height) {
            let nextW = w, nextH = h;
            if (w < width) nextW = w * 2;
            if (h < height) nextH = h * 2;
            if (nextW === w && nextH === h) break;

            // Inverse transform columns first
            if (nextH > h) {
                for (let i = 0; i < w; i++) {
                    for (let j = 0; j < h; j++) {
                        const avg = matrix[j][i];
                        const diff = matrix[j + h][i];
                        temp[2 * j] = avg + diff;
                        temp[2 * j + 1] = avg - diff;
                    }
                    for (let j = 0; j < nextH; j++) matrix[j][i] = temp[j];
                }
            }

            // Inverse transform rows
            if (nextW > w) {
                for (let i = 0; i < nextH; i++) { // Use nextH here
                    for (let j = 0; j < w; j++) {
                        const avg = matrix[i][j];
                        const diff = matrix[i][j + w];
                        temp[2 * j] = avg + diff;
                        temp[2 * j + 1] = avg - diff;
                    }
                    for (let j = 0; j < nextW; j++) matrix[i][j] = temp[j];
                }
            }
            w = nextW;
            h = nextH;
        }
        return matrix;
    };

    /**
     * Applies soft thresholding to a value.
     * @param {number} value - The input coefficient.
     * @param {number} thresh - The threshold.
     * @returns {number} The thresholded value.
     */
    const softThreshold = (value, thresh) => {
        return Math.sign(value) * Math.max(0, Math.abs(value) - thresh);
    };

    /**
     * Applies thresholding to the detail coefficients of a transformed matrix.
     * It avoids thresholding the final approximation sub-band (top-left corner).
     * @param {number[][]} coeffs - The matrix of wavelet coefficients.
     * @param {number} thresh - The threshold value.
     * @param {number} width - The width of the coefficient matrix.
     * @param {number} height - The height of the coefficient matrix.
     */
    const thresholdCoefficients = (coeffs, thresh, width, height) => {
        let maxLevels = Math.floor(Math.log2(Math.min(width, height)));
        let approxW = width / Math.pow(2, maxLevels);
        let approxH = height / Math.pow(2, maxLevels);

        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                // If the coefficient is NOT in the top-leftmost approximation band, threshold it
                if (x >= approxW || y >= approxH) {
                    coeffs[y][x] = softThreshold(coeffs[y][x], thresh);
                }
            }
        }
    };
    
    /**
     * Denoises a single color channel using the wavelet transform.
     * @param {Float64Array[]} channelData - 2D array for the color channel.
     * @param {number} threshold - The denoising threshold.
     * @param {string} waveletType - The type of wavelet (only 'haar' supported).
     * @param {number} width - The padded width.
     * @param {number} height - The padded height.
     * @returns {number[][]} The denoised 2D channel data.
     */
    const denoiseChannel = (channelData, threshold, waveletType, width, height) => {
        if (waveletType !== 'haar') {
            console.warn(`Wavelet type '${waveletType}' is not supported. Using 'haar'.`);
        }
        
        // Deep copy of the channel data to prevent modifying the original during in-place transforms
        const dataCopy = channelData.map(row => new Float64Array(row));

        const transformedData = fwt2d(dataCopy, width, height);
        thresholdCoefficients(transformedData, threshold, width, height);
        const denoisedData = iwt2d(transformedData, width, height);

        return denoisedData;
    };


    // --- End Helper Functions ---


    // 1. SETUP: PAD IMAGE TO POWER-OF-2 DIMENSIONS
    const originalWidth = originalImg.width;
    const originalHeight = originalImg.height;

    const paddedWidth = 1 << Math.ceil(Math.log2(originalWidth));
    const paddedHeight = 1 << Math.ceil(Math.log2(originalHeight));
    
    const padCanvas = document.createElement('canvas');
    padCanvas.width = paddedWidth;
    padCanvas.height = paddedHeight;
    const padCtx = padCanvas.getContext('2d', { willReadFrequently: true });
    padCtx.drawImage(originalImg, 0, 0, originalWidth, originalHeight);
    
    const imageData = padCtx.getImageData(0, 0, paddedWidth, paddedHeight);
    const data = imageData.data;

    // 2. SEPARATE CHANNELS
    // Initialize 2D arrays for R, G, B channels
    const R = [], G = [], B = [];
    for (let i = 0; i < paddedHeight; i++) {
        R[i] = new Float64Array(paddedWidth);
        G[i] = new Float64Array(paddedWidth);
        B[i] = new Float64Array(paddedWidth);
    }
    // Populate the channel arrays
    for (let y = 0; y < paddedHeight; y++) {
        for (let x = 0; x < paddedWidth; x++) {
            const i = (y * paddedWidth + x) * 4;
            R[y][x] = data[i];
            G[y][x] = data[i + 1];
            B[y][x] = data[i + 2];
        }
    }
    
    // 3. PROCESS EACH CHANNEL
    const denoisedR = denoiseChannel(R, threshold, waveletType, paddedWidth, paddedHeight);
    const denoisedG = denoiseChannel(G, threshold, waveletType, paddedWidth, paddedHeight);
    const denoisedB = denoiseChannel(B, threshold, waveletType, paddedWidth, paddedHeight);
    
    // 4. RECONSTRUCT IMAGE FROM DENOISED CHANNELS
    const newImageData = padCtx.createImageData(paddedWidth, paddedHeight);
    const newData = newImageData.data;

    for (let y = 0; y < paddedHeight; y++) {
        for (let x = 0; x < paddedWidth; x++) {
            const i = (y * paddedWidth + x) * 4;
            // Clamp values to the valid 0-255 range and reconstruct pixel
            newData[i] = Math.max(0, Math.min(255, denoisedR[y][x]));
            newData[i + 1] = Math.max(0, Math.min(255, denoisedG[y][x]));
            newData[i + 2] = Math.max(0, Math.min(255, denoisedB[y][x]));
            newData[i + 3] = data[i + 3]; // Preserve original alpha channel
        }
    }
    
    // Put the denoised (but still padded) data back onto the padded canvas
    padCtx.putImageData(newImageData, 0, 0);

    // 5. CROP TO ORIGINAL DIMENSIONS AND RETURN
    const finalCanvas = document.createElement('canvas');
    finalCanvas.width = originalWidth;
    finalCanvas.height = originalHeight;
    const finalCtx = finalCanvas.getContext('2d');
    finalCtx.drawImage(padCanvas, 0, 0, originalWidth, originalHeight, 0, 0, originalWidth, originalHeight);

    return finalCanvas;
}

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 Wavelet Denoising Tool allows users to reduce noise in images by applying wavelet transformations, specifically using the Haar wavelet method. It processes images to enhance their quality by minimizing unwanted variations while retaining significant details. This tool is particularly useful for photographers and digital artists who want to improve the clarity of their images or for anyone looking to enhance image processing in fields such as medical imaging, computer vision, or remote sensing. Users can specify a threshold for denoising, enabling fine control over the level of noise reduction applied to their images.

Leave a Reply

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