File size: 3,690 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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { useCallback, useRef, useEffect } from 'react'
import { useAppStore } from '@/store'

export function useCanvas() {
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const contextRef = useRef<CanvasRenderingContext2D | null>(null)

  useEffect(() => {
    if (canvasRef.current) {
      contextRef.current = canvasRef.current.getContext('2d', { willReadFrequently: true })
    }
  }, [])

  const drawImage = useCallback((imageData: ImageData) => {
    const ctx = contextRef.current
    const canvas = canvasRef.current
    if (!ctx || !canvas) return

    canvas.width = imageData.width
    canvas.height = imageData.height
    ctx.putImageData(imageData, 0, 0)
  }, [])

  const drawImageScaled = useCallback(
    (imageData: ImageData, scale: number, offsetX: number, offsetY: number) => {
      const ctx = contextRef.current
      const canvas = canvasRef.current
      if (!ctx || !canvas) return

      const displayWidth = Math.round(imageData.width * scale)
      const displayHeight = Math.round(imageData.height * scale)

      canvas.width = displayWidth
      canvas.height = displayHeight

      ctx.imageSmoothingEnabled = false
      ctx.drawImage(
        createImageBitmapFromImageData(imageData),
        0,
        0,
        imageData.width,
        imageData.height,
        offsetX,
        offsetY,
        displayWidth,
        displayHeight
      )
    },
    []
  )

  const clear = useCallback(() => {
    const ctx = contextRef.current
    const canvas = canvasRef.current
    if (!ctx || !canvas) return
    ctx.clearRect(0, 0, canvas.width, canvas.height)
  }, [])

  const getCanvas = useCallback(() => canvasRef.current, [])
  const getContext = useCallback(() => contextRef.current, [])

  return {
    canvasRef,
    drawImage,
    drawImageScaled,
    clear,
    getCanvas,
    getContext,
  }
}

function createImageBitmapFromImageData(imageData: ImageData): ImageBitmap {
  const canvas = document.createElement('canvas')
  canvas.width = imageData.width
  canvas.height = imageData.height
  const ctx = canvas.getContext('2d')
  if (!ctx) throw new Error('Failed to create canvas context')
  ctx.putImageData(imageData, 0, 0)
  // Note: createImageBitmap is async, this is a synchronous fallback
  return canvas as unknown as ImageBitmap
}

export function useZoomPan() {
  const { settings, setZoomLevel, setPanOffset } = useAppStore()
  const zoomRef = useRef(settings.zoomLevel)
  const panRef = useRef(settings.panOffset)

  useEffect(() => {
    zoomRef.current = settings.zoomLevel
    panRef.current = settings.panOffset
  }, [settings.zoomLevel, settings.panOffset])

  const zoomIn = useCallback(() => {
    const newZoom = Math.min(zoomRef.current * 1.2, 20)
    setZoomLevel(newZoom)
  }, [setZoomLevel])

  const zoomOut = useCallback(() => {
    const newZoom = Math.max(zoomRef.current / 1.2, 0.1)
    setZoomLevel(newZoom)
  }, [setZoomLevel])

  const resetZoom = useCallback(() => {
    setZoomLevel(1)
    setPanOffset({ x: 0, y: 0 })
  }, [setZoomLevel, setPanOffset])

  const setZoom = useCallback(
    (zoom: number) => {
      setZoomLevel(Math.max(0.1, Math.min(20, zoom)))
    },
    [setZoomLevel]
  )

  const pan = useCallback(
    (deltaX: number, deltaY: number) => {
      const newPan = {
        x: panRef.current.x + deltaX,
        y: panRef.current.y + deltaY,
      }
      setPanOffset(newPan)
    },
    [setPanOffset]
  )

  const panTo = useCallback(
    (x: number, y: number) => {
      setPanOffset({ x, y })
    },
    [setPanOffset]
  )

  return {
    zoom: settings.zoomLevel,
    panOffset: settings.panOffset,
    zoomIn,
    zoomOut,
    resetZoom,
    setZoom,
    pan,
    panTo,
  }
}