mihirmithani
Add Kernel Convolution Visualizer - Phase 1 complete
80bcac8
Raw History Blame
6.51 kB
'use client'
import { useRef, useEffect, useCallback, useState } from 'react'
import { useAppStore } from '@/store'
import { cn } from '@/utils/helpers'
interface SplitViewProps {
width?: number
height?: number
className?: string
}
export function SplitView({ width = 800, height = 600, className }: SplitViewProps) {
const canvasRef = useRef<HTMLCanvasElement>(null)
const { originalImageData, processedImageData } = useAppStore()
const { splitPosition, mode, setComparisonViewMode, setSplitPosition } = useAppStore((state) => ({
splitPosition: state.comparison.splitPosition,
mode: state.comparison.mode,
setComparisonViewMode: state.setComparisonViewMode,
setSplitPosition: state.setSplitPosition,
}))
const [isDragging, setIsDragging] = useState(false)
const drawCanvas = useCallback(() => {
const canvas = canvasRef.current
const ctx = canvas?.getContext('2d', { willReadFrequently: true })
if (!canvas || !ctx || !originalImageData) return
const displayWidth = width
const displayHeight = height
canvas.width = displayWidth
canvas.height = displayHeight
ctx.clearRect(0, 0, displayWidth, displayHeight)
const splitX = displayWidth * splitPosition
if (mode === 'split' || mode === 'overlay') {
// Draw original on left
if (originalImageData) {
const tempCanvas = document.createElement('canvas')
tempCanvas.width = originalImageData.width
tempCanvas.height = originalImageData.height
const tempCtx = tempCanvas.getContext('2d')
if (tempCtx) {
tempCtx.putImageData(originalImageData, 0, 0)
ctx.drawImage(
tempCanvas,
0,
0,
originalImageData.width,
originalImageData.height,
0,
0,
splitX,
displayHeight
)
}
}
// Draw processed on right
if (processedImageData) {
const tempCanvas = document.createElement('canvas')
tempCanvas.width = processedImageData.width
tempCanvas.height = processedImageData.height
const tempCtx = tempCanvas.getContext('2d')
if (tempCtx) {
tempCtx.putImageData(processedImageData, 0, 0)
ctx.drawImage(
tempCanvas,
splitX * (originalImageData.width / displayWidth),
0,
processedImageData.width - splitX * (originalImageData.width / displayWidth),
processedImageData.height,
splitX,
0,
displayWidth - splitX,
displayHeight
)
}
}
// Draw divider
ctx.strokeStyle = '#fff'
ctx.lineWidth = 3
ctx.setLineDash([10, 5])
ctx.beginPath()
ctx.moveTo(splitX, 0)
ctx.lineTo(splitX, displayHeight)
ctx.stroke()
ctx.setLineDash([])
// Draw handle
ctx.fillStyle = '#3b82f6'
ctx.beginPath()
ctx.arc(splitX, displayHeight / 2, 12, 0, Math.PI * 2)
ctx.fill()
ctx.strokeStyle = '#fff'
ctx.lineWidth = 2
ctx.stroke()
} else if (mode === 'side-by-side') {
// Side by side view
const halfWidth = displayWidth / 2
if (originalImageData) {
const tempCanvas = document.createElement('canvas')
tempCanvas.width = originalImageData.width
tempCanvas.height = originalImageData.height
const tempCtx = tempCanvas.getContext('2d')
if (tempCtx) {
tempCtx.putImageData(originalImageData, 0, 0)
ctx.drawImage(tempCanvas, 0, 0, halfWidth, displayHeight)
}
}
if (processedImageData) {
const tempCanvas = document.createElement('canvas')
tempCanvas.width = processedImageData.width
tempCanvas.height = processedImageData.height
const tempCtx = tempCanvas.getContext('2d')
if (tempCtx) {
tempCtx.putImageData(processedImageData, 0, 0)
ctx.drawImage(tempCanvas, halfWidth, 0, halfWidth, displayHeight)
}
}
// Draw center line
ctx.strokeStyle = 'rgba(255,255,255,0.5)'
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(halfWidth, 0)
ctx.lineTo(halfWidth, displayHeight)
ctx.stroke()
}
}, [originalImageData, processedImageData, splitPosition, mode, width, height])
useEffect(() => {
drawCanvas()
}, [drawCanvas])
const handleMouseDown = useCallback((e: React.MouseEvent) => {
if (mode !== 'split') return
const rect = canvasRef.current?.getBoundingClientRect()
if (!rect) return
const x = e.clientX - rect.left
if (Math.abs(x - width * splitPosition) < 20) {
setIsDragging(true)
}
}, [mode, splitPosition, width])
const handleMouseMove = useCallback(
(e: React.MouseEvent) => {
if (!isDragging || mode !== 'split') return
const rect = canvasRef.current?.getBoundingClientRect()
if (!rect) return
const x = e.clientX - rect.left
const newPosition = Math.max(0.1, Math.min(0.9, x / width))
setSplitPosition(newPosition)
},
[isDragging, mode, width, setSplitPosition]
)
const handleMouseUp = useCallback(() => {
setIsDragging(false)
}, [])
return (
<div className={cn('relative bg-gray-100 dark:bg-gray-900 rounded-lg overflow-hidden', className)}>
<canvas
ref={canvasRef}
width={width}
height={height}
className="block"
style={{ cursor: isDragging ? 'ew-resize' : mode === 'split' ? 'ew-resize' : 'default' }}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
/>
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 flex gap-2">
<button
className={cn('px-3 py-1 text-xs rounded', mode === 'split' && 'bg-blue-600 text-white')}
onClick={() => setComparisonViewMode('split')}
>
Split
</button>
<button
className={cn('px-3 py-1 text-xs rounded', mode === 'side-by-side' && 'bg-blue-600 text-white')}
onClick={() => setComparisonViewMode('side-by-side')}
>
Side by Side
</button>
<button
className={cn('px-3 py-1 text-xs rounded', mode === 'overlay' && 'bg-blue-600 text-white')}
onClick={() => setComparisonViewMode('overlay')}
>
Overlay
</button>
</div>
</div>
)
}