File size: 3,120 Bytes
80bcac8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
'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()
  }
}