Download src/components/canvas/SplitView.tsx from TechAvenger/kernel-convolution-visualizer: direct link, hf CLI and curl.
- Browser
- Download file 6.51 kB
-
https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/25e773913dbd4e9dcd6b40f787d68b280ccdfca1/src/components/canvas/SplitView.tsx
- Command line
-
hf download hf://spaces/TechAvenger/kernel-convolution-visualizer@25e773913dbd4e9dcd6b40f787d68b280ccdfca1/src/components/canvas/SplitView.tsx
-
curl -L -o SplitView.tsx https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/25e773913dbd4e9dcd6b40f787d68b280ccdfca1/src/components/canvas/SplitView.tsx
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> | |
| ) | |
| } |