(0);
+
+ React.useEffect(() => {
+ if (isProcessing) {
+ // Show immediately when processing starts
+ setVisible(true);
+ showTimeRef.current = Date.now();
+ } else if (visible) {
+ // Calculate how long we've been visible
+ const elapsed = Date.now() - showTimeRef.current;
+ const remaining = Math.max(0, minDisplayTime - elapsed);
+
+ // Only hide after minimum display time
+ const timeout = setTimeout(() => {
+ setVisible(false);
+ }, remaining);
+
+ return () => clearTimeout(timeout);
+ }
+ }, [isProcessing, visible, minDisplayTime]);
+
+ if (!visible) return null;
return (
diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx
index 551eb74..d9d31ee 100644
--- a/src/components/Sidebar/Sidebar.tsx
+++ b/src/components/Sidebar/Sidebar.tsx
@@ -207,6 +207,69 @@ const FILTER_CATEGORIES: FilterCategory[] = [
},
],
},
+ {
+ id: 'edge',
+ name: 'Edge Detection',
+ filters: [
+ {
+ id: 'sobelX',
+ name: 'Sobel X',
+ description: 'Detects vertical edges',
+ formula: 'G_x = \\begin{bmatrix} -1 & 0 & 1 \\\\ -2 & 0 & 2 \\\\ -1 & 0 & 1 \\end{bmatrix}',
+ },
+ {
+ id: 'sobelY',
+ name: 'Sobel Y',
+ description: 'Detects horizontal edges',
+ formula: 'G_y = \\begin{bmatrix} -1 & -2 & -1 \\\\ 0 & 0 & 0 \\\\ 1 & 2 & 1 \\end{bmatrix}',
+ },
+ {
+ id: 'sobelMagnitude',
+ name: 'Sobel Magnitude',
+ description: 'Combined edge magnitude',
+ formula: 'G = \\sqrt{G_x^2 + G_y^2}',
+ },
+ ],
+ },
+ {
+ id: 'noise',
+ name: 'Noise Reduction',
+ filters: [
+ {
+ id: 'median',
+ name: 'Median Filter',
+ description: 'Removes salt-and-pepper noise',
+ formula: 's = \\text{median}(N)',
+ params: [
+ { key: 'kernelSize', label: 'Size', min: 3, max: 7, step: 2 },
+ ],
+ },
+ ],
+ },
+ {
+ id: 'color',
+ name: 'Color Filters',
+ filters: [
+ {
+ id: 'grayscale',
+ name: 'Grayscale',
+ description: 'Converts to black and white',
+ formula: 'Y = 0.299R + 0.587G + 0.114B',
+ },
+ {
+ id: 'sepia',
+ name: 'Sepia',
+ description: 'Vintage brownish tone',
+ formula: '\\begin{bmatrix} 0.393 & 0.769 & 0.189 \\\\ 0.349 & 0.686 & 0.168 \\\\ 0.272 & 0.534 & 0.131 \\end{bmatrix}',
+ },
+ {
+ id: 'swapChannels',
+ name: 'Swap RGB',
+ description: 'Rotates color channels',
+ formula: 'R \\rightarrow G \\rightarrow B \\rightarrow R',
+ },
+ ],
+ },
{
id: 'custom',
name: 'Custom Operations',
@@ -246,9 +309,9 @@ export const Sidebar: React.FC = ({
canRedo,
hasImage,
}) => {
- // Track which categories are expanded
+ // Track which categories are expanded (all collapsed by default)
const [expandedCategories, setExpandedCategories] = useState>(
- new Set(['point', 'spatial'])
+ new Set()
);
// Track About modal visibility
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index eee6367..06d734e 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -1,2 +1,3 @@
export { useHistory } from './useHistory';
-export type { HistoryState } from './useHistory';
+export { useImageWorker } from './useImageWorker';
+
diff --git a/src/hooks/useImageWorker.ts b/src/hooks/useImageWorker.ts
new file mode 100644
index 0000000..83e1c2d
--- /dev/null
+++ b/src/hooks/useImageWorker.ts
@@ -0,0 +1,164 @@
+/**
+ * ImageVisLab - useImageWorker Hook
+ *
+ * React hook for managing image processing Web Worker.
+ * Handles worker lifecycle, request queuing, and cleanup.
+ *
+ * @module useImageWorker
+ * @author ImageVisLab Contributors
+ * @license MIT
+ */
+
+import { useCallback, useEffect, useRef, useState } from 'react';
+import type { FilterType, FilterParams } from '../types';
+import type { WorkerRequest, WorkerResponse } from '../workers/imageWorker';
+
+// =============================================================================
+// Types
+// =============================================================================
+
+interface UseImageWorkerOptions {
+ /** Whether to use the worker (can disable for debugging) */
+ enabled?: boolean;
+}
+
+interface UseImageWorkerResult {
+ /** Process an image with a filter */
+ processImage: (
+ imageData: ImageData,
+ filter: FilterType,
+ params: FilterParams
+ ) => Promise;
+ /** Whether processing is in progress */
+ isProcessing: boolean;
+ /** Last processing time in ms */
+ lastProcessingTime: number | null;
+ /** Whether worker is ready */
+ isReady: boolean;
+ /** Any error that occurred */
+ error: string | null;
+}
+
+// =============================================================================
+// Hook Implementation
+// =============================================================================
+
+export function useImageWorker(
+ options: UseImageWorkerOptions = {}
+): UseImageWorkerResult {
+ const { enabled = true } = options;
+
+ const workerRef = useRef(null);
+ const requestIdRef = useRef(0);
+ const pendingRequestsRef = useRef