Please bookmark this page to avoid losing your image tool!

Image Frequency Visualization Filter Effect 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) {
    // Initialize a static-like variable on the function object itself for the script loading promise.
    // This helps manage script loading status across multiple calls to processImage.
    if (typeof processImage.dspJsLoadingPromise === 'undefined') {
        processImage.dspJsLoadingPromise = null;
    }

    // --- Helper: Dynamically load dsp.js library ---
    async function ensureDspJsLoadedInternal() {
        const dspJsUrl = 'https://cdnjs.cloudflare.com/ajax/libs/dsp.js/1.0.1/dsp.min.js';

        // If dsp.js is already loaded and good, resolve immediately
        if (typeof dsp !== 'undefined' && typeof dsp.FFT !== 'undefined') {
            return Promise.resolve();
        }

        // If a loading promise already exists, return it
        if (processImage.dspJsLoadingPromise) {
            return processImage.dspJsLoadingPromise;
        }

        // Create a new promise for loading the script
        processImage.dspJsLoadingPromise = new Promise((resolve, reject) => {
            const existingScript = document.querySelector(`script[src="${dspJsUrl}"]`);
            if (existingScript) {
                // If script tag exists, it might be loading or loaded. Poll for dsp.FFT.
                let attempts = 0;
                const interval = setInterval(() => {
                    attempts++;
                    if (typeof dsp !== 'undefined' && typeof dsp.FFT !== 'undefined') {
                        clearInterval(interval);
                        resolve();
                    } else if (attempts > 60) { // Max 6 seconds wait (60 * 100ms)
                        clearInterval(interval);
                        // Clean up promise to allow retry if script failed to make dsp.FFT available
                        processImage.dspJsLoadingPromise = null; 
                        // Optionally remove script if it's problematic:
                        // if (document.head.contains(existingScript)) document.head.removeChild(existingScript);
                        reject(new Error(`dsp.js script was present but dsp.FFT never became available.`));
                    }
                }, 100);
                return;
            }

            // If no script tag exists, create and append it
            const script = document.createElement('script');
            script.src = dspJsUrl;
            script.onload = () => {
                if (typeof dsp !== 'undefined' && typeof dsp.FFT !== 'undefined') {
                    resolve();
                } else {
                    processImage.dspJsLoadingPromise = null; // Allow retry
                    if (document.head.contains(script)) document.head.removeChild(script);
                    reject(new Error(`dsp.js loaded but dsp.FFT is not defined.`));
                }
            };
            script.onerror = (event) => {
                processImage.dspJsLoadingPromise = null; // Allow retry
                if (document.head.contains(script)) document.head.removeChild(script);
                // event is an ErrorEvent, event.message might not be available
                // For script load errors, target.src gives URL, event.type gives 'error'
                reject(new Error(`Failed to load dsp.js from ${dspJsUrl}. Event type: ${event.type}`));
            };
            document.head.appendChild(script);
        });
        return processImage.dspJsLoadingPromise;
    }

    // --- Helper: Calculate the next power of 2 ---
    function nextPowerOf2(n) {
        if (n === 0) return 1;
        n--;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        n++;
        return n;
    }
    
    // --- Error Canvas Utility ---
    function createErrorCanvas(message, width, height) {
        const errCanvas = document.createElement('canvas');
        errCanvas.width = width || 200;
        errCanvas.height = height || 100;
        const ctx = errCanvas.getContext('2d');
        ctx.fillStyle = '#f0f0f0';
        ctx.fillRect(0, 0, errCanvas.width, errCanvas.height);
        ctx.fillStyle = 'red';
        ctx.font = '16px Arial';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        // Simple text wrapping
        const words = message.split(' ');
        let line = '';
        let y = errCanvas.height / 2 - ( (message.length > 30 ? 1 : 0) * 10); // Adjust y for multi-line
        for(let i = 0; i < words.length; i++) {
            const testLine = line + words[i] + ' ';
            if (ctx.measureText(testLine).width > errCanvas.width - 20 && i > 0) {
                ctx.fillText(line, errCanvas.width / 2, y);
                line = words[i] + ' ';
                y += 20; // Line height
            } else {
                line = testLine;
            }
        }
        ctx.fillText(line, errCanvas.width / 2, y);
        return errCanvas;
    }

    // --- Main Image Processing Logic ---
    try {
        await ensureDspJsLoadedInternal();
         // Verify dsp object after loading
        if (typeof dsp === 'undefined' || typeof dsp.FFT === 'undefined') {
            return createErrorCanvas('DSP library loaded but FFT is not available.', originalImg.width, originalImg.height);
        }
    } catch (error) {
        console.error("Error loading DSP library:", error);
        return createErrorCanvas(`Error loading DSP library: ${error.message}`, originalImg.width, originalImg.height);
    }

    if (!originalImg || originalImg.width === 0 || originalImg.height === 0) {
        console.error("Invalid input image provided.");
        return createErrorCanvas("Invalid input image.", 200,100);
    }

    const H = originalImg.height;
    const W = originalImg.width;

    // Use dimensions that are powers of 2 for FFT efficiency
    const fftHeight = nextPowerOf2(H);
    const fftWidth = nextPowerOf2(W);

    // 1. Create a canvas to get image data and convert to grayscale
    const inputCanvas = document.createElement('canvas');
    inputCanvas.width = fftWidth;
    inputCanvas.height = fftHeight;
    const inputCtx = inputCanvas.getContext('2d');
    
    // Draw image and pad with black if fft dimensions are larger
    inputCtx.fillStyle = 'black';
    inputCtx.fillRect(0, 0, fftWidth, fftHeight);
    inputCtx.drawImage(originalImg, 0, 0, W, H);
    
    const imageData = inputCtx.getImageData(0, 0, fftWidth, fftHeight);
    const pixelData = imageData.data;

    // Grayscale data array [fftHeight][fftWidth]
    const grayData = Array(fftHeight).fill(null).map(() => Array(fftWidth).fill(0));
    for (let r = 0; r < fftHeight; r++) {
        for (let c = 0; c < fftWidth; c++) {
            const i = (r * fftWidth + c) * 4;
            // Standard luminance calculation
            grayData[r][c] = 0.299 * pixelData[i] + 0.587 * pixelData[i+1] + 0.114 * pixelData[i+2];
        }
    }

    // 2. Perform 2D FFT
    // `tempComplexData` stores {real, imag} after row FFTs
    const tempComplexData = Array(fftHeight).fill(null).map(() => Array(fftWidth).fill(null));

    // a. Row-wise FFTs
    const rowFFT = new dsp.FFT(fftWidth, 44100); // Sample rate doesn't matter for images
    for (let r = 0; r < fftHeight; r++) {
        const rowSignal = grayData[r];
        const spectrum = rowFFT.forward(rowSignal); // Returns [re, im, re, im, ...]
        for (let c = 0; c < fftWidth; c++) {
            tempComplexData[r][c] = { real: spectrum[c*2], imag: spectrum[c*2+1] };
        }
    }

    // `ftComplex` stores final {real, imag} after column FFTs
    const ftComplex = Array(fftHeight).fill(null).map(() => Array(fftWidth).fill(null));
    
    // b. Column-wise FFTs
    const colFFT = new dsp.FFT(fftHeight, 44100);
    for (let c = 0; c < fftWidth; c++) {
        const colSignalReal = new Float32Array(fftHeight);
        const colSignalImag = new Float32Array(fftHeight);
        for (let r = 0; r < fftHeight; r++) {
            colSignalReal[r] = tempComplexData[r][c].real;
            colSignalImag[r] = tempComplexData[r][c].imag;
        }

        const spectrumReal = colFFT.forward(colSignalReal); // FFT of real parts
        const spectrumImag = colFFT.forward(colSignalImag); // FFT of imaginary parts
        
        for (let r = 0; r < fftHeight; r++) {
            // F(u,v) = FFT_col( FFT_row(f(x,y)) )
            // FFT_col( R_rc + i*I_rc ) = FFT_col(R_rc) + i*FFT_col(I_rc)
            // Let FFT_col(R_rc) = Ar + i*Br for current (r,c)
            // Let FFT_col(I_rc) = Cr + i*Dr for current (r,c)
            // Result is (Ar + i*Br) + i*(Cr + i*Dr) = (Ar - Dr) + i*(Br + Cr)
            const Ar = spectrumReal[r*2];
            const Br = spectrumReal[r*2+1];
            const Cr = spectrumImag[r*2];
            const Dr = spectrumImag[r*2+1];
            ftComplex[r][c] = { real: Ar - Dr, imag: Br + Cr };
        }
    }
    
    // 3. FFT Shift (center the DC component)
    const shiftedFtComplex = Array(fftHeight).fill(null).map(() => Array(fftWidth).fill(null));
    const H_half = Math.floor(fftHeight / 2);
    const W_half = Math.floor(fftWidth / 2);

    for (let r = 0; r < fftHeight; r++) {
        for (let c = 0; c < fftWidth; c++) {
            const shifted_r = (r + H_half) % fftHeight;
            const shifted_c = (c + W_half) % fftWidth;
            shiftedFtComplex[shifted_r][shifted_c] = ftComplex[r][c];
        }
    }

    // 4. Calculate Magnitude Spectrum and apply Logarithmic Scale
    const logMagnitude = Array(fftHeight).fill(null).map(() => Array(fftWidth).fill(0));
    let minLogMag = Infinity;
    let maxLogMag = -Infinity;

    for (let r = 0; r < fftHeight; r++) {
        for (let c = 0; c < fftWidth; c++) {
            const val = shiftedFtComplex[r][c];
            const mag = Math.sqrt(val.real * val.real + val.imag * val.imag);
            const logMagVal = Math.log(1 + mag); // log(1+M) to handle M=0 and compress dynamic range
            logMagnitude[r][c] = logMagVal;
            if (logMagVal < minLogMag) minLogMag = logMagVal;
            if (logMagVal > maxLogMag) maxLogMag = logMagVal;
        }
    }

    // 5. Normalize to 0-255 for display
    const outputCanvas = document.createElement('canvas');
    outputCanvas.width = fftWidth;
    outputCanvas.height = fftHeight;
    const outputCtx = outputCanvas.getContext('2d');
    const outputImageData = outputCtx.createImageData(fftWidth, fftHeight);
    const outputPixelData = outputImageData.data;

    const range = maxLogMag - minLogMag;
    const scale = (range === 0) ? 0 : 255 / range;

    for (let r = 0; r < fftHeight; r++) {
        for (let c = 0; c < fftWidth; c++) {
            const normVal = Math.round((logMagnitude[r][c] - minLogMag) * scale);
            const i = (r * fftWidth + c) * 4;
            outputPixelData[i]   = normVal; // R
            outputPixelData[i+1] = normVal; // G
            outputPixelData[i+2] = normVal; // B
            outputPixelData[i+3] = 255;     // A
        }
    }
    outputCtx.putImageData(outputImageData, 0, 0);

    return outputCanvas;
}

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 Frequency Visualization Filter Effect Tool allows users to visualize the frequency components of images. By processing images through a 2D Fast Fourier Transform (FFT), the tool converts images into a grayscale format, applies a frequency analysis, and then visualizes the results. This tool can be useful for various applications, including analyzing image patterns, improving image processing techniques, or educational purposes in understanding concepts related to frequency analysis in image processing.

Leave a Reply

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