'use client' import { useRef, useEffect } from 'react' import { useAppStore } from '@/store' import { PADDING_MODES } from '@/lib/kernels' import { Select, Card, CardContent } from '@/components/ui' import { cn } from '@/utils/helpers' import { Settings, Layers, Grid, ZoomIn, ZoomOut } from 'lucide-react' export function OverlayControls() { const { settings, updateSettings, toggleOverlay, setPadding } = useAppStore() const { showOverlay, padding } = settings return ( Visualization Overlays {Object.entries(showOverlay).map(([key, value]) => ( toggleOverlay(key as keyof typeof showOverlay)} className="rounded border-gray-300" /> {key.replace(/([A-Z])/g, ' $1')} ))} ) } export function PaddingControls() { const { settings, setPadding, originalImageData, selectedKernel } = useAppStore() const { padding } = settings return ( Padding Strategy setPadding(e.target.value as typeof padding)} options={PADDING_MODES.map((p) => ({ value: p.value, label: p.label }))} placeholder="Select padding mode" /> {PADDING_MODES.find((p) => p.value === padding)?.description} ) } function PaddingVisualization({ imageData, padding, kernelSize }: { imageData: ImageData | null padding: 'zero' | 'reflect' | 'replicate' | 'wrap' kernelSize: number }) { const canvasRef = useRef(null) const halfKernel = Math.floor(kernelSize / 2) const previewSize = 120 useEffect(() => { if (!imageData || !canvasRef.current) return const canvas = canvasRef.current const ctx = canvas.getContext('2d') if (!ctx) return // Small preview canvas canvas.width = previewSize canvas.height = previewSize ctx.clearRect(0, 0, previewSize, previewSize) const scale = Math.min(previewSize / (imageData.width + 2 * halfKernel), previewSize / (imageData.height + 2 * halfKernel)) const paddedWidth = (imageData.width + 2 * halfKernel) * scale const paddedHeight = (imageData.height + 2 * halfKernel) * scale const offsetX = (previewSize - paddedWidth) / 2 const offsetY = (previewSize - paddedHeight) / 2 // Draw padding visualization const getPixel = (x: number, y: number, channel: number): number => { let px = x let py = y const { width, height, data } = imageData switch (padding) { case 'zero': if (px < 0 || px >= width || py < 0 || py >= height) return 0 break case 'replicate': px = Math.max(0, Math.min(px, width - 1)) py = Math.max(0, Math.min(py, height - 1)) break case 'reflect': if (px < 0) px = -px if (px >= width) px = 2 * width - px - 2 if (py < 0) py = -py if (py >= height) py = 2 * height - py - 2 px = Math.max(0, Math.min(px, width - 1)) py = Math.max(0, Math.min(py, height - 1)) break case 'wrap': px = ((px % width) + width) % width py = ((py % height) + height) % height break } const index = (py * width + px) * 4 + channel return data[index] } // Draw padded image for (let py = -halfKernel; py < imageData.height + halfKernel; py++) { for (let px = -halfKernel; px < imageData.width + halfKernel; px++) { const r = getPixel(px, py, 0) const g = getPixel(px, py, 1) const b = getPixel(px, py, 2) const x = offsetX + (px + halfKernel) * scale const y = offsetY + (py + halfKernel) * scale ctx.fillStyle = `rgb(${r}, ${g}, ${b})` ctx.fillRect(x, y, Math.ceil(scale), Math.ceil(scale)) } } // Draw original image border ctx.strokeStyle = 'rgba(59, 130, 246, 0.8)' ctx.lineWidth = 2 ctx.setLineDash([4, 2]) ctx.strokeRect( offsetX + halfKernel * scale, offsetY + halfKernel * scale, imageData.width * scale, imageData.height * scale ) ctx.setLineDash([]) // Draw kernel center indicator ctx.fillStyle = 'rgba(239, 68, 68, 0.8)' ctx.beginPath() ctx.arc( offsetX + halfKernel * scale, offsetY + halfKernel * scale, 4, 0, Math.PI * 2 ) ctx.fill() // Draw kernel area outline ctx.strokeStyle = 'rgba(239, 68, 68, 0.5)' ctx.lineWidth = 1 ctx.strokeRect( offsetX, offsetY, paddedWidth, paddedHeight ) }, [imageData, padding, kernelSize]) return ( Padding Preview Original Image Kernel Center Padding Area ) } export function KernelInfoPanel() { const { selectedKernel } = useAppStore() if (!selectedKernel) { return ( No kernel selected ) } const { name, category, matrix, size, normalize, description, formula, reference } = selectedKernel return ( {name} {category} {size}×{size} {normalize && ( Normalized )} {description} {formula && ( Formula: {formula} )} Kernel Matrix {matrix.map((row, i) => row.map((val, j) => ( 0 ? 'bg-green-50 text-green-800 dark:bg-green-900/30 dark:text-green-300' : 'bg-red-50 text-red-800 dark:bg-red-900/30 dark:text-red-300' )} > {val.toFixed(val === Math.round(val) ? 0 : 2)} )) )} {reference && ( Reference )} ) }
{description}