Upload an image to begin
Drag & drop or click the upload area
'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 (
Upload an image to begin
Drag & drop or click the upload area
Drag & drop or click the upload area in the left sidebar.
Choose from predefined kernels (Blur, Sharpen, Edge Detection, etc.) or create your own custom kernel.
Press Play to see the kernel move across the image pixel by pixel. The visualization shows:
Click “Show Comparison” to see side-by-side or split view of original vs processed image.
Tip: Shortcuts don't work when typing in input fields. Click canvas to focus.