'use client' import { useState, useCallback, useEffect } from 'react' import { useAppStore, useImageStore, useUIStore } from '@/store' import { KernelSelector } from '@/components/kernel/KernelSelector' import { PlaybackControls } from '@/components/controls/PlaybackControls' import { OverlayControls, PaddingControls, KernelInfoPanel } from '@/components/controls/OverlayControls' import { ConvolutionCanvas } from '@/components/canvas/ConvolutionCanvas' import { StaticConvolutionView } from '@/components/canvas/StaticConvolutionView' import { SplitView } from '@/components/canvas/SplitView' import { ImageCanvas } from '@/components/canvas/ImageCanvas' import { Button, Card, CardContent, Badge } from '@/components/ui' import { useImageLoader, useDragAndDrop } from '@/hooks/useImageLoader' import { useConvolution } from '@/hooks/useConvolution' import { cn, formatNumber } from '@/utils/helpers' import { Upload, Image as ImageIcon, Download, RotateCcw, HelpCircle, Settings, ChevronLeft, ChevronRight, Keyboard, } from 'lucide-react' export default function Home() { const { originalImageData, processedImageData, selectedKernel, convolution, settings, isLoading, error, showHelp, showSettings, setOriginalImage, setError, toggleHelp, toggleSettings, resetConvolution, } = useAppStore() const { runConvolution, pause, resume, stop, step, reset, isRunning, isPaused, canRun, toggleStepMode } = useConvolution() const { fileInputRef, openFileDialog, handleFileSelect } = useImageLoader() const { handleDragOver, handleDragLeave, handleDrop, isDragging } = useDragAndDrop((image, imageData) => { setOriginalImage(image, imageData) resetConvolution() }) const [showComparison, setShowComparison] = useState(false) const [sidebarWidth, setSidebarWidth] = useState(320) const [inspectedPosition, setInspectedPosition] = useState<{ x: number; y: number } | null>(null) const [showShortcuts, setShowShortcuts] = useState(false) const handleInspectClick = useCallback((x: number, y: number) => { setInspectedPosition({ x, y }) }, []) // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Don't trigger shortcuts when typing in inputs if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { return } switch (e.code) { case 'Space': e.preventDefault() if (isRunning) pause() else if (canRun) runConvolution() else if (isPaused) resume() break case 'ArrowRight': e.preventDefault() step() break case 'ArrowLeft': e.preventDefault() // Step back not implemented yet break case 'Equal': case 'NumpadAdd': if (e.ctrlKey || e.metaKey) { e.preventDefault() const newZoom = Math.min(20, settings.zoomLevel * 1.2) useAppStore.getState().setZoomLevel(newZoom) } break case 'Minus': case 'NumpadSubtract': if (e.ctrlKey || e.metaKey) { e.preventDefault() const newZoom = Math.max(0.1, settings.zoomLevel / 1.2) useAppStore.getState().setZoomLevel(newZoom) } break case 'Digit0': if (e.ctrlKey || e.metaKey) { e.preventDefault() useAppStore.getState().setZoomLevel(1) useAppStore.getState().setPanOffset({ x: 0, y: 0 }) } break case 'KeyR': if (e.ctrlKey || e.metaKey) { e.preventDefault() reset() resetConvolution() } break case 'KeyS': if (e.ctrlKey || e.metaKey) { e.preventDefault() toggleStepMode(!settings.stepMode) } break case 'Slash': if (e.shiftKey) { e.preventDefault() setShowShortcuts(!showShortcuts) } break } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [isRunning, isPaused, canRun, settings.zoomLevel, settings.stepMode, runConvolution, pause, resume, step, reset, resetConvolution, toggleStepMode]) const handleImageLoad = useCallback((data: { image: HTMLImageElement; imageData: ImageData }) => { setOriginalImage(data.image, data.imageData) resetConvolution() }, [setOriginalImage, resetConvolution]) const handleDownload = useCallback(() => { if (!processedImageData) return const canvas = document.createElement('canvas') canvas.width = processedImageData.width canvas.height = processedImageData.height const ctx = canvas.getContext('2d') if (!ctx) return ctx.putImageData(processedImageData, 0, 0) const link = document.createElement('a') link.download = 'convolved-image.png' link.href = canvas.toDataURL('image/png') link.click() }, [processedImageData]) const handleReset = useCallback(() => { reset() resetConvolution() }, [reset, resetConvolution]) if (isLoading) { return (
) } return (
{/* Header */}

Kernel Convolution Visualizer

Educational Tool
{/* Main Content */}
{/* Left Sidebar - Controls */} {/* Resizer */}
{ const startX = e.clientX const startWidth = sidebarWidth const onMouseMove = (e: MouseEvent) => { setSidebarWidth(Math.max(280, Math.min(400, startWidth + (e.clientX - startX)))) } const onMouseUp = () => { document.removeEventListener('mousemove', onMouseMove) document.removeEventListener('mouseup', onMouseUp) } document.addEventListener('mousemove', onMouseMove) document.addEventListener('mouseup', onMouseUp) }} >
{/* Center - Visualization */}
{originalImageData ? ( <> {/* Convolution Visualization */}
{/* Comparison View Toggle */}
{selectedKernel && ( {selectedKernel.name} ({selectedKernel.size}×{selectedKernel.size}) )} {convolution.status !== 'idle' && ( {convolution.status} )}
) : (

Upload an image to begin

Drag & drop or click the upload area

)} {/* Comparison View */} {showComparison && processedImageData && (
)}
{/* Right Sidebar - Info Panels */}
{/* Error Toast */} {error && (
{error}
)} {/* Help Modal */} {showHelp && (

How to Use

1. Upload an Image

Drag & drop or click the upload area in the left sidebar.

2. Select a Kernel

Choose from predefined kernels (Blur, Sharpen, Edge Detection, etc.) or create your own custom kernel.

3. Watch the Convolution

Press Play to see the kernel move across the image pixel by pixel. The visualization shows:

  • Current image patch under the kernel
  • Element-wise multiplication of kernel and pixel values
  • Summation that produces the output pixel

4. Control Playback

  • Play/Pause/Step through the animation
  • Adjust animation speed
  • Zoom and pan for pixel-level inspection

5. Compare Results

Click “Show Comparison” to see side-by-side or split view of original vs processed image.

6. Experiment

  • Try different padding strategies (Zero, Reflect, Replicate, Wrap)
  • Toggle overlays to see different visualization aspects
  • Create custom kernels with the editor
  • Download the processed image
)} {/* Settings Modal */} {showSettings && (

Settings

)} {/* Shortcuts Modal */} {showShortcuts && (

Keyboard Shortcuts

Playback

Space
Play / Pause / Resume
→
Step Forward
←
Step Backward (future)
Ctrl+S
Toggle Step Mode

Zoom & Pan

Ctrl++ / =
Zoom In
Ctrl+-
Zoom Out
Ctrl+0
Reset Zoom & Pan
Mouse Wheel
Zoom at Cursor
Drag
Pan Image

General

Ctrl+R
Reset All
Shift+/
Show This Help

Tip: Shortcuts don't work when typing in input fields. Click canvas to focus.

)}
) }