From a66ae400f7626454cd6f144a36b1457521aa0144 Mon Sep 17 00:00:00 2001 From: medeirosdev Date: Thu, 25 Dec 2025 09:58:40 -0300 Subject: [PATCH 1/2] feat: add status bar with image statistics (mean, variance, entropy) --- src/App.tsx | 9 +- src/components/StatusBar/StatusBar.css | 159 +++++++++++++++++++++++++ src/components/StatusBar/StatusBar.tsx | 121 +++++++++++++++++++ src/components/StatusBar/index.ts | 2 + src/components/index.ts | 1 + src/utils/imageStatistics.ts | 141 ++++++++++++++++++++++ 6 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 src/components/StatusBar/StatusBar.css create mode 100644 src/components/StatusBar/StatusBar.tsx create mode 100644 src/components/StatusBar/index.ts create mode 100644 src/utils/imageStatistics.ts diff --git a/src/App.tsx b/src/App.tsx index abfb70e..d859830 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,7 @@ */ import { useState, useCallback, useRef, useMemo, useEffect } from 'react'; -import { ImageCanvas, Sidebar, PixelInspector, FormulaPanel, LoadingSkeleton, ProcessingIndicator } from './components'; +import { ImageCanvas, Sidebar, PixelInspector, FormulaPanel, LoadingSkeleton, ProcessingIndicator, StatusBar } from './components'; import { useHistory, useImageWorker } from './hooks'; import type { FilterType, FilterParams } from './types'; import { @@ -865,6 +865,13 @@ function App() { originalHistogramData={originalHistogramData} hasImage={!!originalImage} /> + + {/* Status Bar: Image Statistics */} + ); diff --git a/src/components/StatusBar/StatusBar.css b/src/components/StatusBar/StatusBar.css new file mode 100644 index 0000000..a2e0732 --- /dev/null +++ b/src/components/StatusBar/StatusBar.css @@ -0,0 +1,159 @@ +/** + * StatusBar - Styles + * + * Compact horizontal bar displaying image statistics. + * Dark theme with subtle separators and hover effects. + */ + +/* ============================================================================= + Container + ============================================================================= */ + +.status-bar { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-xs) var(--space-lg); + background: var(--color-bg-secondary); + border-top: 1px solid var(--color-border); + height: 32px; + min-height: 32px; + font-size: 0.8125rem; + font-family: var(--font-mono); + user-select: none; +} + +.status-bar--empty { + justify-content: center; +} + +.status-bar__placeholder { + color: var(--color-text-muted); + font-family: var(--font-sans); + font-style: italic; + font-size: 0.75rem; +} + +/* ============================================================================= + Items + ============================================================================= */ + +.status-bar__item { + display: flex; + align-items: center; + gap: var(--space-xs); + padding: 2px 6px; + border-radius: var(--radius-sm); + transition: background-color 0.15s ease; + cursor: default; +} + +.status-bar__item:hover { + background: var(--color-bg-tertiary); +} + +.status-bar__label { + color: var(--color-accent-primary); + font-weight: 600; + font-size: 0.875rem; +} + +.status-bar__value { + color: var(--color-text-primary); + font-weight: 500; +} + +.status-bar__unit { + color: var(--color-text-muted); + font-size: 0.6875rem; + margin-left: 1px; +} + +/* ============================================================================= + Dividers + ============================================================================= */ + +.status-bar__divider { + width: 1px; + height: 14px; + background: var(--color-border); + opacity: 0.5; +} + +/* ============================================================================= + Special Items + ============================================================================= */ + +/* Entropy - highlight with gradient */ +.status-bar__item--entropy .status-bar__label { + background: linear-gradient(135deg, #10b981, #3b82f6); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* Range - subtle different color */ +.status-bar__item--range .status-bar__label { + color: var(--color-text-secondary); + font-size: 0.6875rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Pixels - muted */ +.status-bar__item--pixels .status-bar__label { + color: var(--color-text-secondary); + font-size: 0.6875rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* ============================================================================= + Original Indicator + ============================================================================= */ + +.status-bar__original-indicator { + margin-left: auto; + padding: 2px 8px; + background: rgba(245, 158, 11, 0.15); + border: 1px solid rgba(245, 158, 11, 0.3); + border-radius: var(--radius-sm); + color: #f59e0b; + font-size: 0.625rem; + font-weight: 700; + letter-spacing: 1px; + animation: pulse 1.5s ease-in-out infinite; +} + +/* ============================================================================= + Responsive + ============================================================================= */ + +@media (max-width: 900px) { + .status-bar { + gap: var(--space-xs); + padding: var(--space-xs) var(--space-sm); + font-size: 0.75rem; + } + + .status-bar__item { + padding: 2px 4px; + gap: 2px; + } + + /* Hide less important items on small screens */ + .status-bar__item--pixels, + .status-bar__item--range { + display: none; + } + + .status-bar__divider:nth-last-child(-n+4) { + display: none; + } +} + +@media (max-width: 600px) { + .status-bar__unit { + display: none; + } +} \ No newline at end of file diff --git a/src/components/StatusBar/StatusBar.tsx b/src/components/StatusBar/StatusBar.tsx new file mode 100644 index 0000000..a86102c --- /dev/null +++ b/src/components/StatusBar/StatusBar.tsx @@ -0,0 +1,121 @@ +/** + * StatusBar - Image Statistics Display + * + * Displays real-time statistics about the current image + * in a compact bar at the bottom of the application. + * + * @module StatusBar + * @author ImageVisLab Contributors + * @license MIT + */ + +import { useMemo } from 'react'; +import { calculateImageStatistics, formatPixelCount, type ImageStatistics } from '../../utils/imageStatistics'; +import './StatusBar.css'; + +// ============================================================================= +// Types +// ============================================================================= + +interface StatusBarProps { + /** The image data to calculate statistics from */ + imageData: ImageData | null; + /** Whether to show original image stats (when Space is held) */ + originalData?: ImageData | null; + /** Whether currently showing original */ + showOriginal?: boolean; +} + +// ============================================================================= +// Component +// ============================================================================= + +/** + * StatusBar displays image statistics in a compact horizontal bar. + * + * Statistics shown: + * - μ (Mean): Average intensity + * - σ (Std Dev): Intensity spread + * - H (Entropy): Information content in bits + * - Min/Max: Intensity range + * - Pixel count: Total pixels + */ +export function StatusBar({ imageData, originalData, showOriginal }: StatusBarProps) { + // Calculate statistics (memoized for performance) + const stats = useMemo(() => { + const data = showOriginal && originalData ? originalData : imageData; + if (!data) return null; + return calculateImageStatistics(data); + }, [imageData, originalData, showOriginal]); + + // Don't render if no image + if (!stats) { + return ( +
+ + Carregue uma imagem para ver estatísticas + +
+ ); + } + + return ( +
+ {/* 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/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(); +} From 48b534f6c9bd9f08b8e95d98bdcc8cc4104b155f Mon Sep 17 00:00:00 2001 From: medeirosdev Date: Thu, 25 Dec 2025 10:18:25 -0300 Subject: [PATCH 2/2] feat: add FFT spectrum visualization and image statistics - Add 2D FFT with Cooley-Tukey algorithm (fft.ts) - Add Frequency Domain category in Sidebar - Add FFT formula display in FormulaPanel - Add StatusBar with real-time image statistics (mean, variance, entropy) - Integrate FFT in App.tsx and Web Worker for background processing --- src/App.tsx | 4 + src/components/FormulaPanel/FormulaPanel.tsx | 14 + src/components/Sidebar/Sidebar.tsx | 12 + src/types/index.ts | 4 +- src/utils/fft.ts | 309 +++++++++++++++++++ src/workers/imageWorker.ts | 6 + 6 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 src/utils/fft.ts diff --git a/src/App.tsx b/src/App.tsx index d859830..890e44a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -52,6 +52,7 @@ import { applySepia, applySwapChannels, } from './utils/colorFilters'; +import { applyFFTSpectrum } from './utils/fft'; import './App.css'; // ============================================================================= @@ -137,6 +138,9 @@ function processImageSync( return applySepia(imageData); case 'swapChannels': return applySwapChannels(imageData); + // Frequency Domain + case 'fftSpectrum': + return applyFFTSpectrum(imageData); case 'none': default: return imageData; diff --git a/src/components/FormulaPanel/FormulaPanel.tsx b/src/components/FormulaPanel/FormulaPanel.tsx index 8a49399..cf45bd5 100644 --- a/src/components/FormulaPanel/FormulaPanel.tsx +++ b/src/components/FormulaPanel/FormulaPanel.tsx @@ -322,6 +322,20 @@ const getFormulaInfo = ( ], }; + // Frequency Domain + case 'fftSpectrum': + return { + name: '2D Fourier Transform (FFT)', + latex: 'F(u,v) = \\sum_{x=0}^{M-1} \\sum_{y=0}^{N-1} f(x,y) \\cdot e^{-j2\\pi(ux/M + vy/N)}', + description: 'Decomposes image into frequency components. Center shows low frequencies (smooth areas), edges show high frequencies (details/edges).', + variables: [ + { symbol: 'F(u,v)', meaning: 'Frequency component' }, + { symbol: 'f(x,y)', meaning: 'Spatial pixel value' }, + { symbol: 'u, v', meaning: 'Frequency coordinates' }, + { symbol: 'M, N', meaning: 'Image dimensions' }, + ], + }; + case 'none': default: return { diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index d9d31ee..5af12d0 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -288,6 +288,18 @@ const FILTER_CATEGORIES: FilterCategory[] = [ }, ], }, + { + id: 'frequency', + name: 'Frequency Domain', + filters: [ + { + id: 'fftSpectrum', + name: 'FFT Spectrum', + description: 'Visualize frequency components', + formula: 'F(u,v) = \\sum f \\cdot e^{-j2\\pi(ux/M + vy/N)}', + }, + ], + }, ]; // ============================================================================= 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/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: