Download src/components/canvas/ImageCanvas.tsx from TechAvenger/kernel-convolution-visualizer: direct link, hf CLI and curl.
- Browser
- Download file 3.12 kB
-
https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/main/src/components/canvas/ImageCanvas.tsx
- Command line
-
hf download hf://spaces/TechAvenger/kernel-convolution-visualizer/src/components/canvas/ImageCanvas.tsx
-
curl -L -o ImageCanvas.tsx https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/main/src/components/canvas/ImageCanvas.tsx
3.12 kB
| 'use client' | |
| import { useRef, useEffect, useCallback } from 'react' | |
| import { useAppStore } from '@/store' | |
| import { cn } from '@/utils/helpers' | |
| interface ImageCanvasProps { | |
| imageData: ImageData | null | |
| title: string | |
| width?: number | |
| height?: number | |
| className?: string | |
| onWheel?: (e: React.WheelEvent) => void | |
| onMouseDown?: (e: React.MouseEvent) => void | |
| onMouseMove?: (e: React.MouseEvent) => void | |
| onMouseUp?: (e: React.MouseEvent) => void | |
| } | |
| export function ImageCanvas({ | |
| imageData, | |
| title, | |
| width = 400, | |
| height = 300, | |
| className, | |
| onWheel, | |
| onMouseDown, | |
| onMouseMove, | |
| onMouseUp, | |
| }: ImageCanvasProps) { | |
| const canvasRef = useRef<HTMLCanvasElement>(null) | |
| const { settings } = useAppStore() | |
| const drawCanvas = useCallback(() => { | |
| const canvas = canvasRef.current | |
| const ctx = canvas?.getContext('2d', { willReadFrequently: true }) | |
| if (!canvas || !ctx || !imageData) return | |
| const { zoomLevel, panOffset } = settings | |
| const displayWidth = Math.round(imageData.width * zoomLevel) | |
| const displayHeight = Math.round(imageData.height * zoomLevel) | |
| canvas.width = displayWidth | |
| canvas.height = displayHeight | |
| ctx.clearRect(0, 0, displayWidth, displayHeight) | |
| ctx.imageSmoothingEnabled = zoomLevel < 2 | |
| const tempCanvas = document.createElement('canvas') | |
| tempCanvas.width = imageData.width | |
| tempCanvas.height = imageData.height | |
| const tempCtx = tempCanvas.getContext('2d') | |
| if (tempCtx) { | |
| tempCtx.putImageData(imageData, 0, 0) | |
| ctx.drawImage( | |
| tempCanvas, | |
| 0, | |
| 0, | |
| imageData.width, | |
| imageData.height, | |
| panOffset.x, | |
| panOffset.y, | |
| displayWidth, | |
| displayHeight | |
| ) | |
| } | |
| // Draw grid at high zoom | |
| if (zoomLevel >= 4) { | |
| drawGrid(ctx, imageData.width, imageData.height, zoomLevel, panOffset) | |
| } | |
| }, [imageData, settings.zoomLevel, settings.panOffset]) | |
| useEffect(() => { | |
| drawCanvas() | |
| }, [drawCanvas]) | |
| return ( | |
| <div className={cn('relative bg-gray-100 dark:bg-gray-900 rounded-lg overflow-hidden', className)}> | |
| <div className="absolute top-2 left-2 z-10 px-2 py-1 bg-black/50 text-white text-xs rounded"> | |
| {title} | |
| </div> | |
| <canvas | |
| ref={canvasRef} | |
| width={width} | |
| height={height} | |
| className="block cursor-grab active:cursor-grabbing" | |
| style={{ touchAction: 'none' }} | |
| onWheel={onWheel} | |
| onMouseDown={onMouseDown} | |
| onMouseMove={onMouseMove} | |
| onMouseUp={onMouseUp} | |
| /> | |
| </div> | |
| ) | |
| } | |
| function drawGrid( | |
| ctx: CanvasRenderingContext2D, | |
| imgWidth: number, | |
| imgHeight: number, | |
| zoom: number, | |
| pan: { x: number; y: number } | |
| ) { | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)' | |
| ctx.lineWidth = 0.5 | |
| for (let x = 0; x <= imgWidth; x++) { | |
| const px = pan.x + x * zoom | |
| ctx.beginPath() | |
| ctx.moveTo(px, 0) | |
| ctx.lineTo(px, imgHeight * zoom) | |
| ctx.stroke() | |
| } | |
| for (let y = 0; y <= imgHeight; y++) { | |
| const py = pan.y + y * zoom | |
| ctx.beginPath() | |
| ctx.moveTo(0, py) | |
| ctx.lineTo(imgWidth * zoom, py) | |
| ctx.stroke() | |
| } | |
| } |