+ {/* Mean */}
+
+ μ
+ {stats.mean.toFixed(1)}
+
+
+
+
+ {/* Standard Deviation */}
+
+ σ
+ {stats.stdDev.toFixed(1)}
+
+
+
+
+ {/* Entropy */}
+
+ H
+ {stats.entropy.toFixed(2)}
+ bits
+
+
+
+
+ {/* Min/Max */}
+
+ Range
+ {stats.min}–{stats.max}
+
+
+
+
+ {/* Variance (more compact) */}
+
+ σ²
+ {stats.variance.toFixed(0)}
+
+
+
+
+ {/* Pixel Count */}
+
+ Pixels
+ {formatPixelCount(stats.pixelCount)}
+
+
+ {/* Original indicator */}
+ {showOriginal && (
+
+ ORIGINAL
+
+ )}
+
+ );
+}
+
+export default StatusBar;
diff --git a/src/components/StatusBar/index.ts b/src/components/StatusBar/index.ts
new file mode 100644
index 0000000..5d9ab0d
--- /dev/null
+++ b/src/components/StatusBar/index.ts
@@ -0,0 +1,2 @@
+export { StatusBar } from './StatusBar';
+export { default } from './StatusBar';
diff --git a/src/components/index.ts b/src/components/index.ts
index 3aa45f5..cb00d27 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -16,3 +16,4 @@ export { FormulaPanel } from './FormulaPanel';
export { LaTeXFormula } from './LaTeXFormula';
export { ErrorBoundary } from './ErrorBoundary';
export { LoadingSkeleton, ProcessingIndicator } from './LoadingSkeleton';
+export { StatusBar } from './StatusBar';
diff --git a/src/types/index.ts b/src/types/index.ts
index 08f399f..b47e75b 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -114,7 +114,9 @@ export type FilterType =
| 'closing'
// Custom Operations
| 'customFormula'
- | 'customKernel';
+ | 'customKernel'
+ // Frequency Domain
+ | 'fftSpectrum';
// =============================================================================
// Application State Types
diff --git a/src/utils/fft.ts b/src/utils/fft.ts
new file mode 100644
index 0000000..5c0e70d
--- /dev/null
+++ b/src/utils/fft.ts
@@ -0,0 +1,309 @@
+/**
+ * ImageVisLab - Fast Fourier Transform (FFT)
+ *
+ * Implementation of 2D FFT for image frequency analysis.
+ * Uses Cooley-Tukey radix-2 algorithm with zero-padding.
+ *
+ * @module fft
+ * @author ImageVisLab Contributors
+ * @license MIT
+ */
+
+// =============================================================================
+// Types
+// =============================================================================
+
+/**
+ * Complex number representation.
+ */
+export interface Complex {
+ re: number;
+ im: number;
+}
+
+// =============================================================================
+// Complex Number Operations
+// =============================================================================
+
+/**
+ * Creates a complex number.
+ */
+function complex(re: number, im: number = 0): Complex {
+ return { re, im };
+}
+
+/**
+ * Adds two complex numbers.
+ */
+function add(a: Complex, b: Complex): Complex {
+ return { re: a.re + b.re, im: a.im + b.im };
+}
+
+/**
+ * Subtracts two complex numbers.
+ */
+function subtract(a: Complex, b: Complex): Complex {
+ return { re: a.re - b.re, im: a.im - b.im };
+}
+
+/**
+ * Multiplies two complex numbers.
+ */
+function multiply(a: Complex, b: Complex): Complex {
+ return {
+ re: a.re * b.re - a.im * b.im,
+ im: a.re * b.im + a.im * b.re,
+ };
+}
+
+/**
+ * Calculates the magnitude of a complex number.
+ */
+function magnitude(c: Complex): number {
+ return Math.sqrt(c.re * c.re + c.im * c.im);
+}
+
+// =============================================================================
+// FFT Core Algorithm
+// =============================================================================
+
+/**
+ * Computes the next power of 2 >= n.
+ */
+function nextPowerOf2(n: number): number {
+ return Math.pow(2, Math.ceil(Math.log2(n)));
+}
+
+/**
+ * 1D FFT using Cooley-Tukey radix-2 algorithm.
+ * Input length must be a power of 2.
+ *
+ * @param data - Array of complex numbers
+ * @returns FFT of the input data
+ */
+function fft1D(data: Complex[]): Complex[] {
+ const n = data.length;
+
+ // Base case
+ if (n <= 1) return data;
+
+ // Verify power of 2
+ if (n & (n - 1)) {
+ throw new Error('FFT length must be a power of 2');
+ }
+
+ // Split into even and odd
+ const even: Complex[] = [];
+ const odd: Complex[] = [];
+ for (let i = 0; i < n; i += 2) {
+ even.push(data[i]);
+ odd.push(data[i + 1]);
+ }
+
+ // Recursive FFT
+ const evenFFT = fft1D(even);
+ const oddFFT = fft1D(odd);
+
+ // Combine
+ const result: Complex[] = new Array(n);
+ for (let k = 0; k < n / 2; k++) {
+ // Twiddle factor: e^(-2πik/n)
+ const angle = -2 * Math.PI * k / n;
+ const twiddle = complex(Math.cos(angle), Math.sin(angle));
+ const t = multiply(twiddle, oddFFT[k]);
+
+ result[k] = add(evenFFT[k], t);
+ result[k + n / 2] = subtract(evenFFT[k], t);
+ }
+
+ return result;
+}
+
+/**
+ * 2D FFT by applying 1D FFT to rows then columns.
+ *
+ * @param matrix - 2D array of grayscale values (0-255)
+ * @returns 2D array of complex FFT coefficients
+ */
+function fft2D(matrix: number[][]): Complex[][] {
+ const rows = matrix.length;
+ const cols = matrix[0].length;
+
+ // Pad to power of 2
+ const paddedRows = nextPowerOf2(rows);
+ const paddedCols = nextPowerOf2(cols);
+
+ // Convert to complex and pad with zeros
+ let complexMatrix: Complex[][] = [];
+ for (let i = 0; i < paddedRows; i++) {
+ complexMatrix[i] = [];
+ for (let j = 0; j < paddedCols; j++) {
+ if (i < rows && j < cols) {
+ complexMatrix[i][j] = complex(matrix[i][j]);
+ } else {
+ complexMatrix[i][j] = complex(0);
+ }
+ }
+ }
+
+ // FFT on rows
+ for (let i = 0; i < paddedRows; i++) {
+ complexMatrix[i] = fft1D(complexMatrix[i]);
+ }
+
+ // FFT on columns
+ for (let j = 0; j < paddedCols; j++) {
+ const column: Complex[] = [];
+ for (let i = 0; i < paddedRows; i++) {
+ column.push(complexMatrix[i][j]);
+ }
+ const fftColumn = fft1D(column);
+ for (let i = 0; i < paddedRows; i++) {
+ complexMatrix[i][j] = fftColumn[i];
+ }
+ }
+
+ return complexMatrix;
+}
+
+/**
+ * Shifts the zero-frequency component to the center.
+ * This makes the spectrum easier to interpret visually.
+ *
+ * @param spectrum - 2D FFT result
+ * @returns Shifted spectrum
+ */
+function fftShift(spectrum: Complex[][]): Complex[][] {
+ const rows = spectrum.length;
+ const cols = spectrum[0].length;
+ const halfRows = Math.floor(rows / 2);
+ const halfCols = Math.floor(cols / 2);
+
+ const shifted: Complex[][] = [];
+ for (let i = 0; i < rows; i++) {
+ shifted[i] = [];
+ for (let j = 0; j < cols; j++) {
+ const srcI = (i + halfRows) % rows;
+ const srcJ = (j + halfCols) % cols;
+ shifted[i][j] = spectrum[srcI][srcJ];
+ }
+ }
+
+ return shifted;
+}
+
+// =============================================================================
+// Image Processing Functions
+// =============================================================================
+
+/**
+ * Converts ImageData to grayscale matrix.
+ */
+function imageToGrayscale(imageData: ImageData): number[][] {
+ const { data, width, height } = imageData;
+ const matrix: number[][] = [];
+
+ for (let y = 0; y < height; y++) {
+ matrix[y] = [];
+ for (let x = 0; x < width; x++) {
+ const idx = (y * width + x) * 4;
+ // Luminosity method
+ const gray = 0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2];
+ matrix[y][x] = gray;
+ }
+ }
+
+ return matrix;
+}
+
+/**
+ * Computes the magnitude spectrum from FFT result.
+ * Applies log scaling for better visualization.
+ *
+ * @param spectrum - 2D FFT coefficients
+ * @returns ImageData with magnitude spectrum
+ */
+function spectrumToImageData(spectrum: Complex[][], originalWidth: number, originalHeight: number): ImageData {
+ const rows = spectrum.length;
+ const cols = spectrum[0].length;
+
+ // Calculate magnitudes with log scaling
+ const magnitudes: number[][] = [];
+ let maxMag = 0;
+
+ for (let i = 0; i < rows; i++) {
+ magnitudes[i] = [];
+ for (let j = 0; j < cols; j++) {
+ // Log scaling: log(1 + |F(u,v)|)
+ const mag = Math.log(1 + magnitude(spectrum[i][j]));
+ magnitudes[i][j] = mag;
+ if (mag > maxMag) maxMag = mag;
+ }
+ }
+
+ // Normalize and create ImageData (crop to original size)
+ const imageData = new ImageData(originalWidth, originalHeight);
+ const { data } = imageData;
+
+ // Calculate offsets to center the spectrum on the original image size
+ const offsetY = Math.floor((rows - originalHeight) / 2);
+ const offsetX = Math.floor((cols - originalWidth) / 2);
+
+ for (let y = 0; y < originalHeight; y++) {
+ for (let x = 0; x < originalWidth; x++) {
+ const srcY = y + offsetY;
+ const srcX = x + offsetX;
+
+ let normalized = 0;
+ if (srcY >= 0 && srcY < rows && srcX >= 0 && srcX < cols && maxMag > 0) {
+ normalized = (magnitudes[srcY][srcX] / maxMag) * 255;
+ }
+
+ const idx = (y * originalWidth + x) * 4;
+ data[idx] = normalized; // R
+ data[idx + 1] = normalized; // G
+ data[idx + 2] = normalized; // B
+ data[idx + 3] = 255; // A
+ }
+ }
+
+ return imageData;
+}
+
+// =============================================================================
+// Public API
+// =============================================================================
+
+/**
+ * Applies FFT to an image and returns the magnitude spectrum.
+ *
+ * The spectrum is:
+ * - Shifted so low frequencies are at the center
+ * - Log-scaled for better visualization
+ * - Normalized to 0-255 range
+ *
+ * @param imageData - Input image
+ * @returns Magnitude spectrum as ImageData
+ */
+export function applyFFTSpectrum(imageData: ImageData): ImageData {
+ const { width, height } = imageData;
+
+ // Convert to grayscale matrix
+ const grayscale = imageToGrayscale(imageData);
+
+ // Apply 2D FFT
+ const spectrum = fft2D(grayscale);
+
+ // Shift to center low frequencies
+ const shifted = fftShift(spectrum);
+
+ // Convert to displayable image
+ return spectrumToImageData(shifted, width, height);
+}
+
+/**
+ * Gets the formula description for FFT.
+ */
+export function getFFTFormula(): string {
+ return 'F(u,v) = \\sum_{x=0}^{M-1} \\sum_{y=0}^{N-1} f(x,y) \\cdot e^{-j2\\pi(ux/M + vy/N)}';
+}
diff --git a/src/utils/imageStatistics.ts b/src/utils/imageStatistics.ts
new file mode 100644
index 0000000..a8c23bd
--- /dev/null
+++ b/src/utils/imageStatistics.ts
@@ -0,0 +1,141 @@
+/**
+ * ImageVisLab - Image Statistics Utilities
+ *
+ * Functions to calculate statistical measures of image data including
+ * mean, variance, standard deviation, and entropy.
+ *
+ * @module imageStatistics
+ * @author ImageVisLab Contributors
+ * @license MIT
+ */
+
+// =============================================================================
+// Types
+// =============================================================================
+
+export interface ImageStatistics {
+ /** Mean intensity value (0-255) */
+ mean: number;
+ /** Variance of intensity values */
+ variance: number;
+ /** Standard deviation of intensity values */
+ stdDev: number;
+ /** Entropy in bits (0-8 for 8-bit images) */
+ entropy: number;
+ /** Minimum intensity value */
+ min: number;
+ /** Maximum intensity value */
+ max: number;
+ /** Total number of pixels */
+ pixelCount: number;
+}
+
+// =============================================================================
+// Helper Functions
+// =============================================================================
+
+/**
+ * Converts RGB to grayscale using luminosity method.
+ * Uses ITU-R BT.601 standard weights.
+ *
+ * @param r - Red channel (0-255)
+ * @param g - Green channel (0-255)
+ * @param b - Blue channel (0-255)
+ * @returns Grayscale value (0-255)
+ */
+function rgbToGrayscale(r: number, g: number, b: number): number {
+ return Math.round(0.299 * r + 0.587 * g + 0.114 * b);
+}
+
+// =============================================================================
+// Main Function
+// =============================================================================
+
+/**
+ * Calculates comprehensive statistics for an image.
+ *
+ * Computes:
+ * - **Mean (μ)**: Average intensity value
+ * - **Variance (σ²)**: Measure of intensity spread
+ * - **Standard Deviation (σ)**: Square root of variance
+ * - **Entropy (H)**: Information content in bits
+ * - **Min/Max**: Intensity range
+ *
+ * Entropy formula: H = -Σ p(i) * log₂(p(i))
+ * Where p(i) is the probability of intensity level i
+ *
+ * @param imageData - The image data to analyze
+ * @returns Object containing all statistics
+ */
+export function calculateImageStatistics(imageData: ImageData): ImageStatistics {
+ const { data, width, height } = imageData;
+ const pixelCount = width * height;
+
+ // Initialize histogram for entropy calculation (256 levels for 8-bit)
+ const histogram = new Uint32Array(256);
+
+ // First pass: calculate histogram, sum, min, max
+ let sum = 0;
+ let min = 255;
+ let max = 0;
+
+ for (let i = 0; i < data.length; i += 4) {
+ const gray = rgbToGrayscale(data[i], data[i + 1], data[i + 2]);
+
+ histogram[gray]++;
+ sum += gray;
+
+ if (gray < min) min = gray;
+ if (gray > max) max = gray;
+ }
+
+ // Calculate mean
+ const mean = sum / pixelCount;
+
+ // Second pass: calculate variance
+ let varianceSum = 0;
+ for (let i = 0; i < data.length; i += 4) {
+ const gray = rgbToGrayscale(data[i], data[i + 1], data[i + 2]);
+ const diff = gray - mean;
+ varianceSum += diff * diff;
+ }
+ const variance = varianceSum / pixelCount;
+ const stdDev = Math.sqrt(variance);
+
+ // Calculate entropy from histogram
+ // H = -Σ p(i) * log₂(p(i)) for all i where p(i) > 0
+ let entropy = 0;
+ for (let i = 0; i < 256; i++) {
+ if (histogram[i] > 0) {
+ const probability = histogram[i] / pixelCount;
+ entropy -= probability * Math.log2(probability);
+ }
+ }
+
+ return {
+ mean: Math.round(mean * 100) / 100,
+ variance: Math.round(variance * 100) / 100,
+ stdDev: Math.round(stdDev * 100) / 100,
+ entropy: Math.round(entropy * 100) / 100,
+ min,
+ max,
+ pixelCount,
+ };
+}
+
+/**
+ * Formats pixel count in a human-readable way.
+ * Examples: 1000 -> "1K", 1500000 -> "1.5M"
+ *
+ * @param count - Number of pixels
+ * @returns Formatted string
+ */
+export function formatPixelCount(count: number): string {
+ if (count >= 1_000_000) {
+ return `${(count / 1_000_000).toFixed(1)}M`;
+ }
+ if (count >= 1_000) {
+ return `${(count / 1_000).toFixed(0)}K`;
+ }
+ return count.toString();
+}
diff --git a/src/workers/imageWorker.ts b/src/workers/imageWorker.ts
index 21fb995..0a78100 100644
--- a/src/workers/imageWorker.ts
+++ b/src/workers/imageWorker.ts
@@ -53,6 +53,8 @@ import {
applySwapChannels,
} from '../utils/colorFilters';
+import { applyFFTSpectrum } from '../utils/fft';
+
import type { FilterType, FilterParams } from '../types';
// =============================================================================
@@ -147,6 +149,10 @@ function processImage(
case 'swapChannels':
return applySwapChannels(imageData);
+ // Frequency Domain
+ case 'fftSpectrum':
+ return applyFFTSpectrum(imageData);
+
// No filter
case 'none':
default: