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,
}
} |